agno 2.1.2__py3-none-any.whl → 2.3.13__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 (314) hide show
  1. agno/agent/agent.py +5540 -2273
  2. agno/api/api.py +2 -0
  3. agno/api/os.py +1 -1
  4. agno/compression/__init__.py +3 -0
  5. agno/compression/manager.py +247 -0
  6. agno/culture/__init__.py +3 -0
  7. agno/culture/manager.py +956 -0
  8. agno/db/async_postgres/__init__.py +3 -0
  9. agno/db/base.py +689 -6
  10. agno/db/dynamo/dynamo.py +933 -37
  11. agno/db/dynamo/schemas.py +174 -10
  12. agno/db/dynamo/utils.py +63 -4
  13. agno/db/firestore/firestore.py +831 -9
  14. agno/db/firestore/schemas.py +51 -0
  15. agno/db/firestore/utils.py +102 -4
  16. agno/db/gcs_json/gcs_json_db.py +660 -12
  17. agno/db/gcs_json/utils.py +60 -26
  18. agno/db/in_memory/in_memory_db.py +287 -14
  19. agno/db/in_memory/utils.py +60 -2
  20. agno/db/json/json_db.py +590 -14
  21. agno/db/json/utils.py +60 -26
  22. agno/db/migrations/manager.py +199 -0
  23. agno/db/migrations/v1_to_v2.py +43 -13
  24. agno/db/migrations/versions/__init__.py +0 -0
  25. agno/db/migrations/versions/v2_3_0.py +938 -0
  26. agno/db/mongo/__init__.py +15 -1
  27. agno/db/mongo/async_mongo.py +2760 -0
  28. agno/db/mongo/mongo.py +879 -11
  29. agno/db/mongo/schemas.py +42 -0
  30. agno/db/mongo/utils.py +80 -8
  31. agno/db/mysql/__init__.py +2 -1
  32. agno/db/mysql/async_mysql.py +2912 -0
  33. agno/db/mysql/mysql.py +946 -68
  34. agno/db/mysql/schemas.py +72 -10
  35. agno/db/mysql/utils.py +198 -7
  36. agno/db/postgres/__init__.py +2 -1
  37. agno/db/postgres/async_postgres.py +2579 -0
  38. agno/db/postgres/postgres.py +942 -57
  39. agno/db/postgres/schemas.py +81 -18
  40. agno/db/postgres/utils.py +164 -2
  41. agno/db/redis/redis.py +671 -7
  42. agno/db/redis/schemas.py +50 -0
  43. agno/db/redis/utils.py +65 -7
  44. agno/db/schemas/__init__.py +2 -1
  45. agno/db/schemas/culture.py +120 -0
  46. agno/db/schemas/evals.py +1 -0
  47. agno/db/schemas/memory.py +17 -2
  48. agno/db/singlestore/schemas.py +63 -0
  49. agno/db/singlestore/singlestore.py +949 -83
  50. agno/db/singlestore/utils.py +60 -2
  51. agno/db/sqlite/__init__.py +2 -1
  52. agno/db/sqlite/async_sqlite.py +2911 -0
  53. agno/db/sqlite/schemas.py +62 -0
  54. agno/db/sqlite/sqlite.py +965 -46
  55. agno/db/sqlite/utils.py +169 -8
  56. agno/db/surrealdb/__init__.py +3 -0
  57. agno/db/surrealdb/metrics.py +292 -0
  58. agno/db/surrealdb/models.py +334 -0
  59. agno/db/surrealdb/queries.py +71 -0
  60. agno/db/surrealdb/surrealdb.py +1908 -0
  61. agno/db/surrealdb/utils.py +147 -0
  62. agno/db/utils.py +2 -0
  63. agno/eval/__init__.py +10 -0
  64. agno/eval/accuracy.py +75 -55
  65. agno/eval/agent_as_judge.py +861 -0
  66. agno/eval/base.py +29 -0
  67. agno/eval/performance.py +16 -7
  68. agno/eval/reliability.py +28 -16
  69. agno/eval/utils.py +35 -17
  70. agno/exceptions.py +27 -2
  71. agno/filters.py +354 -0
  72. agno/guardrails/prompt_injection.py +1 -0
  73. agno/hooks/__init__.py +3 -0
  74. agno/hooks/decorator.py +164 -0
  75. agno/integrations/discord/client.py +1 -1
  76. agno/knowledge/chunking/agentic.py +13 -10
  77. agno/knowledge/chunking/fixed.py +4 -1
  78. agno/knowledge/chunking/semantic.py +9 -4
  79. agno/knowledge/chunking/strategy.py +59 -15
  80. agno/knowledge/embedder/fastembed.py +1 -1
  81. agno/knowledge/embedder/nebius.py +1 -1
  82. agno/knowledge/embedder/ollama.py +8 -0
  83. agno/knowledge/embedder/openai.py +8 -8
  84. agno/knowledge/embedder/sentence_transformer.py +6 -2
  85. agno/knowledge/embedder/vllm.py +262 -0
  86. agno/knowledge/knowledge.py +1618 -318
  87. agno/knowledge/reader/base.py +6 -2
  88. agno/knowledge/reader/csv_reader.py +8 -10
  89. agno/knowledge/reader/docx_reader.py +5 -6
  90. agno/knowledge/reader/field_labeled_csv_reader.py +16 -20
  91. agno/knowledge/reader/json_reader.py +5 -4
  92. agno/knowledge/reader/markdown_reader.py +8 -8
  93. agno/knowledge/reader/pdf_reader.py +17 -19
  94. agno/knowledge/reader/pptx_reader.py +101 -0
  95. agno/knowledge/reader/reader_factory.py +32 -3
  96. agno/knowledge/reader/s3_reader.py +3 -3
  97. agno/knowledge/reader/tavily_reader.py +193 -0
  98. agno/knowledge/reader/text_reader.py +22 -10
  99. agno/knowledge/reader/web_search_reader.py +1 -48
  100. agno/knowledge/reader/website_reader.py +10 -10
  101. agno/knowledge/reader/wikipedia_reader.py +33 -1
  102. agno/knowledge/types.py +1 -0
  103. agno/knowledge/utils.py +72 -7
  104. agno/media.py +22 -6
  105. agno/memory/__init__.py +14 -1
  106. agno/memory/manager.py +544 -83
  107. agno/memory/strategies/__init__.py +15 -0
  108. agno/memory/strategies/base.py +66 -0
  109. agno/memory/strategies/summarize.py +196 -0
  110. agno/memory/strategies/types.py +37 -0
  111. agno/models/aimlapi/aimlapi.py +17 -0
  112. agno/models/anthropic/claude.py +515 -40
  113. agno/models/aws/bedrock.py +102 -21
  114. agno/models/aws/claude.py +131 -274
  115. agno/models/azure/ai_foundry.py +41 -19
  116. agno/models/azure/openai_chat.py +39 -8
  117. agno/models/base.py +1249 -525
  118. agno/models/cerebras/cerebras.py +91 -21
  119. agno/models/cerebras/cerebras_openai.py +21 -2
  120. agno/models/cohere/chat.py +40 -6
  121. agno/models/cometapi/cometapi.py +18 -1
  122. agno/models/dashscope/dashscope.py +2 -3
  123. agno/models/deepinfra/deepinfra.py +18 -1
  124. agno/models/deepseek/deepseek.py +69 -3
  125. agno/models/fireworks/fireworks.py +18 -1
  126. agno/models/google/gemini.py +877 -80
  127. agno/models/google/utils.py +22 -0
  128. agno/models/groq/groq.py +51 -18
  129. agno/models/huggingface/huggingface.py +17 -6
  130. agno/models/ibm/watsonx.py +16 -6
  131. agno/models/internlm/internlm.py +18 -1
  132. agno/models/langdb/langdb.py +13 -1
  133. agno/models/litellm/chat.py +44 -9
  134. agno/models/litellm/litellm_openai.py +18 -1
  135. agno/models/message.py +28 -5
  136. agno/models/meta/llama.py +47 -14
  137. agno/models/meta/llama_openai.py +22 -17
  138. agno/models/mistral/mistral.py +8 -4
  139. agno/models/nebius/nebius.py +6 -7
  140. agno/models/nvidia/nvidia.py +20 -3
  141. agno/models/ollama/chat.py +24 -8
  142. agno/models/openai/chat.py +104 -29
  143. agno/models/openai/responses.py +101 -81
  144. agno/models/openrouter/openrouter.py +60 -3
  145. agno/models/perplexity/perplexity.py +17 -1
  146. agno/models/portkey/portkey.py +7 -6
  147. agno/models/requesty/requesty.py +24 -4
  148. agno/models/response.py +73 -2
  149. agno/models/sambanova/sambanova.py +20 -3
  150. agno/models/siliconflow/siliconflow.py +19 -2
  151. agno/models/together/together.py +20 -3
  152. agno/models/utils.py +254 -8
  153. agno/models/vercel/v0.py +20 -3
  154. agno/models/vertexai/__init__.py +0 -0
  155. agno/models/vertexai/claude.py +190 -0
  156. agno/models/vllm/vllm.py +19 -14
  157. agno/models/xai/xai.py +19 -2
  158. agno/os/app.py +549 -152
  159. agno/os/auth.py +190 -3
  160. agno/os/config.py +23 -0
  161. agno/os/interfaces/a2a/router.py +8 -11
  162. agno/os/interfaces/a2a/utils.py +1 -1
  163. agno/os/interfaces/agui/router.py +18 -3
  164. agno/os/interfaces/agui/utils.py +152 -39
  165. agno/os/interfaces/slack/router.py +55 -37
  166. agno/os/interfaces/slack/slack.py +9 -1
  167. agno/os/interfaces/whatsapp/router.py +0 -1
  168. agno/os/interfaces/whatsapp/security.py +3 -1
  169. agno/os/mcp.py +110 -52
  170. agno/os/middleware/__init__.py +2 -0
  171. agno/os/middleware/jwt.py +676 -112
  172. agno/os/router.py +40 -1478
  173. agno/os/routers/agents/__init__.py +3 -0
  174. agno/os/routers/agents/router.py +599 -0
  175. agno/os/routers/agents/schema.py +261 -0
  176. agno/os/routers/evals/evals.py +96 -39
  177. agno/os/routers/evals/schemas.py +65 -33
  178. agno/os/routers/evals/utils.py +80 -10
  179. agno/os/routers/health.py +10 -4
  180. agno/os/routers/knowledge/knowledge.py +196 -38
  181. agno/os/routers/knowledge/schemas.py +82 -22
  182. agno/os/routers/memory/memory.py +279 -52
  183. agno/os/routers/memory/schemas.py +46 -17
  184. agno/os/routers/metrics/metrics.py +20 -8
  185. agno/os/routers/metrics/schemas.py +16 -16
  186. agno/os/routers/session/session.py +462 -34
  187. agno/os/routers/teams/__init__.py +3 -0
  188. agno/os/routers/teams/router.py +512 -0
  189. agno/os/routers/teams/schema.py +257 -0
  190. agno/os/routers/traces/__init__.py +3 -0
  191. agno/os/routers/traces/schemas.py +414 -0
  192. agno/os/routers/traces/traces.py +499 -0
  193. agno/os/routers/workflows/__init__.py +3 -0
  194. agno/os/routers/workflows/router.py +624 -0
  195. agno/os/routers/workflows/schema.py +75 -0
  196. agno/os/schema.py +256 -693
  197. agno/os/scopes.py +469 -0
  198. agno/os/utils.py +514 -36
  199. agno/reasoning/anthropic.py +80 -0
  200. agno/reasoning/gemini.py +73 -0
  201. agno/reasoning/openai.py +5 -0
  202. agno/reasoning/vertexai.py +76 -0
  203. agno/run/__init__.py +6 -0
  204. agno/run/agent.py +155 -32
  205. agno/run/base.py +55 -3
  206. agno/run/requirement.py +181 -0
  207. agno/run/team.py +125 -38
  208. agno/run/workflow.py +72 -18
  209. agno/session/agent.py +102 -89
  210. agno/session/summary.py +56 -15
  211. agno/session/team.py +164 -90
  212. agno/session/workflow.py +405 -40
  213. agno/table.py +10 -0
  214. agno/team/team.py +3974 -1903
  215. agno/tools/dalle.py +2 -4
  216. agno/tools/eleven_labs.py +23 -25
  217. agno/tools/exa.py +21 -16
  218. agno/tools/file.py +153 -23
  219. agno/tools/file_generation.py +16 -10
  220. agno/tools/firecrawl.py +15 -7
  221. agno/tools/function.py +193 -38
  222. agno/tools/gmail.py +238 -14
  223. agno/tools/google_drive.py +271 -0
  224. agno/tools/googlecalendar.py +36 -8
  225. agno/tools/googlesheets.py +20 -5
  226. agno/tools/jira.py +20 -0
  227. agno/tools/mcp/__init__.py +10 -0
  228. agno/tools/mcp/mcp.py +331 -0
  229. agno/tools/mcp/multi_mcp.py +347 -0
  230. agno/tools/mcp/params.py +24 -0
  231. agno/tools/mcp_toolbox.py +3 -3
  232. agno/tools/models/nebius.py +5 -5
  233. agno/tools/models_labs.py +20 -10
  234. agno/tools/nano_banana.py +151 -0
  235. agno/tools/notion.py +204 -0
  236. agno/tools/parallel.py +314 -0
  237. agno/tools/postgres.py +76 -36
  238. agno/tools/redshift.py +406 -0
  239. agno/tools/scrapegraph.py +1 -1
  240. agno/tools/shopify.py +1519 -0
  241. agno/tools/slack.py +18 -3
  242. agno/tools/spotify.py +919 -0
  243. agno/tools/tavily.py +146 -0
  244. agno/tools/toolkit.py +25 -0
  245. agno/tools/workflow.py +8 -1
  246. agno/tools/yfinance.py +12 -11
  247. agno/tracing/__init__.py +12 -0
  248. agno/tracing/exporter.py +157 -0
  249. agno/tracing/schemas.py +276 -0
  250. agno/tracing/setup.py +111 -0
  251. agno/utils/agent.py +938 -0
  252. agno/utils/cryptography.py +22 -0
  253. agno/utils/dttm.py +33 -0
  254. agno/utils/events.py +151 -3
  255. agno/utils/gemini.py +15 -5
  256. agno/utils/hooks.py +118 -4
  257. agno/utils/http.py +113 -2
  258. agno/utils/knowledge.py +12 -5
  259. agno/utils/log.py +1 -0
  260. agno/utils/mcp.py +92 -2
  261. agno/utils/media.py +187 -1
  262. agno/utils/merge_dict.py +3 -3
  263. agno/utils/message.py +60 -0
  264. agno/utils/models/ai_foundry.py +9 -2
  265. agno/utils/models/claude.py +49 -14
  266. agno/utils/models/cohere.py +9 -2
  267. agno/utils/models/llama.py +9 -2
  268. agno/utils/models/mistral.py +4 -2
  269. agno/utils/print_response/agent.py +109 -16
  270. agno/utils/print_response/team.py +223 -30
  271. agno/utils/print_response/workflow.py +251 -34
  272. agno/utils/streamlit.py +1 -1
  273. agno/utils/team.py +98 -9
  274. agno/utils/tokens.py +657 -0
  275. agno/vectordb/base.py +39 -7
  276. agno/vectordb/cassandra/cassandra.py +21 -5
  277. agno/vectordb/chroma/chromadb.py +43 -12
  278. agno/vectordb/clickhouse/clickhousedb.py +21 -5
  279. agno/vectordb/couchbase/couchbase.py +29 -5
  280. agno/vectordb/lancedb/lance_db.py +92 -181
  281. agno/vectordb/langchaindb/langchaindb.py +24 -4
  282. agno/vectordb/lightrag/lightrag.py +17 -3
  283. agno/vectordb/llamaindex/llamaindexdb.py +25 -5
  284. agno/vectordb/milvus/milvus.py +50 -37
  285. agno/vectordb/mongodb/__init__.py +7 -1
  286. agno/vectordb/mongodb/mongodb.py +36 -30
  287. agno/vectordb/pgvector/pgvector.py +201 -77
  288. agno/vectordb/pineconedb/pineconedb.py +41 -23
  289. agno/vectordb/qdrant/qdrant.py +67 -54
  290. agno/vectordb/redis/__init__.py +9 -0
  291. agno/vectordb/redis/redisdb.py +682 -0
  292. agno/vectordb/singlestore/singlestore.py +50 -29
  293. agno/vectordb/surrealdb/surrealdb.py +31 -41
  294. agno/vectordb/upstashdb/upstashdb.py +34 -6
  295. agno/vectordb/weaviate/weaviate.py +53 -14
  296. agno/workflow/__init__.py +2 -0
  297. agno/workflow/agent.py +299 -0
  298. agno/workflow/condition.py +120 -18
  299. agno/workflow/loop.py +77 -10
  300. agno/workflow/parallel.py +231 -143
  301. agno/workflow/router.py +118 -17
  302. agno/workflow/step.py +609 -170
  303. agno/workflow/steps.py +73 -6
  304. agno/workflow/types.py +96 -21
  305. agno/workflow/workflow.py +2039 -262
  306. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/METADATA +201 -66
  307. agno-2.3.13.dist-info/RECORD +613 -0
  308. agno/tools/googlesearch.py +0 -98
  309. agno/tools/mcp.py +0 -679
  310. agno/tools/memori.py +0 -339
  311. agno-2.1.2.dist-info/RECORD +0 -543
  312. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +0 -0
  313. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/licenses/LICENSE +0 -0
  314. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/top_level.txt +0 -0
