agno 2.2.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 (575) hide show
  1. agno/__init__.py +8 -0
  2. agno/agent/__init__.py +51 -0
  3. agno/agent/agent.py +10405 -0
  4. agno/api/__init__.py +0 -0
  5. agno/api/agent.py +28 -0
  6. agno/api/api.py +40 -0
  7. agno/api/evals.py +22 -0
  8. agno/api/os.py +17 -0
  9. agno/api/routes.py +13 -0
  10. agno/api/schemas/__init__.py +9 -0
  11. agno/api/schemas/agent.py +16 -0
  12. agno/api/schemas/evals.py +16 -0
  13. agno/api/schemas/os.py +14 -0
  14. agno/api/schemas/response.py +6 -0
  15. agno/api/schemas/team.py +16 -0
  16. agno/api/schemas/utils.py +21 -0
  17. agno/api/schemas/workflows.py +16 -0
  18. agno/api/settings.py +53 -0
  19. agno/api/team.py +30 -0
  20. agno/api/workflow.py +28 -0
  21. agno/cloud/aws/base.py +214 -0
  22. agno/cloud/aws/s3/__init__.py +2 -0
  23. agno/cloud/aws/s3/api_client.py +43 -0
  24. agno/cloud/aws/s3/bucket.py +195 -0
  25. agno/cloud/aws/s3/object.py +57 -0
  26. agno/culture/__init__.py +3 -0
  27. agno/culture/manager.py +956 -0
  28. agno/db/__init__.py +24 -0
  29. agno/db/async_postgres/__init__.py +3 -0
  30. agno/db/base.py +598 -0
  31. agno/db/dynamo/__init__.py +3 -0
  32. agno/db/dynamo/dynamo.py +2042 -0
  33. agno/db/dynamo/schemas.py +314 -0
  34. agno/db/dynamo/utils.py +743 -0
  35. agno/db/firestore/__init__.py +3 -0
  36. agno/db/firestore/firestore.py +1795 -0
  37. agno/db/firestore/schemas.py +140 -0
  38. agno/db/firestore/utils.py +376 -0
  39. agno/db/gcs_json/__init__.py +3 -0
  40. agno/db/gcs_json/gcs_json_db.py +1335 -0
  41. agno/db/gcs_json/utils.py +228 -0
  42. agno/db/in_memory/__init__.py +3 -0
  43. agno/db/in_memory/in_memory_db.py +1160 -0
  44. agno/db/in_memory/utils.py +230 -0
  45. agno/db/json/__init__.py +3 -0
  46. agno/db/json/json_db.py +1328 -0
  47. agno/db/json/utils.py +230 -0
  48. agno/db/migrations/__init__.py +0 -0
  49. agno/db/migrations/v1_to_v2.py +635 -0
  50. agno/db/mongo/__init__.py +17 -0
  51. agno/db/mongo/async_mongo.py +2026 -0
  52. agno/db/mongo/mongo.py +1982 -0
  53. agno/db/mongo/schemas.py +87 -0
  54. agno/db/mongo/utils.py +259 -0
  55. agno/db/mysql/__init__.py +3 -0
  56. agno/db/mysql/mysql.py +2308 -0
  57. agno/db/mysql/schemas.py +138 -0
  58. agno/db/mysql/utils.py +355 -0
  59. agno/db/postgres/__init__.py +4 -0
  60. agno/db/postgres/async_postgres.py +1927 -0
  61. agno/db/postgres/postgres.py +2260 -0
  62. agno/db/postgres/schemas.py +139 -0
  63. agno/db/postgres/utils.py +442 -0
  64. agno/db/redis/__init__.py +3 -0
  65. agno/db/redis/redis.py +1660 -0
  66. agno/db/redis/schemas.py +123 -0
  67. agno/db/redis/utils.py +346 -0
  68. agno/db/schemas/__init__.py +4 -0
  69. agno/db/schemas/culture.py +120 -0
  70. agno/db/schemas/evals.py +33 -0
  71. agno/db/schemas/knowledge.py +40 -0
  72. agno/db/schemas/memory.py +46 -0
  73. agno/db/schemas/metrics.py +0 -0
  74. agno/db/singlestore/__init__.py +3 -0
  75. agno/db/singlestore/schemas.py +130 -0
  76. agno/db/singlestore/singlestore.py +2272 -0
  77. agno/db/singlestore/utils.py +384 -0
  78. agno/db/sqlite/__init__.py +4 -0
  79. agno/db/sqlite/async_sqlite.py +2293 -0
  80. agno/db/sqlite/schemas.py +133 -0
  81. agno/db/sqlite/sqlite.py +2288 -0
  82. agno/db/sqlite/utils.py +431 -0
  83. agno/db/surrealdb/__init__.py +3 -0
  84. agno/db/surrealdb/metrics.py +292 -0
  85. agno/db/surrealdb/models.py +309 -0
  86. agno/db/surrealdb/queries.py +71 -0
  87. agno/db/surrealdb/surrealdb.py +1353 -0
  88. agno/db/surrealdb/utils.py +147 -0
  89. agno/db/utils.py +116 -0
  90. agno/debug.py +18 -0
  91. agno/eval/__init__.py +14 -0
  92. agno/eval/accuracy.py +834 -0
  93. agno/eval/performance.py +773 -0
  94. agno/eval/reliability.py +306 -0
  95. agno/eval/utils.py +119 -0
  96. agno/exceptions.py +161 -0
  97. agno/filters.py +354 -0
  98. agno/guardrails/__init__.py +6 -0
  99. agno/guardrails/base.py +19 -0
  100. agno/guardrails/openai.py +144 -0
  101. agno/guardrails/pii.py +94 -0
  102. agno/guardrails/prompt_injection.py +52 -0
  103. agno/integrations/__init__.py +0 -0
  104. agno/integrations/discord/__init__.py +3 -0
  105. agno/integrations/discord/client.py +203 -0
  106. agno/knowledge/__init__.py +5 -0
  107. agno/knowledge/chunking/__init__.py +0 -0
  108. agno/knowledge/chunking/agentic.py +79 -0
  109. agno/knowledge/chunking/document.py +91 -0
  110. agno/knowledge/chunking/fixed.py +57 -0
  111. agno/knowledge/chunking/markdown.py +151 -0
  112. agno/knowledge/chunking/recursive.py +63 -0
  113. agno/knowledge/chunking/row.py +39 -0
  114. agno/knowledge/chunking/semantic.py +86 -0
  115. agno/knowledge/chunking/strategy.py +165 -0
  116. agno/knowledge/content.py +74 -0
  117. agno/knowledge/document/__init__.py +5 -0
  118. agno/knowledge/document/base.py +58 -0
  119. agno/knowledge/embedder/__init__.py +5 -0
  120. agno/knowledge/embedder/aws_bedrock.py +343 -0
  121. agno/knowledge/embedder/azure_openai.py +210 -0
  122. agno/knowledge/embedder/base.py +23 -0
  123. agno/knowledge/embedder/cohere.py +323 -0
  124. agno/knowledge/embedder/fastembed.py +62 -0
  125. agno/knowledge/embedder/fireworks.py +13 -0
  126. agno/knowledge/embedder/google.py +258 -0
  127. agno/knowledge/embedder/huggingface.py +94 -0
  128. agno/knowledge/embedder/jina.py +182 -0
  129. agno/knowledge/embedder/langdb.py +22 -0
  130. agno/knowledge/embedder/mistral.py +206 -0
  131. agno/knowledge/embedder/nebius.py +13 -0
  132. agno/knowledge/embedder/ollama.py +154 -0
  133. agno/knowledge/embedder/openai.py +195 -0
  134. agno/knowledge/embedder/sentence_transformer.py +63 -0
  135. agno/knowledge/embedder/together.py +13 -0
  136. agno/knowledge/embedder/vllm.py +262 -0
  137. agno/knowledge/embedder/voyageai.py +165 -0
  138. agno/knowledge/knowledge.py +1988 -0
  139. agno/knowledge/reader/__init__.py +7 -0
  140. agno/knowledge/reader/arxiv_reader.py +81 -0
  141. agno/knowledge/reader/base.py +95 -0
  142. agno/knowledge/reader/csv_reader.py +166 -0
  143. agno/knowledge/reader/docx_reader.py +82 -0
  144. agno/knowledge/reader/field_labeled_csv_reader.py +292 -0
  145. agno/knowledge/reader/firecrawl_reader.py +201 -0
  146. agno/knowledge/reader/json_reader.py +87 -0
  147. agno/knowledge/reader/markdown_reader.py +137 -0
  148. agno/knowledge/reader/pdf_reader.py +431 -0
  149. agno/knowledge/reader/pptx_reader.py +101 -0
  150. agno/knowledge/reader/reader_factory.py +313 -0
  151. agno/knowledge/reader/s3_reader.py +89 -0
  152. agno/knowledge/reader/tavily_reader.py +194 -0
  153. agno/knowledge/reader/text_reader.py +115 -0
  154. agno/knowledge/reader/web_search_reader.py +372 -0
  155. agno/knowledge/reader/website_reader.py +455 -0
  156. agno/knowledge/reader/wikipedia_reader.py +59 -0
  157. agno/knowledge/reader/youtube_reader.py +78 -0
  158. agno/knowledge/remote_content/__init__.py +0 -0
  159. agno/knowledge/remote_content/remote_content.py +88 -0
  160. agno/knowledge/reranker/__init__.py +3 -0
  161. agno/knowledge/reranker/base.py +14 -0
  162. agno/knowledge/reranker/cohere.py +64 -0
  163. agno/knowledge/reranker/infinity.py +195 -0
  164. agno/knowledge/reranker/sentence_transformer.py +54 -0
  165. agno/knowledge/types.py +39 -0
  166. agno/knowledge/utils.py +189 -0
  167. agno/media.py +462 -0
  168. agno/memory/__init__.py +3 -0
  169. agno/memory/manager.py +1327 -0
  170. agno/models/__init__.py +0 -0
  171. agno/models/aimlapi/__init__.py +5 -0
  172. agno/models/aimlapi/aimlapi.py +45 -0
  173. agno/models/anthropic/__init__.py +5 -0
  174. agno/models/anthropic/claude.py +757 -0
  175. agno/models/aws/__init__.py +15 -0
  176. agno/models/aws/bedrock.py +701 -0
  177. agno/models/aws/claude.py +378 -0
  178. agno/models/azure/__init__.py +18 -0
  179. agno/models/azure/ai_foundry.py +485 -0
  180. agno/models/azure/openai_chat.py +131 -0
  181. agno/models/base.py +2175 -0
  182. agno/models/cerebras/__init__.py +12 -0
  183. agno/models/cerebras/cerebras.py +501 -0
  184. agno/models/cerebras/cerebras_openai.py +112 -0
  185. agno/models/cohere/__init__.py +5 -0
  186. agno/models/cohere/chat.py +389 -0
  187. agno/models/cometapi/__init__.py +5 -0
  188. agno/models/cometapi/cometapi.py +57 -0
  189. agno/models/dashscope/__init__.py +5 -0
  190. agno/models/dashscope/dashscope.py +91 -0
  191. agno/models/deepinfra/__init__.py +5 -0
  192. agno/models/deepinfra/deepinfra.py +28 -0
  193. agno/models/deepseek/__init__.py +5 -0
  194. agno/models/deepseek/deepseek.py +61 -0
  195. agno/models/defaults.py +1 -0
  196. agno/models/fireworks/__init__.py +5 -0
  197. agno/models/fireworks/fireworks.py +26 -0
  198. agno/models/google/__init__.py +5 -0
  199. agno/models/google/gemini.py +1085 -0
  200. agno/models/groq/__init__.py +5 -0
  201. agno/models/groq/groq.py +556 -0
  202. agno/models/huggingface/__init__.py +5 -0
  203. agno/models/huggingface/huggingface.py +491 -0
  204. agno/models/ibm/__init__.py +5 -0
  205. agno/models/ibm/watsonx.py +422 -0
  206. agno/models/internlm/__init__.py +3 -0
  207. agno/models/internlm/internlm.py +26 -0
  208. agno/models/langdb/__init__.py +1 -0
  209. agno/models/langdb/langdb.py +48 -0
  210. agno/models/litellm/__init__.py +14 -0
  211. agno/models/litellm/chat.py +468 -0
  212. agno/models/litellm/litellm_openai.py +25 -0
  213. agno/models/llama_cpp/__init__.py +5 -0
  214. agno/models/llama_cpp/llama_cpp.py +22 -0
  215. agno/models/lmstudio/__init__.py +5 -0
  216. agno/models/lmstudio/lmstudio.py +25 -0
  217. agno/models/message.py +434 -0
  218. agno/models/meta/__init__.py +12 -0
  219. agno/models/meta/llama.py +475 -0
  220. agno/models/meta/llama_openai.py +78 -0
  221. agno/models/metrics.py +120 -0
  222. agno/models/mistral/__init__.py +5 -0
  223. agno/models/mistral/mistral.py +432 -0
  224. agno/models/nebius/__init__.py +3 -0
  225. agno/models/nebius/nebius.py +54 -0
  226. agno/models/nexus/__init__.py +3 -0
  227. agno/models/nexus/nexus.py +22 -0
  228. agno/models/nvidia/__init__.py +5 -0
  229. agno/models/nvidia/nvidia.py +28 -0
  230. agno/models/ollama/__init__.py +5 -0
  231. agno/models/ollama/chat.py +441 -0
  232. agno/models/openai/__init__.py +9 -0
  233. agno/models/openai/chat.py +883 -0
  234. agno/models/openai/like.py +27 -0
  235. agno/models/openai/responses.py +1050 -0
  236. agno/models/openrouter/__init__.py +5 -0
  237. agno/models/openrouter/openrouter.py +66 -0
  238. agno/models/perplexity/__init__.py +5 -0
  239. agno/models/perplexity/perplexity.py +187 -0
  240. agno/models/portkey/__init__.py +3 -0
  241. agno/models/portkey/portkey.py +81 -0
  242. agno/models/requesty/__init__.py +5 -0
  243. agno/models/requesty/requesty.py +52 -0
  244. agno/models/response.py +199 -0
  245. agno/models/sambanova/__init__.py +5 -0
  246. agno/models/sambanova/sambanova.py +28 -0
  247. agno/models/siliconflow/__init__.py +5 -0
  248. agno/models/siliconflow/siliconflow.py +25 -0
  249. agno/models/together/__init__.py +5 -0
  250. agno/models/together/together.py +25 -0
  251. agno/models/utils.py +266 -0
  252. agno/models/vercel/__init__.py +3 -0
  253. agno/models/vercel/v0.py +26 -0
  254. agno/models/vertexai/__init__.py +0 -0
  255. agno/models/vertexai/claude.py +70 -0
  256. agno/models/vllm/__init__.py +3 -0
  257. agno/models/vllm/vllm.py +78 -0
  258. agno/models/xai/__init__.py +3 -0
  259. agno/models/xai/xai.py +113 -0
  260. agno/os/__init__.py +3 -0
  261. agno/os/app.py +876 -0
  262. agno/os/auth.py +57 -0
  263. agno/os/config.py +104 -0
  264. agno/os/interfaces/__init__.py +1 -0
  265. agno/os/interfaces/a2a/__init__.py +3 -0
  266. agno/os/interfaces/a2a/a2a.py +42 -0
  267. agno/os/interfaces/a2a/router.py +250 -0
  268. agno/os/interfaces/a2a/utils.py +924 -0
  269. agno/os/interfaces/agui/__init__.py +3 -0
  270. agno/os/interfaces/agui/agui.py +47 -0
  271. agno/os/interfaces/agui/router.py +144 -0
  272. agno/os/interfaces/agui/utils.py +534 -0
  273. agno/os/interfaces/base.py +25 -0
  274. agno/os/interfaces/slack/__init__.py +3 -0
  275. agno/os/interfaces/slack/router.py +148 -0
  276. agno/os/interfaces/slack/security.py +30 -0
  277. agno/os/interfaces/slack/slack.py +47 -0
  278. agno/os/interfaces/whatsapp/__init__.py +3 -0
  279. agno/os/interfaces/whatsapp/router.py +211 -0
  280. agno/os/interfaces/whatsapp/security.py +53 -0
  281. agno/os/interfaces/whatsapp/whatsapp.py +36 -0
  282. agno/os/mcp.py +292 -0
  283. agno/os/middleware/__init__.py +7 -0
  284. agno/os/middleware/jwt.py +233 -0
  285. agno/os/router.py +1763 -0
  286. agno/os/routers/__init__.py +3 -0
  287. agno/os/routers/evals/__init__.py +3 -0
  288. agno/os/routers/evals/evals.py +430 -0
  289. agno/os/routers/evals/schemas.py +142 -0
  290. agno/os/routers/evals/utils.py +162 -0
  291. agno/os/routers/health.py +31 -0
  292. agno/os/routers/home.py +52 -0
  293. agno/os/routers/knowledge/__init__.py +3 -0
  294. agno/os/routers/knowledge/knowledge.py +997 -0
  295. agno/os/routers/knowledge/schemas.py +178 -0
  296. agno/os/routers/memory/__init__.py +3 -0
  297. agno/os/routers/memory/memory.py +515 -0
  298. agno/os/routers/memory/schemas.py +62 -0
  299. agno/os/routers/metrics/__init__.py +3 -0
  300. agno/os/routers/metrics/metrics.py +190 -0
  301. agno/os/routers/metrics/schemas.py +47 -0
  302. agno/os/routers/session/__init__.py +3 -0
  303. agno/os/routers/session/session.py +997 -0
  304. agno/os/schema.py +1055 -0
  305. agno/os/settings.py +43 -0
  306. agno/os/utils.py +630 -0
  307. agno/py.typed +0 -0
  308. agno/reasoning/__init__.py +0 -0
  309. agno/reasoning/anthropic.py +80 -0
  310. agno/reasoning/azure_ai_foundry.py +67 -0
  311. agno/reasoning/deepseek.py +63 -0
  312. agno/reasoning/default.py +97 -0
  313. agno/reasoning/gemini.py +73 -0
  314. agno/reasoning/groq.py +71 -0
  315. agno/reasoning/helpers.py +63 -0
  316. agno/reasoning/ollama.py +67 -0
  317. agno/reasoning/openai.py +86 -0
  318. agno/reasoning/step.py +31 -0
  319. agno/reasoning/vertexai.py +76 -0
  320. agno/run/__init__.py +6 -0
  321. agno/run/agent.py +787 -0
  322. agno/run/base.py +229 -0
  323. agno/run/cancel.py +81 -0
  324. agno/run/messages.py +32 -0
  325. agno/run/team.py +753 -0
  326. agno/run/workflow.py +708 -0
  327. agno/session/__init__.py +10 -0
  328. agno/session/agent.py +295 -0
  329. agno/session/summary.py +265 -0
  330. agno/session/team.py +392 -0
  331. agno/session/workflow.py +205 -0
  332. agno/team/__init__.py +37 -0
  333. agno/team/team.py +8793 -0
  334. agno/tools/__init__.py +10 -0
  335. agno/tools/agentql.py +120 -0
  336. agno/tools/airflow.py +69 -0
  337. agno/tools/api.py +122 -0
  338. agno/tools/apify.py +314 -0
  339. agno/tools/arxiv.py +127 -0
  340. agno/tools/aws_lambda.py +53 -0
  341. agno/tools/aws_ses.py +66 -0
  342. agno/tools/baidusearch.py +89 -0
  343. agno/tools/bitbucket.py +292 -0
  344. agno/tools/brandfetch.py +213 -0
  345. agno/tools/bravesearch.py +106 -0
  346. agno/tools/brightdata.py +367 -0
  347. agno/tools/browserbase.py +209 -0
  348. agno/tools/calcom.py +255 -0
  349. agno/tools/calculator.py +151 -0
  350. agno/tools/cartesia.py +187 -0
  351. agno/tools/clickup.py +244 -0
  352. agno/tools/confluence.py +240 -0
  353. agno/tools/crawl4ai.py +158 -0
  354. agno/tools/csv_toolkit.py +185 -0
  355. agno/tools/dalle.py +110 -0
  356. agno/tools/daytona.py +475 -0
  357. agno/tools/decorator.py +262 -0
  358. agno/tools/desi_vocal.py +108 -0
  359. agno/tools/discord.py +161 -0
  360. agno/tools/docker.py +716 -0
  361. agno/tools/duckdb.py +379 -0
  362. agno/tools/duckduckgo.py +91 -0
  363. agno/tools/e2b.py +703 -0
  364. agno/tools/eleven_labs.py +196 -0
  365. agno/tools/email.py +67 -0
  366. agno/tools/evm.py +129 -0
  367. agno/tools/exa.py +396 -0
  368. agno/tools/fal.py +127 -0
  369. agno/tools/file.py +240 -0
  370. agno/tools/file_generation.py +350 -0
  371. agno/tools/financial_datasets.py +288 -0
  372. agno/tools/firecrawl.py +143 -0
  373. agno/tools/function.py +1187 -0
  374. agno/tools/giphy.py +93 -0
  375. agno/tools/github.py +1760 -0
  376. agno/tools/gmail.py +922 -0
  377. agno/tools/google_bigquery.py +117 -0
  378. agno/tools/google_drive.py +270 -0
  379. agno/tools/google_maps.py +253 -0
  380. agno/tools/googlecalendar.py +674 -0
  381. agno/tools/googlesearch.py +98 -0
  382. agno/tools/googlesheets.py +377 -0
  383. agno/tools/hackernews.py +77 -0
  384. agno/tools/jina.py +101 -0
  385. agno/tools/jira.py +170 -0
  386. agno/tools/knowledge.py +218 -0
  387. agno/tools/linear.py +426 -0
  388. agno/tools/linkup.py +58 -0
  389. agno/tools/local_file_system.py +90 -0
  390. agno/tools/lumalab.py +183 -0
  391. agno/tools/mcp/__init__.py +10 -0
  392. agno/tools/mcp/mcp.py +331 -0
  393. agno/tools/mcp/multi_mcp.py +347 -0
  394. agno/tools/mcp/params.py +24 -0
  395. agno/tools/mcp_toolbox.py +284 -0
  396. agno/tools/mem0.py +193 -0
  397. agno/tools/memori.py +339 -0
  398. agno/tools/memory.py +419 -0
  399. agno/tools/mlx_transcribe.py +139 -0
  400. agno/tools/models/__init__.py +0 -0
  401. agno/tools/models/azure_openai.py +190 -0
  402. agno/tools/models/gemini.py +203 -0
  403. agno/tools/models/groq.py +158 -0
  404. agno/tools/models/morph.py +186 -0
  405. agno/tools/models/nebius.py +124 -0
  406. agno/tools/models_labs.py +195 -0
  407. agno/tools/moviepy_video.py +349 -0
  408. agno/tools/neo4j.py +134 -0
  409. agno/tools/newspaper.py +46 -0
  410. agno/tools/newspaper4k.py +93 -0
  411. agno/tools/notion.py +204 -0
  412. agno/tools/openai.py +202 -0
  413. agno/tools/openbb.py +160 -0
  414. agno/tools/opencv.py +321 -0
  415. agno/tools/openweather.py +233 -0
  416. agno/tools/oxylabs.py +385 -0
  417. agno/tools/pandas.py +102 -0
  418. agno/tools/parallel.py +314 -0
  419. agno/tools/postgres.py +257 -0
  420. agno/tools/pubmed.py +188 -0
  421. agno/tools/python.py +205 -0
  422. agno/tools/reasoning.py +283 -0
  423. agno/tools/reddit.py +467 -0
  424. agno/tools/replicate.py +117 -0
  425. agno/tools/resend.py +62 -0
  426. agno/tools/scrapegraph.py +222 -0
  427. agno/tools/searxng.py +152 -0
  428. agno/tools/serpapi.py +116 -0
  429. agno/tools/serper.py +255 -0
  430. agno/tools/shell.py +53 -0
  431. agno/tools/slack.py +136 -0
  432. agno/tools/sleep.py +20 -0
  433. agno/tools/spider.py +116 -0
  434. agno/tools/sql.py +154 -0
  435. agno/tools/streamlit/__init__.py +0 -0
  436. agno/tools/streamlit/components.py +113 -0
  437. agno/tools/tavily.py +254 -0
  438. agno/tools/telegram.py +48 -0
  439. agno/tools/todoist.py +218 -0
  440. agno/tools/tool_registry.py +1 -0
  441. agno/tools/toolkit.py +146 -0
  442. agno/tools/trafilatura.py +388 -0
  443. agno/tools/trello.py +274 -0
  444. agno/tools/twilio.py +186 -0
  445. agno/tools/user_control_flow.py +78 -0
  446. agno/tools/valyu.py +228 -0
  447. agno/tools/visualization.py +467 -0
  448. agno/tools/webbrowser.py +28 -0
  449. agno/tools/webex.py +76 -0
  450. agno/tools/website.py +54 -0
  451. agno/tools/webtools.py +45 -0
  452. agno/tools/whatsapp.py +286 -0
  453. agno/tools/wikipedia.py +63 -0
  454. agno/tools/workflow.py +278 -0
  455. agno/tools/x.py +335 -0
  456. agno/tools/yfinance.py +257 -0
  457. agno/tools/youtube.py +184 -0
  458. agno/tools/zendesk.py +82 -0
  459. agno/tools/zep.py +454 -0
  460. agno/tools/zoom.py +382 -0
  461. agno/utils/__init__.py +0 -0
  462. agno/utils/agent.py +820 -0
  463. agno/utils/audio.py +49 -0
  464. agno/utils/certs.py +27 -0
  465. agno/utils/code_execution.py +11 -0
  466. agno/utils/common.py +132 -0
  467. agno/utils/dttm.py +13 -0
  468. agno/utils/enum.py +22 -0
  469. agno/utils/env.py +11 -0
  470. agno/utils/events.py +696 -0
  471. agno/utils/format_str.py +16 -0
  472. agno/utils/functions.py +166 -0
  473. agno/utils/gemini.py +426 -0
  474. agno/utils/hooks.py +57 -0
  475. agno/utils/http.py +74 -0
  476. agno/utils/json_schema.py +234 -0
  477. agno/utils/knowledge.py +36 -0
  478. agno/utils/location.py +19 -0
  479. agno/utils/log.py +255 -0
  480. agno/utils/mcp.py +214 -0
  481. agno/utils/media.py +352 -0
  482. agno/utils/merge_dict.py +41 -0
  483. agno/utils/message.py +118 -0
  484. agno/utils/models/__init__.py +0 -0
  485. agno/utils/models/ai_foundry.py +43 -0
  486. agno/utils/models/claude.py +358 -0
  487. agno/utils/models/cohere.py +87 -0
  488. agno/utils/models/llama.py +78 -0
  489. agno/utils/models/mistral.py +98 -0
  490. agno/utils/models/openai_responses.py +140 -0
  491. agno/utils/models/schema_utils.py +153 -0
  492. agno/utils/models/watsonx.py +41 -0
  493. agno/utils/openai.py +257 -0
  494. agno/utils/pickle.py +32 -0
  495. agno/utils/pprint.py +178 -0
  496. agno/utils/print_response/__init__.py +0 -0
  497. agno/utils/print_response/agent.py +842 -0
  498. agno/utils/print_response/team.py +1724 -0
  499. agno/utils/print_response/workflow.py +1668 -0
  500. agno/utils/prompts.py +111 -0
  501. agno/utils/reasoning.py +108 -0
  502. agno/utils/response.py +163 -0
  503. agno/utils/response_iterator.py +17 -0
  504. agno/utils/safe_formatter.py +24 -0
  505. agno/utils/serialize.py +32 -0
  506. agno/utils/shell.py +22 -0
  507. agno/utils/streamlit.py +487 -0
  508. agno/utils/string.py +231 -0
  509. agno/utils/team.py +139 -0
  510. agno/utils/timer.py +41 -0
  511. agno/utils/tools.py +102 -0
  512. agno/utils/web.py +23 -0
  513. agno/utils/whatsapp.py +305 -0
  514. agno/utils/yaml_io.py +25 -0
  515. agno/vectordb/__init__.py +3 -0
  516. agno/vectordb/base.py +127 -0
  517. agno/vectordb/cassandra/__init__.py +5 -0
  518. agno/vectordb/cassandra/cassandra.py +501 -0
  519. agno/vectordb/cassandra/extra_param_mixin.py +11 -0
  520. agno/vectordb/cassandra/index.py +13 -0
  521. agno/vectordb/chroma/__init__.py +5 -0
  522. agno/vectordb/chroma/chromadb.py +929 -0
  523. agno/vectordb/clickhouse/__init__.py +9 -0
  524. agno/vectordb/clickhouse/clickhousedb.py +835 -0
  525. agno/vectordb/clickhouse/index.py +9 -0
  526. agno/vectordb/couchbase/__init__.py +3 -0
  527. agno/vectordb/couchbase/couchbase.py +1442 -0
  528. agno/vectordb/distance.py +7 -0
  529. agno/vectordb/lancedb/__init__.py +6 -0
  530. agno/vectordb/lancedb/lance_db.py +995 -0
  531. agno/vectordb/langchaindb/__init__.py +5 -0
  532. agno/vectordb/langchaindb/langchaindb.py +163 -0
  533. agno/vectordb/lightrag/__init__.py +5 -0
  534. agno/vectordb/lightrag/lightrag.py +388 -0
  535. agno/vectordb/llamaindex/__init__.py +3 -0
  536. agno/vectordb/llamaindex/llamaindexdb.py +166 -0
  537. agno/vectordb/milvus/__init__.py +4 -0
  538. agno/vectordb/milvus/milvus.py +1182 -0
  539. agno/vectordb/mongodb/__init__.py +9 -0
  540. agno/vectordb/mongodb/mongodb.py +1417 -0
  541. agno/vectordb/pgvector/__init__.py +12 -0
  542. agno/vectordb/pgvector/index.py +23 -0
  543. agno/vectordb/pgvector/pgvector.py +1462 -0
  544. agno/vectordb/pineconedb/__init__.py +5 -0
  545. agno/vectordb/pineconedb/pineconedb.py +747 -0
  546. agno/vectordb/qdrant/__init__.py +5 -0
  547. agno/vectordb/qdrant/qdrant.py +1134 -0
  548. agno/vectordb/redis/__init__.py +9 -0
  549. agno/vectordb/redis/redisdb.py +694 -0
  550. agno/vectordb/search.py +7 -0
  551. agno/vectordb/singlestore/__init__.py +10 -0
  552. agno/vectordb/singlestore/index.py +41 -0
  553. agno/vectordb/singlestore/singlestore.py +763 -0
  554. agno/vectordb/surrealdb/__init__.py +3 -0
  555. agno/vectordb/surrealdb/surrealdb.py +699 -0
  556. agno/vectordb/upstashdb/__init__.py +5 -0
  557. agno/vectordb/upstashdb/upstashdb.py +718 -0
  558. agno/vectordb/weaviate/__init__.py +8 -0
  559. agno/vectordb/weaviate/index.py +15 -0
  560. agno/vectordb/weaviate/weaviate.py +1005 -0
  561. agno/workflow/__init__.py +23 -0
  562. agno/workflow/agent.py +299 -0
  563. agno/workflow/condition.py +738 -0
  564. agno/workflow/loop.py +735 -0
  565. agno/workflow/parallel.py +824 -0
  566. agno/workflow/router.py +702 -0
  567. agno/workflow/step.py +1432 -0
  568. agno/workflow/steps.py +592 -0
  569. agno/workflow/types.py +520 -0
  570. agno/workflow/workflow.py +4321 -0
  571. agno-2.2.13.dist-info/METADATA +614 -0
  572. agno-2.2.13.dist-info/RECORD +575 -0
  573. agno-2.2.13.dist-info/WHEEL +5 -0
  574. agno-2.2.13.dist-info/licenses/LICENSE +201 -0
  575. agno-2.2.13.dist-info/top_level.txt +1 -0