agno/os/scopes.py ADDED
@@ -0,0 +1,469 @@
1
+ """AgentOS RBAC Scopes
2
+
3
+ This module defines all available permission scopes for AgentOS RBAC (Role-Based Access Control).
4
+
5
+ Scope Format:
6
+ - Global resource scopes: `resource:action`
7
+ - Per-resource scopes: `resource:<resource-id>:action`
8
+ - Wildcards: `resource:*:action` for any resource
9
+
10
+ The AgentOS ID is verified via the JWT `aud` (audience) claim.
11
+
12
+ Examples:
13
+ - `system:read` - Read system config
14
+ - `agents:read` - List all agents
15
+ - `agents:web-agent:read` - Read specific agent
16
+ - `agents:web-agent:run` - Run specific agent
17
+ - `agents:*:run` - Run any agent (wildcard)
18
+ - `agent_os:admin` - Full access to everything
19
+ """
20
+
21
+ from dataclasses import dataclass
22
+ from enum import Enum
23
+ from typing import Dict, List, Optional, Set
24
+
25
+
26
+ class AgentOSScope(str, Enum):
27
+ """
28
+ Enum of all available AgentOS permission scopes.
29
+
30
+ Special Scopes:
31
+ - ADMIN: Grants full access to all endpoints (agent_os:admin)
32
+
33
+ Scope format:
34
+
35
+ Global Resource Scopes:
36
+ - system:read - System configuration and model information
37
+ - agents:read - List all agents
38
+ - teams:read - List all teams
39
+ - workflows:read - List all workflows
40
+ - sessions:read - View session data
41
+ - sessions:write - Create and update sessions
42
+ - sessions:delete - Delete sessions
43
+ - memories:read - View memories
44
+ - memories:write - Create and update memories
45
+ - memories:delete - Delete memories
46
+ - knowledge:read - View and search knowledge
47
+ - knowledge:write - Add and update knowledge
48
+ - knowledge:delete - Delete knowledge
49
+ - metrics:read - View metrics
50
+ - metrics:write - Refresh metrics
51
+ - evals:read - View evaluation runs
52
+ - evals:write - Create and update evaluation runs
53
+ - evals:delete - Delete evaluation runs
54
+ - traces:read - View traces and trace statistics
55
+
56
+ Per-Resource Scopes (with resource ID):
57
+ - agents:<agent-id>:read - Read specific agent
58
+ - agents:<agent-id>:run - Run specific agent
59
+ - teams:<team-id>:read - Read specific team
60
+ - teams:<team-id>:run - Run specific team
61
+ - workflows:<workflow-id>:read - Read specific workflow
62
+ - workflows:<workflow-id>:run - Run specific workflow
63
+
64
+ Wildcards:
65
+ - agents:*:run - Run any agent
66
+ - teams:*:run - Run any team
67
+ """
68
+
69
+ # Special scopes
70
+ ADMIN = "agent_os:admin"
71
+
72
+
73
+ @dataclass
74
+ class ParsedScope:
75
+ """Represents a parsed scope with its components."""
76
+
77
+ raw: str
78
+ scope_type: str # "admin", "global", "per_resource", or "unknown"
79
+ resource: Optional[str] = None
80
+ resource_id: Optional[str] = None
81
+ action: Optional[str] = None
82
+ is_wildcard_resource: bool = False
83
+
84
+ @property
85
+ def is_global_resource_scope(self) -> bool:
86
+ """Check if this scope targets all resources of a type (no resource_id)."""
87
+ return self.scope_type == "global"
88
+
89
+ @property
90
+ def is_per_resource_scope(self) -> bool:
91
+ """Check if this scope targets a specific resource (has resource_id)."""
92
+ return self.scope_type == "per_resource"
93
+
94
+
95
+ def parse_scope(scope: str, admin_scope: Optional[str] = None) -> ParsedScope:
96
+ """
97
+ Parse a scope string into its components.
98
+
99
+ Args:
100
+ scope: The scope string to parse
101
+ admin_scope: The scope string that grants admin access (default: "agent_os:admin")
102
+
103
+ Returns:
104
+ ParsedScope object with parsed components
105
+
106
+ Examples:
107
+ >>> parse_scope("agent_os:admin")
108
+ ParsedScope(raw="agent_os:admin", scope_type="admin")
109
+
110
+ >>> parse_scope("system:read")
111
+ ParsedScope(raw="system:read", scope_type="global", resource="system", action="read")
112
+
113
+ >>> parse_scope("agents:web-agent:read")
114
+ ParsedScope(raw="...", scope_type="per_resource", resource="agents", resource_id="web-agent", action="read")
115
+
116
+ >>> parse_scope("agents:*:run")
117
+ ParsedScope(raw="...", scope_type="per_resource", resource="agents", resource_id="*", action="run", is_wildcard_resource=True)
118
+ """
119
+ effective_admin_scope = admin_scope or AgentOSScope.ADMIN.value
120
+ if scope == effective_admin_scope:
121
+ return ParsedScope(raw=scope, scope_type="admin")
122
+
123
+ parts = scope.split(":")
124
+
125
+ # Global resource scope: resource:action (2 parts)
126
+ if len(parts) == 2:
127
+ return ParsedScope(
128
+ raw=scope,
129
+ scope_type="global",
130
+ resource=parts[0],
131
+ action=parts[1],
132
+ )
133
+
134
+ # Per-resource scope: resource:<resource-id>:action (3 parts)
135
+ if len(parts) == 3:
136
+ resource_id = parts[1]
137
+ is_wildcard_resource = resource_id == "*"
138
+
139
+ return ParsedScope(
140
+ raw=scope,
141
+ scope_type="per_resource",
142
+ resource=parts[0],
143
+ resource_id=resource_id,
144
+ action=parts[2],
145
+ is_wildcard_resource=is_wildcard_resource,
146
+ )
147
+
148
+ # Invalid format
149
+ return ParsedScope(raw=scope, scope_type="unknown")
150
+
151
+
152
+ def matches_scope(
153
+ user_scope: ParsedScope,
154
+ required_scope: ParsedScope,
155
+ resource_id: Optional[str] = None,
156
+ ) -> bool:
157
+ """
158
+ Check if a user's scope matches a required scope.
159
+
160
+ Args:
161
+ user_scope: The user's parsed scope
162
+ required_scope: The required parsed scope
163
+ resource_id: The specific resource ID being accessed
164
+
165
+ Returns:
166
+ True if the user's scope satisfies the required scope
167
+
168
+ Examples:
169
+ >>> user = parse_scope("system:read")
170
+ >>> required = parse_scope("system:read")
171
+ >>> matches_scope(user, required)
172
+ True
173
+
174
+ >>> user = parse_scope("agents:web-agent:run")
175
+ >>> required = parse_scope("agents:<id>:run")
176
+ >>> matches_scope(user, required, resource_id="web-agent")
177
+ True
178
+
179
+ >>> user = parse_scope("agents:*:run")
180
+ >>> required = parse_scope("agents:<id>:run")
181
+ >>> matches_scope(user, required, resource_id="web-agent")
182
+ True
183
+ """
184
+ # Admin always matches
185
+ if user_scope.scope_type == "admin":
186
+ return True
187
+
188
+ # Unknown scopes don't match anything
189
+ if user_scope.scope_type == "unknown" or required_scope.scope_type == "unknown":
190
+ return False
191
+
192
+ # Resource type must match
193
+ if user_scope.resource != required_scope.resource:
194
+ return False
195
+
196
+ # Action must match
197
+ if user_scope.action != required_scope.action:
198
+ return False
199
+
200
+ # If required scope has a resource_id, check it
201
+ if required_scope.resource_id:
202
+ # User has wildcard resource access
203
+ if user_scope.is_wildcard_resource:
204
+ return True
205
+ # User has global resource access (no resource_id in user scope)
206
+ if not user_scope.resource_id:
207
+ return True
208
+ # User has specific resource access - must match
209
+ return user_scope.resource_id == resource_id
210
+
211
+ # Required scope is global (no resource_id), user scope matches if:
212
+ # - User has global scope (no resource_id), OR
213
+ # - User has wildcard resource scope
214
+ return not user_scope.resource_id or user_scope.is_wildcard_resource
215
+
216
+
217
+ def has_required_scopes(
218
+ user_scopes: List[str],
219
+ required_scopes: List[str],
220
+ resource_type: Optional[str] = None,
221
+ resource_id: Optional[str] = None,
222
+ admin_scope: Optional[str] = None,
223
+ ) -> bool:
224
+ """
225
+ Check if user has all required scopes.
226
+
227
+ Args:
228
+ user_scopes: List of scope strings the user has
229
+ required_scopes: List of scope strings required
230
+ resource_type: Type of resource being accessed ("agents", "teams", "workflows")
231
+ resource_id: Specific resource ID being accessed
232
+ admin_scope: The scope string that grants admin access (default: "agent_os:admin")
233
+
234
+ Returns:
235
+ True if user has all required scopes
236
+
237
+ Examples:
238
+ >>> has_required_scopes(
239
+ ... ["agents:read"],
240
+ ... ["agents:read"],
241
+ ... )
242
+ True
243
+
244
+ >>> has_required_scopes(
245
+ ... ["agents:web-agent:run"],
246
+ ... ["agents:run"],
247
+ ... resource_type="agents",
248
+ ... resource_id="web-agent"
249
+ ... )
250
+ True
251
+
252
+ >>> has_required_scopes(
253
+ ... ["agents:*:run"],
254
+ ... ["agents:run"],
255
+ ... resource_type="agents",
256
+ ... resource_id="any-agent"
257
+ ... )
258
+ True
259
+ """
260
+ if not required_scopes:
261
+ return True
262
+
263
+ # Parse user scopes once
264
+ parsed_user_scopes = [parse_scope(scope, admin_scope=admin_scope) for scope in user_scopes]
265
+
266
+ # Check for admin scope
267
+ if any(s.scope_type == "admin" for s in parsed_user_scopes):
268
+ return True
269
+
270
+ # Check each required scope
271
+ for required_scope_str in required_scopes:
272
+ parts = required_scope_str.split(":")
273
+ if len(parts) == 2:
274
+ resource, action = parts
275
+ # Build the required scope based on context
276
+ if resource_id and resource_type:
277
+ # Per-resource scope required
278
+ full_required_scope = f"{resource_type}:<resource-id>:{action}"
279
+ else:
280
+ # Global resource scope required
281
+ full_required_scope = required_scope_str
282
+
283
+ required = parse_scope(full_required_scope, admin_scope=admin_scope)
284
+ else:
285
+ required = parse_scope(required_scope_str, admin_scope=admin_scope)
286
+
287
+ scope_matched = False
288
+ for user_scope in parsed_user_scopes:
289
+ if matches_scope(user_scope, required, resource_id=resource_id):
290
+ scope_matched = True
291
+ break
292
+
293
+ if not scope_matched:
294
+ return False
295
+
296
+ return True
297
+
298
+
299
+ def get_accessible_resource_ids(
300
+ user_scopes: List[str],
301
+ resource_type: str,
302
+ admin_scope: Optional[str] = None,
303
+ ) -> Set[str]:
304
+ """
305
+ Get the set of resource IDs the user has access to.
306
+
307
+ Args:
308
+ user_scopes: List of scope strings the user has
309
+ resource_type: Type of resource ("agents", "teams", "workflows")
310
+ admin_scope: The scope string that grants admin access (default: "agent_os:admin")
311
+
312
+ Returns:
313
+ Set of resource IDs the user can access. Returns {"*"} for wildcard access.
314
+
315
+ Examples:
316
+ >>> get_accessible_resource_ids(
317
+ ... ["agents:agent-1:read", "agents:agent-2:read"],
318
+ ... "agents"
319
+ ... )
320
+ {'agent-1', 'agent-2'}
321
+
322
+ >>> get_accessible_resource_ids(["agents:*:read"], "agents")
323
+ {'*'}
324
+
325
+ >>> get_accessible_resource_ids(["agents:read"], "agents")
326
+ {'*'}
327
+
328
+ >>> get_accessible_resource_ids(["admin"], "agents")
329
+ {'*'}
330
+ """
331
+ parsed_scopes = [parse_scope(scope, admin_scope=admin_scope) for scope in user_scopes]
332
+
333
+ # Check for admin or global wildcard access
334
+ for scope in parsed_scopes:
335
+ if scope.scope_type == "admin":
336
+ return {"*"}
337
+
338
+ # Check if resource type matches
339
+ if scope.resource == resource_type:
340
+ # Global resource scope (no resource_id) grants access to all
341
+ if not scope.resource_id and scope.action in ["read", "run"]:
342
+ return {"*"}
343
+ # Wildcard resource scope grants access to all
344
+ if scope.is_wildcard_resource and scope.action in ["read", "run"]:
345
+ return {"*"}
346
+
347
+ # Collect specific resource IDs
348
+ accessible_ids: Set[str] = set()
349
+ for scope in parsed_scopes:
350
+ # Check if resource type matches
351
+ if scope.resource == resource_type:
352
+ # Specific resource ID
353
+ if scope.resource_id and not scope.is_wildcard_resource and scope.action in ["read", "run"]:
354
+ accessible_ids.add(scope.resource_id)
355
+
356
+ return accessible_ids
357
+
358
+
359
+ def get_default_scope_mappings() -> Dict[str, List[str]]:
360
+ """
361
+ Get default scope mappings for AgentOS endpoints.
362
+
363
+ Returns a dictionary mapping route patterns (with HTTP methods) to required scope templates.
364
+ Format: "METHOD /path/pattern": ["resource:action"]
365
+ """
366
+ return {
367
+ # System endpoints
368
+ "GET /config": ["system:read"],
369
+ "GET /models": ["system:read"],
370
+ # Agent endpoints
371
+ "GET /agents": ["agents:read"],
372
+ "GET /agents/*": ["agents:read"],
373
+ "POST /agents": ["agents:write"],
374
+ "PATCH /agents/*": ["agents:write"],
375
+ "DELETE /agents/*": ["agents:delete"],
376
+ "POST /agents/*/runs": ["agents:run"],
377
+ "POST /agents/*/runs/*/continue": ["agents:run"],
378
+ "POST /agents/*/runs/*/cancel": ["agents:run"],
379
+ # Team endpoints
380
+ "GET /teams": ["teams:read"],
381
+ "GET /teams/*": ["teams:read"],
382
+ "POST /teams": ["teams:write"],
383
+ "PATCH /teams/*": ["teams:write"],
384
+ "DELETE /teams/*": ["teams:delete"],
385
+ "POST /teams/*/runs": ["teams:run"],
386
+ "POST /teams/*/runs/*/continue": ["teams:run"],
387
+ "POST /teams/*/runs/*/cancel": ["teams:run"],
388
+ # Workflow endpoints
389
+ "GET /workflows": ["workflows:read"],
390
+ "GET /workflows/*": ["workflows:read"],
391
+ "POST /workflows": ["workflows:write"],
392
+ "PATCH /workflows/*": ["workflows:write"],
393
+ "DELETE /workflows/*": ["workflows:delete"],
394
+ "POST /workflows/*/runs": ["workflows:run"],
395
+ "POST /workflows/*/runs/*/continue": ["workflows:run"],
396
+ "POST /workflows/*/runs/*/cancel": ["workflows:run"],
397
+ # Session endpoints
398
+ "GET /sessions": ["sessions:read"],
399
+ "GET /sessions/*": ["sessions:read"],
400
+ "POST /sessions": ["sessions:write"],
401
+ "POST /sessions/*/rename": ["sessions:write"],
402
+ "PATCH /sessions/*": ["sessions:write"],
403
+ "DELETE /sessions": ["sessions:delete"],
404
+ "DELETE /sessions/*": ["sessions:delete"],
405
+ # Memory endpoints
406
+ "GET /memories": ["memories:read"],
407
+ "GET /memories/*": ["memories:read"],
408
+ "GET /memory_topics": ["memories:read"],
409
+ "GET /user_memory_stats": ["memories:read"],
410
+ "POST /memories": ["memories:write"],
411
+ "PATCH /memories/*": ["memories:write"],
412
+ "DELETE /memories": ["memories:delete"],
413
+ "DELETE /memories/*": ["memories:delete"],
414
+ "POST /optimize-memories": ["memories:write"],
415
+ # Knowledge endpoints
416
+ "GET /knowledge/content": ["knowledge:read"],
417
+ "GET /knowledge/content/*": ["knowledge:read"],
418
+ "GET /knowledge/config": ["knowledge:read"],
419
+ "POST /knowledge/content": ["knowledge:write"],
420
+ "PATCH /knowledge/content/*": ["knowledge:write"],
421
+ "POST /knowledge/search": ["knowledge:read"],
422
+ "DELETE /knowledge/content": ["knowledge:delete"],
423
+ "DELETE /knowledge/content/*": ["knowledge:delete"],
424
+ # Metrics endpoints
425
+ "GET /metrics": ["metrics:read"],
426
+ "POST /metrics/refresh": ["metrics:write"],
427
+ # Evaluation endpoints
428
+ "GET /eval-runs": ["evals:read"],
429
+ "GET /eval-runs/*": ["evals:read"],
430
+ "POST /eval-runs": ["evals:write"],
431
+ "PATCH /eval-runs/*": ["evals:write"],
432
+ "DELETE /eval-runs": ["evals:delete"],
433
+ # Trace endpoints
434
+ "GET /traces": ["traces:read"],
435
+ "GET /traces/*": ["traces:read"],
436
+ "GET /trace_session_stats": ["traces:read"],
437
+ }
438
+
439
+
440
+ def get_scope_value(scope: AgentOSScope) -> str:
441
+ """
442
+ Get the string value of a scope.
443
+
444
+ Args:
445
+ scope: The AgentOSScope enum value
446
+
447
+ Returns:
448
+ The string value of the scope
449
+
450
+ Example:
451
+ >>> get_scope_value(AgentOSScope.ADMIN)
452
+ 'admin'
453
+ """
454
+ return scope.value
455
+
456
+
457
+ def get_all_scopes() -> list[str]:
458
+ """
459
+ Get a list of all available scope strings.
460
+
461
+ Returns:
462
+ List of all scope string values
463
+
464
+ Example:
465
+ >>> scopes = get_all_scopes()
466
+ >>> 'admin' in scopes
467
+ True
468
+ """
469
+ return [scope.value for scope in AgentOSScope]