@@ -0,0 +1,674 @@
1
+ import datetime
2
+ import json
3
+ import uuid
4
+ from functools import wraps
5
+ from os import getenv
6
+ from pathlib import Path
7
+ from typing import Any, Dict, List, Optional, cast
8
+
9
+ from agno.tools import Toolkit
10
+ from agno.utils.log import log_debug, log_error, log_info
11
+
12
+ try:
13
+ from google.auth.transport.requests import Request
14
+ from google.oauth2.credentials import Credentials
15
+ from google_auth_oauthlib.flow import InstalledAppFlow
16
+ from googleapiclient.discovery import Resource, build
17
+ from googleapiclient.errors import HttpError
18
+
19
+ except ImportError:
20
+ raise ImportError(
21
+ "Google client libraries not found, Please install using `pip install google-api-python-client google-auth-httplib2 google-auth-oauthlib`"
22
+ )
23
+
24
+ SCOPES = ["https://www.googleapis.com/auth/calendar"]
25
+
26
+
27
+ def authenticate(func):
28
+ """Decorator to ensure authentication before executing the method."""
29
+
30
+ @wraps(func)
31
+ def wrapper(self, *args, **kwargs):
32
+ try:
33
+ if not self.creds or not self.creds.valid:
34
+ self._auth()
35
+ if not self.service:
36
+ self.service = build("calendar", "v3", credentials=self.creds)
37
+ except Exception as e:
38
+ log_error(f"An error occurred: {e}")
39
+ return func(self, *args, **kwargs)
40
+
41
+ return wrapper
42
+
43
+
44
+ class GoogleCalendarTools(Toolkit):
45
+ # Default scopes for Google Calendar API access
46
+ DEFAULT_SCOPES = {
47
+ "read": "https://www.googleapis.com/auth/calendar.readonly",
48
+ "write": "https://www.googleapis.com/auth/calendar",
49
+ }
50
+
51
+ service: Optional[Resource]
52
+
53
+ def __init__(
54
+ self,
55
+ scopes: Optional[List[str]] = None,
56
+ credentials_path: Optional[str] = None,
57
+ token_path: Optional[str] = "token.json",
58
+ access_token: Optional[str] = None,
59
+ calendar_id: str = "primary",
60
+ oauth_port: int = 8080,
61
+ allow_update: bool = False,
62
+ **kwargs,
63
+ ):
64
+ self.creds: Optional[Credentials] = None
65
+ self.service: Optional[Resource] = None
66
+ self.calendar_id: str = calendar_id
67
+ self.oauth_port: int = oauth_port
68
+ self.access_token = access_token
69
+ self.credentials_path = credentials_path
70
+ self.token_path = token_path
71
+ self.allow_update = allow_update
72
+ self.scopes = scopes or []
73
+
74
+ super().__init__(
75
+ name="google_calendar_tools",
76
+ tools=[
77
+ self.list_events,
78
+ self.create_event,
79
+ self.update_event,
80
+ self.delete_event,
81
+ self.fetch_all_events,
82
+ self.find_available_slots,
83
+ self.list_calendars,
84
+ ],
85
+ **kwargs,
86
+ )
87
+ if not self.scopes:
88
+ # Add read permission by default
89
+ self.scopes.append(self.DEFAULT_SCOPES["read"])
90
+ # Add write permission if allow_update is True
91
+ if self.allow_update:
92
+ self.scopes.append(self.DEFAULT_SCOPES["write"])
93
+
94
+ # Validate that required scopes are present for requested operations
95
+ if self.allow_update and self.DEFAULT_SCOPES["write"] not in self.scopes:
96
+ raise ValueError(f"The scope {self.DEFAULT_SCOPES['write']} is required for write operations")
97
+ if self.DEFAULT_SCOPES["read"] not in self.scopes and self.DEFAULT_SCOPES["write"] not in self.scopes:
98
+ raise ValueError(
99
+ f"Either {self.DEFAULT_SCOPES['read']} or {self.DEFAULT_SCOPES['write']} is required for read operations"
100
+ )
101
+
102
+ def _auth(self) -> None:
103
+ """
104
+ Authenticate with Google Calendar API
105
+ """
106
+ if self.creds and self.creds.valid:
107
+ return
108
+
109
+ token_file = Path(self.token_path or "token.json")
110
+ creds_file = Path(self.credentials_path or "credentials.json")
111
+
112
+ if token_file.exists():
113
+ self.creds = Credentials.from_authorized_user_file(str(token_file), self.DEFAULT_SCOPES)
114
+
115
+ if not self.creds or not self.creds.valid:
116
+ if self.creds and self.creds.expired and self.creds.refresh_token:
117
+ self.creds.refresh(Request())
118
+ else:
119
+ client_config = {
120
+ "installed": {
121
+ "client_id": getenv("GOOGLE_CLIENT_ID"),
122
+ "client_secret": getenv("GOOGLE_CLIENT_SECRET"),
123
+ "project_id": getenv("GOOGLE_PROJECT_ID"),
124
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
125
+ "token_uri": "https://oauth2.googleapis.com/token",
126
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
127
+ "redirect_uris": [getenv("GOOGLE_REDIRECT_URI", "http://localhost")],
128
+ }
129
+ }
130
+ # File based authentication
131
+ if creds_file.exists():
132
+ flow = InstalledAppFlow.from_client_secrets_file(str(creds_file), self.scopes)
133
+ else:
134
+ flow = InstalledAppFlow.from_client_config(client_config, self.scopes)
135
+ # Opens up a browser window for OAuth authentication
136
+ self.creds = flow.run_local_server(port=self.oauth_port)
137
+
138
+ if self.creds:
139
+ token_file.write_text(self.creds.to_json())
140
+ log_debug("Successfully authenticated with Google Calendar API.")
141
+ log_info(f"Token file path: {token_file}")
142
+
143
+ @authenticate
144
+ def list_events(self, limit: int = 10, start_date: Optional[str] = None) -> str:
145
+ """
146
+ List upcoming events from the user's Google Calendar.
147
+
148
+ Args:
149
+ limit (Optional[int]): Number of events to return, default value is 10
150
+ start_date (Optional[str]): The start date to return events from in ISO format (YYYY-MM-DDTHH:MM:SS)
151
+
152
+ Returns:
153
+ str: JSON string containing the Google Calendar events or error message
154
+ """
155
+ if start_date is None:
156
+ start_date = datetime.datetime.now(datetime.timezone.utc).isoformat()
157
+ log_debug(f"No start date provided, using current datetime: {start_date}")
158
+ elif isinstance(start_date, str):
159
+ try:
160
+ start_date = datetime.datetime.fromisoformat(start_date).strftime("%Y-%m-%dT%H:%M:%S.%fZ")
161
+ except ValueError:
162
+ return json.dumps(
163
+ {"error": f"Invalid date format: {start_date}. Use ISO format (YYYY-MM-DDTHH:MM:SS)."}
164
+ )
165
+
166
+ try:
167
+ service = cast(Resource, self.service)
168
+
169
+ events_result = (
170
+ service.events()
171
+ .list(
172
+ calendarId=self.calendar_id,
173
+ timeMin=start_date,
174
+ maxResults=limit,
175
+ singleEvents=True,
176
+ orderBy="startTime",
177
+ )
178
+ .execute()
179
+ )
180
+ events = events_result.get("items", [])
181
+ if not events:
182
+ return json.dumps({"message": "No upcoming events found."})
183
+ return json.dumps(events)
184
+ except HttpError as error:
185
+ log_error(f"An error occurred: {error}")
186
+ return json.dumps({"error": f"An error occurred: {error}"})
187
+
188
+ @authenticate
189
+ def create_event(
190
+ self,
191
+ start_date: str,
192
+ end_date: str,
193
+ title: Optional[str] = None,
194
+ description: Optional[str] = None,
195
+ location: Optional[str] = None,
196
+ timezone: Optional[str] = "UTC",
197
+ attendees: Optional[List[str]] = None,
198
+ add_google_meet_link: Optional[bool] = False,
199
+ notify_attendees: Optional[bool] = False,
200
+ ) -> str:
201
+ """
202
+ Create a new event in the Google Calendar.
203
+
204
+ Args:
205
+ start_date (str): Start date and time of the event in ISO format (YYYY-MM-DDTHH:MM:SS)
206
+ end_date (str): End date and time of the event in ISO format (YYYY-MM-DDTHH:MM:SS)
207
+ title (Optional[str]): Title/summary of the event
208
+ description (Optional[str]): Detailed description of the event
209
+ location (Optional[str]): Location of the event
210
+ timezone (Optional[str]): Timezone for the event (default: UTC)
211
+ attendees (Optional[List[str]]): List of email addresses of the attendees
212
+ add_google_meet_link (Optional[bool]): Whether to add a Google Meet video link to the event
213
+ notify_attendees (Optional[bool]): Whether to send email notifications to attendees (default: False)
214
+
215
+ Returns:
216
+ str: JSON string containing the created Google Calendar event or error message
217
+ """
218
+ try:
219
+ # Format attendees if provided
220
+ attendees_list = [{"email": attendee} for attendee in attendees] if attendees else []
221
+
222
+ # Convert ISO string to datetime and format as required
223
+ try:
224
+ start_time = datetime.datetime.fromisoformat(start_date).strftime("%Y-%m-%dT%H:%M:%S")
225
+ end_time = datetime.datetime.fromisoformat(end_date).strftime("%Y-%m-%dT%H:%M:%S")
226
+ except ValueError:
227
+ return json.dumps({"error": "Invalid datetime format. Use ISO format (YYYY-MM-DDTHH:MM:SS)."})
228
+
229
+ # Create event dictionary
230
+ event: Dict[str, Any] = {
231
+ "summary": title,
232
+ "location": location,
233
+ "description": description,
234
+ "start": {"dateTime": start_time, "timeZone": timezone},
235
+ "end": {"dateTime": end_time, "timeZone": timezone},
236
+ "attendees": attendees_list,
237
+ }
238
+
239
+ # Add Google Meet link if requested
240
+ if add_google_meet_link:
241
+ event["conferenceData"] = {
242
+ "createRequest": {"requestId": str(uuid.uuid4()), "conferenceSolutionKey": {"type": "hangoutsMeet"}}
243
+ }
244
+
245
+ # Remove None values
246
+ event = {k: v for k, v in event.items() if v is not None}
247
+
248
+ # Determine sendUpdates value based on notify_attendees parameter
249
+ send_updates = "all" if notify_attendees and attendees else "none"
250
+
251
+ service = cast(Resource, self.service)
252
+
253
+ event_result = (
254
+ service.events()
255
+ .insert(
256
+ calendarId=self.calendar_id,
257
+ body=event,
258
+ conferenceDataVersion=1 if add_google_meet_link else 0,
259
+ sendUpdates=send_updates,
260
+ )
261
+ .execute()
262
+ )
263
+ log_debug(f"Event created successfully in calendar {self.calendar_id}. Event ID: {event_result['id']}")
264
+ return json.dumps(event_result)
265
+ except HttpError as error:
266
+ log_error(f"An error occurred: {error}")
267
+ return json.dumps({"error": f"An error occurred: {error}"})
268
+
269
+ @authenticate
270
+ def update_event(
271
+ self,
272
+ event_id: str,
273
+ title: Optional[str] = None,
274
+ description: Optional[str] = None,
275
+ location: Optional[str] = None,
276
+ start_date: Optional[str] = None,
277
+ end_date: Optional[str] = None,
278
+ timezone: Optional[str] = None,
279
+ attendees: Optional[List[str]] = None,
280
+ notify_attendees: Optional[bool] = False,
281
+ ) -> str:
282
+ """
283
+ Update an existing event in the Google Calendar.
284
+
285
+ Args:
286
+ event_id (str): ID of the event to update
287
+ title (Optional[str]): New title/summary of the event
288
+ description (Optional[str]): New description of the event
289
+ location (Optional[str]): New location of the event
290
+ start_date (Optional[str]): New start date and time in ISO format (YYYY-MM-DDTHH:MM:SS)
291
+ end_date (Optional[str]): New end date and time in ISO format (YYYY-MM-DDTHH:MM:SS)
292
+ timezone (Optional[str]): New timezone for the event
293
+ attendees (Optional[List[str]]): Updated list of attendee email addresses
294
+ notify_attendees (Optional[bool]): Whether to send email notifications to attendees (default: False)
295
+
296
+ Returns:
297
+ str: JSON string containing the updated Google Calendar event or error message
298
+ """
299
+ try:
300
+ service = cast(Resource, self.service)
301
+
302
+ # First get the existing event to preserve its structure
303
+ event = service.events().get(calendarId=self.calendar_id, eventId=event_id).execute()
304
+
305
+ # Update only the fields that are provided
306
+ if title is not None:
307
+ event["summary"] = title
308
+ if description is not None:
309
+ event["description"] = description
310
+ if location is not None:
311
+ event["location"] = location
312
+ if attendees is not None:
313
+ event["attendees"] = [{"email": attendee} for attendee in attendees]
314
+
315
+ # Handle datetime updates
316
+ if start_date:
317
+ try:
318
+ start_time = datetime.datetime.fromisoformat(start_date).strftime("%Y-%m-%dT%H:%M:%S")
319
+ event["start"]["dateTime"] = start_time
320
+ if timezone:
321
+ event["start"]["timeZone"] = timezone
322
+ except ValueError:
323
+ return json.dumps({"error": f"Invalid start datetime format: {start_date}. Use ISO format."})
324
+
325
+ if end_date:
326
+ try:
327
+ end_time = datetime.datetime.fromisoformat(end_date).strftime("%Y-%m-%dT%H:%M:%S")
328
+ event["end"]["dateTime"] = end_time
329
+ if timezone:
330
+ event["end"]["timeZone"] = timezone
331
+ except ValueError:
332
+ return json.dumps({"error": f"Invalid end datetime format: {end_date}. Use ISO format."})
333
+
334
+ # Determine sendUpdates value based on notify_attendees parameter
335
+ send_updates = "all" if notify_attendees and attendees else "none"
336
+
337
+ # Update the event
338
+
339
+ updated_event = (
340
+ service.events()
341
+ .update(calendarId=self.calendar_id, eventId=event_id, body=event, sendUpdates=send_updates)
342
+ .execute()
343
+ )
344
+
345
+ log_debug(f"Event {event_id} updated successfully.")
346
+ return json.dumps(updated_event)
347
+ except HttpError as error:
348
+ log_error(f"An error occurred while updating event: {error}")
349
+ return json.dumps({"error": f"An error occurred: {error}"})
350
+
351
+ @authenticate
352
+ def delete_event(self, event_id: str, notify_attendees: Optional[bool] = True) -> str:
353
+ """
354
+ Delete an event from the Google Calendar.
355
+
356
+ Args:
357
+ event_id (str): ID of the event to delete
358
+ notify_attendees (Optional[bool]): Whether to send email notifications to attendees (default: False)
359
+
360
+ Returns:
361
+ str: JSON string containing success message or error message
362
+ """
363
+ try:
364
+ # Determine sendUpdates value based on notify_attendees parameter
365
+ send_updates = "all" if notify_attendees else "none"
366
+
367
+ service = cast(Resource, self.service)
368
+
369
+ service.events().delete(calendarId=self.calendar_id, eventId=event_id, sendUpdates=send_updates).execute()
370
+
371
+ log_debug(f"Event {event_id} deleted successfully.")
372
+ return json.dumps({"success": True, "message": f"Event {event_id} deleted successfully."})
373
+ except HttpError as error:
374
+ log_error(f"An error occurred while deleting event: {error}")
375
+ return json.dumps({"error": f"An error occurred: {error}"})
376
+
377
+ @authenticate
378
+ def fetch_all_events(
379
+ self,
380
+ max_results: int = 10,
381
+ start_date: Optional[str] = None,
382
+ end_date: Optional[str] = None,
383
+ ) -> str:
384
+ """
385
+ Fetch all Google Calendar events in a given date range.
386
+
387
+ Args:
388
+ start_date (Optional[str]): The minimum date to include events from in ISO format (YYYY-MM-DDTHH:MM:SS).
389
+ end_date (Optional[str]): The maximum date to include events up to in ISO format (YYYY-MM-DDTHH:MM:SS).
390
+
391
+ Returns:
392
+ str: JSON string containing all Google Calendar events or error message
393
+ """
394
+ try:
395
+ service = cast(Resource, self.service)
396
+
397
+ params = {
398
+ "calendarId": self.calendar_id,
399
+ "maxResults": min(max_results, 100),
400
+ "singleEvents": True,
401
+ "orderBy": "startTime",
402
+ }
403
+
404
+ # Set time parameters if provided
405
+ if start_date:
406
+ # Accept both string and already formatted ISO strings
407
+ if isinstance(start_date, str):
408
+ try:
409
+ # Try to parse and reformat to ensure proper timezone format
410
+ dt = datetime.datetime.fromisoformat(start_date)
411
+ if dt.tzinfo is None:
412
+ dt = dt.replace(tzinfo=datetime.timezone.utc)
413
+ params["timeMin"] = dt.isoformat()
414
+ except ValueError:
415
+ # If it's already a valid ISO string, use it directly
416
+ params["timeMin"] = start_date
417
+ else:
418
+ params["timeMin"] = start_date
419
+
420
+ if end_date:
421
+ # Accept both string and already formatted ISO strings
422
+ if isinstance(end_date, str):
423
+ try:
424
+ # Try to parse and reformat to ensure proper timezone format
425
+ dt = datetime.datetime.fromisoformat(end_date)
426
+ if dt.tzinfo is None:
427
+ dt = dt.replace(tzinfo=datetime.timezone.utc)
428
+ params["timeMax"] = dt.isoformat()
429
+ except ValueError:
430
+ # If it's already a valid ISO string, use it directly
431
+ params["timeMax"] = end_date
432
+ else:
433
+ params["timeMax"] = end_date
434
+
435
+ # Handle pagination
436
+ all_events = []
437
+ page_token = None
438
+
439
+ while True:
440
+ if page_token:
441
+ params["pageToken"] = page_token
442
+
443
+ events_result = service.events().list(**params).execute()
444
+ all_events.extend(events_result.get("items", []))
445
+
446
+ page_token = events_result.get("nextPageToken")
447
+ if not page_token:
448
+ break
449
+
450
+ log_debug(f"Fetched {len(all_events)} events from calendar: {self.calendar_id}")
451
+
452
+ if not all_events:
453
+ return json.dumps({"message": "No events found."})
454
+ return json.dumps(all_events)
455
+ except HttpError as error:
456
+ log_error(f"An error occurred while fetching events: {error}")
457
+ return json.dumps({"error": f"An error occurred: {error}"})
458
+
459
+ @authenticate
460
+ def find_available_slots(
461
+ self,
462
+ start_date: str,
463
+ end_date: str,
464
+ duration_minutes: int = 30,
465
+ ) -> str:
466
+ """
467
+ Find available time slots within a date range.
468
+
469
+ This method fetches your actual calendar events to determine busy periods,
470
+ then finds available slots within standard working hours (9 AM - 5 PM).
471
+
472
+ Args:
473
+ start_date (str): Start date to search from in ISO format (YYYY-MM-DD)
474
+ end_date (str): End date to search to in ISO format (YYYY-MM-DD)
475
+ duration_minutes (int): Length of the desired slot in minutes (default: 30 minutes)
476
+
477
+ Returns:
478
+ str: JSON string containing available Google Calendar time slots or error message
479
+ """
480
+ try:
481
+ start_dt = datetime.datetime.fromisoformat(start_date)
482
+ end_dt = datetime.datetime.fromisoformat(end_date)
483
+ # Ensure dates are timezone-aware (use UTC if no timezone specified)
484
+ if start_dt.tzinfo is None:
485
+ start_dt = start_dt.replace(tzinfo=datetime.timezone.utc)
486
+ if end_dt.tzinfo is None:
487
+ end_dt = end_dt.replace(tzinfo=datetime.timezone.utc)
488
+
489
+ # Get working hours from user settings
490
+ working_hours_json = self._get_working_hours()
491
+ working_hours_data = json.loads(working_hours_json)
492
+
493
+ if "error" not in working_hours_data:
494
+ working_hours_start = working_hours_data["start_hour"]
495
+ working_hours_end = working_hours_data["end_hour"]
496
+ timezone = working_hours_data["timezone"]
497
+ locale = working_hours_data["locale"]
498
+ log_debug(
499
+ f"Using working hours from settings: {working_hours_start}:00-{working_hours_end}:00 ({locale})"
500
+ )
501
+ else:
502
+ # Fallback defaults
503
+ working_hours_start, working_hours_end = 9, 17
504
+ timezone = "UTC"
505
+ locale = "en"
506
+ log_debug("Using default working hours: 9:00-17:00")
507
+
508
+ # Fetch actual calendar events to determine busy periods
509
+ events_json = self.fetch_all_events(start_date=start_date, end_date=end_date)
510
+ events_data = json.loads(events_json)
511
+
512
+ if "error" in events_data:
513
+ return json.dumps({"error": events_data["error"]})
514
+
515
+ events = events_data if isinstance(events_data, list) else events_data.get("items", [])
516
+
517
+ # Extract busy periods from actual calendar events
518
+ busy_periods = []
519
+ for event in events:
520
+ # Skip all-day events and transparent events
521
+ if event.get("transparency") == "transparent":
522
+ continue
523
+
524
+ start_info = event.get("start", {})
525
+ end_info = event.get("end", {})
526
+
527
+ # Only process timed events (not all-day)
528
+ if "dateTime" in start_info and "dateTime" in end_info:
529
+ try:
530
+ start_time = datetime.datetime.fromisoformat(start_info["dateTime"].replace("Z", "+00:00"))
531
+ end_time = datetime.datetime.fromisoformat(end_info["dateTime"].replace("Z", "+00:00"))
532
+ busy_periods.append((start_time, end_time))
533
+ except (ValueError, KeyError) as e:
534
+ log_debug(f"Skipping invalid event: {e}")
535
+ continue
536
+
537
+ # Generate available slots within working hours
538
+ available_slots = []
539
+ current_date = start_dt.replace(hour=working_hours_start, minute=0, second=0, microsecond=0)
540
+ end_search = end_dt.replace(hour=working_hours_end, minute=0, second=0, microsecond=0)
541
+
542
+ while current_date <= end_search:
543
+ # Skip weekends if not in working hours
544
+ if current_date.weekday() >= 5: # Saturday=5, Sunday=6
545
+ current_date = (current_date + datetime.timedelta(days=1)).replace(
546
+ hour=working_hours_start, minute=0, second=0, microsecond=0
547
+ )
548
+ continue
549
+
550
+ slot_end = current_date + datetime.timedelta(minutes=duration_minutes)
551
+
552
+ # Check if this slot conflicts with any busy period
553
+ is_available = True
554
+ for busy_start, busy_end in busy_periods:
555
+ if not (slot_end <= busy_start or current_date >= busy_end):
556
+ is_available = False
557
+ break
558
+
559
+ # Only add slots within working hours
560
+ if is_available and slot_end.hour <= working_hours_end:
561
+ available_slots.append({"start": current_date.isoformat(), "end": slot_end.isoformat()})
562
+
563
+ # Move to next slot (30-minute intervals)
564
+ current_date += datetime.timedelta(minutes=30)
565
+
566
+ # Skip to next day at working hours start if past working hours end
567
+ if current_date.hour >= working_hours_end:
568
+ current_date = (current_date + datetime.timedelta(days=1)).replace(
569
+ hour=working_hours_start, minute=0, second=0, microsecond=0
570
+ )
571
+
572
+ result = {
573
+ "available_slots": available_slots,
574
+ "duration_minutes": duration_minutes,
575
+ "working_hours": {"start": f"{working_hours_start:02d}:00", "end": f"{working_hours_end:02d}:00"},
576
+ "timezone": timezone,
577
+ "locale": locale,
578
+ "events_analyzed": len(busy_periods),
579
+ }
580
+
581
+ log_debug(f"Found {len(available_slots)} available slots")
582
+ return json.dumps(result)
583
+
584
+ except Exception as e:
585
+ log_error(f"An error occurred while finding available slots: {e}")
586
+ return json.dumps({"error": f"An error occurred: {str(e)}"})
587
+
588
+ @authenticate
589
+ def _get_working_hours(self) -> str:
590
+ """
591
+ Get working hours based on user's calendar settings and locale.
592
+
593
+ Returns:
594
+ str: JSON string containing working hours information
595
+ """
596
+ try:
597
+ # Get all user settings
598
+ settings_result = self.service.settings().list().execute() # type: ignore
599
+ settings = settings_result.get("items", [])
600
+
601
+ # Process settings into a more usable format
602
+ user_prefs = {}
603
+ for setting in settings:
604
+ user_prefs[setting["id"]] = setting["value"]
605
+
606
+ # Extract relevant settings
607
+ timezone = user_prefs.get("timezone", "UTC")
608
+ locale = user_prefs.get("locale", "en")
609
+ week_start = int(user_prefs.get("weekStart", "0")) # 0=Sunday, 1=Monday, 6=Saturday
610
+ hide_weekends = user_prefs.get("hideWeekends", "false") == "true"
611
+
612
+ # Determine working hours based on locale/culture
613
+ if locale.startswith(("es", "it", "pt")): # Spain, Italy, Portugal
614
+ start_hour, end_hour = 9, 18
615
+ elif locale.startswith(("de", "nl", "dk", "se", "no")): # Northern Europe
616
+ start_hour, end_hour = 8, 17
617
+ elif locale.startswith(("ja", "ko")): # East Asia
618
+ start_hour, end_hour = 9, 18
619
+ else: # Default US/International
620
+ start_hour, end_hour = 9, 17
621
+
622
+ working_hours = {
623
+ "start_hour": start_hour,
624
+ "end_hour": end_hour,
625
+ "start_time": f"{start_hour:02d}:00",
626
+ "end_time": f"{end_hour:02d}:00",
627
+ "timezone": timezone,
628
+ "locale": locale,
629
+ "week_start": week_start,
630
+ "hide_weekends": hide_weekends,
631
+ }
632
+
633
+ log_debug(f"Working hours for locale {locale}: {start_hour}:00-{end_hour}:00")
634
+ return json.dumps(working_hours)
635
+
636
+ except HttpError as error:
637
+ log_error(f"An error occurred while getting working hours: {error}")
638
+ return json.dumps({"error": f"An error occurred: {error}"})
639
+
640
+ @authenticate
641
+ def list_calendars(self) -> str:
642
+ """
643
+ List all available Google Calendars for the authenticated user.
644
+
645
+ Returns:
646
+ str: JSON string containing available calendars with their IDs and names
647
+ """
648
+ try:
649
+ calendar_list = self.service.calendarList().list().execute() # type: ignore
650
+ calendars = calendar_list.get("items", [])
651
+
652
+ all_calendars = []
653
+ for calendar in calendars:
654
+ calendar_info = {
655
+ "id": calendar.get("id"),
656
+ "name": calendar.get("summary", "Unnamed Calendar"),
657
+ "description": calendar.get("description", ""),
658
+ "primary": calendar.get("primary", False),
659
+ "access_role": calendar.get("accessRole", "unknown"),
660
+ "color": calendar.get("backgroundColor", "#ffffff"),
661
+ }
662
+ all_calendars.append(calendar_info)
663
+
664
+ log_debug(f"Found {len(all_calendars)} calendars for user")
665
+ return json.dumps(
666
+ {
667
+ "calendars": all_calendars,
668
+ "current_default": self.calendar_id,
669
+ }
670
+ )
671
+
672
+ except HttpError as error:
673
+ log_error(f"An error occurred while listing calendars: {error}")
674
+ return json.dumps({"error": f"An error occurred: {error}"})