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.
- agno/__init__.py +8 -0
- agno/agent/__init__.py +51 -0
- agno/agent/agent.py +10405 -0
- agno/api/__init__.py +0 -0
- agno/api/agent.py +28 -0
- agno/api/api.py +40 -0
- agno/api/evals.py +22 -0
- agno/api/os.py +17 -0
- agno/api/routes.py +13 -0
- agno/api/schemas/__init__.py +9 -0
- agno/api/schemas/agent.py +16 -0
- agno/api/schemas/evals.py +16 -0
- agno/api/schemas/os.py +14 -0
- agno/api/schemas/response.py +6 -0
- agno/api/schemas/team.py +16 -0
- agno/api/schemas/utils.py +21 -0
- agno/api/schemas/workflows.py +16 -0
- agno/api/settings.py +53 -0
- agno/api/team.py +30 -0
- agno/api/workflow.py +28 -0
- agno/cloud/aws/base.py +214 -0
- agno/cloud/aws/s3/__init__.py +2 -0
- agno/cloud/aws/s3/api_client.py +43 -0
- agno/cloud/aws/s3/bucket.py +195 -0
- agno/cloud/aws/s3/object.py +57 -0
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +956 -0
- agno/db/__init__.py +24 -0
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/base.py +598 -0
- agno/db/dynamo/__init__.py +3 -0
- agno/db/dynamo/dynamo.py +2042 -0
- agno/db/dynamo/schemas.py +314 -0
- agno/db/dynamo/utils.py +743 -0
- agno/db/firestore/__init__.py +3 -0
- agno/db/firestore/firestore.py +1795 -0
- agno/db/firestore/schemas.py +140 -0
- agno/db/firestore/utils.py +376 -0
- agno/db/gcs_json/__init__.py +3 -0
- agno/db/gcs_json/gcs_json_db.py +1335 -0
- agno/db/gcs_json/utils.py +228 -0
- agno/db/in_memory/__init__.py +3 -0
- agno/db/in_memory/in_memory_db.py +1160 -0
- agno/db/in_memory/utils.py +230 -0
- agno/db/json/__init__.py +3 -0
- agno/db/json/json_db.py +1328 -0
- agno/db/json/utils.py +230 -0
- agno/db/migrations/__init__.py +0 -0
- agno/db/migrations/v1_to_v2.py +635 -0
- agno/db/mongo/__init__.py +17 -0
- agno/db/mongo/async_mongo.py +2026 -0
- agno/db/mongo/mongo.py +1982 -0
- agno/db/mongo/schemas.py +87 -0
- agno/db/mongo/utils.py +259 -0
- agno/db/mysql/__init__.py +3 -0
- agno/db/mysql/mysql.py +2308 -0
- agno/db/mysql/schemas.py +138 -0
- agno/db/mysql/utils.py +355 -0
- agno/db/postgres/__init__.py +4 -0
- agno/db/postgres/async_postgres.py +1927 -0
- agno/db/postgres/postgres.py +2260 -0
- agno/db/postgres/schemas.py +139 -0
- agno/db/postgres/utils.py +442 -0
- agno/db/redis/__init__.py +3 -0
- agno/db/redis/redis.py +1660 -0
- agno/db/redis/schemas.py +123 -0
- agno/db/redis/utils.py +346 -0
- agno/db/schemas/__init__.py +4 -0
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/evals.py +33 -0
- agno/db/schemas/knowledge.py +40 -0
- agno/db/schemas/memory.py +46 -0
- agno/db/schemas/metrics.py +0 -0
- agno/db/singlestore/__init__.py +3 -0
- agno/db/singlestore/schemas.py +130 -0
- agno/db/singlestore/singlestore.py +2272 -0
- agno/db/singlestore/utils.py +384 -0
- agno/db/sqlite/__init__.py +4 -0
- agno/db/sqlite/async_sqlite.py +2293 -0
- agno/db/sqlite/schemas.py +133 -0
- agno/db/sqlite/sqlite.py +2288 -0
- agno/db/sqlite/utils.py +431 -0
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +309 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1353 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +116 -0
- agno/debug.py +18 -0
- agno/eval/__init__.py +14 -0
- agno/eval/accuracy.py +834 -0
- agno/eval/performance.py +773 -0
- agno/eval/reliability.py +306 -0
- agno/eval/utils.py +119 -0
- agno/exceptions.py +161 -0
- agno/filters.py +354 -0
- agno/guardrails/__init__.py +6 -0
- agno/guardrails/base.py +19 -0
- agno/guardrails/openai.py +144 -0
- agno/guardrails/pii.py +94 -0
- agno/guardrails/prompt_injection.py +52 -0
- agno/integrations/__init__.py +0 -0
- agno/integrations/discord/__init__.py +3 -0
- agno/integrations/discord/client.py +203 -0
- agno/knowledge/__init__.py +5 -0
- agno/knowledge/chunking/__init__.py +0 -0
- agno/knowledge/chunking/agentic.py +79 -0
- agno/knowledge/chunking/document.py +91 -0
- agno/knowledge/chunking/fixed.py +57 -0
- agno/knowledge/chunking/markdown.py +151 -0
- agno/knowledge/chunking/recursive.py +63 -0
- agno/knowledge/chunking/row.py +39 -0
- agno/knowledge/chunking/semantic.py +86 -0
- agno/knowledge/chunking/strategy.py +165 -0
- agno/knowledge/content.py +74 -0
- agno/knowledge/document/__init__.py +5 -0
- agno/knowledge/document/base.py +58 -0
- agno/knowledge/embedder/__init__.py +5 -0
- agno/knowledge/embedder/aws_bedrock.py +343 -0
- agno/knowledge/embedder/azure_openai.py +210 -0
- agno/knowledge/embedder/base.py +23 -0
- agno/knowledge/embedder/cohere.py +323 -0
- agno/knowledge/embedder/fastembed.py +62 -0
- agno/knowledge/embedder/fireworks.py +13 -0
- agno/knowledge/embedder/google.py +258 -0
- agno/knowledge/embedder/huggingface.py +94 -0
- agno/knowledge/embedder/jina.py +182 -0
- agno/knowledge/embedder/langdb.py +22 -0
- agno/knowledge/embedder/mistral.py +206 -0
- agno/knowledge/embedder/nebius.py +13 -0
- agno/knowledge/embedder/ollama.py +154 -0
- agno/knowledge/embedder/openai.py +195 -0
- agno/knowledge/embedder/sentence_transformer.py +63 -0
- agno/knowledge/embedder/together.py +13 -0
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/embedder/voyageai.py +165 -0
- agno/knowledge/knowledge.py +1988 -0
- agno/knowledge/reader/__init__.py +7 -0
- agno/knowledge/reader/arxiv_reader.py +81 -0
- agno/knowledge/reader/base.py +95 -0
- agno/knowledge/reader/csv_reader.py +166 -0
- agno/knowledge/reader/docx_reader.py +82 -0
- agno/knowledge/reader/field_labeled_csv_reader.py +292 -0
- agno/knowledge/reader/firecrawl_reader.py +201 -0
- agno/knowledge/reader/json_reader.py +87 -0
- agno/knowledge/reader/markdown_reader.py +137 -0
- agno/knowledge/reader/pdf_reader.py +431 -0
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +313 -0
- agno/knowledge/reader/s3_reader.py +89 -0
- agno/knowledge/reader/tavily_reader.py +194 -0
- agno/knowledge/reader/text_reader.py +115 -0
- agno/knowledge/reader/web_search_reader.py +372 -0
- agno/knowledge/reader/website_reader.py +455 -0
- agno/knowledge/reader/wikipedia_reader.py +59 -0
- agno/knowledge/reader/youtube_reader.py +78 -0
- agno/knowledge/remote_content/__init__.py +0 -0
- agno/knowledge/remote_content/remote_content.py +88 -0
- agno/knowledge/reranker/__init__.py +3 -0
- agno/knowledge/reranker/base.py +14 -0
- agno/knowledge/reranker/cohere.py +64 -0
- agno/knowledge/reranker/infinity.py +195 -0
- agno/knowledge/reranker/sentence_transformer.py +54 -0
- agno/knowledge/types.py +39 -0
- agno/knowledge/utils.py +189 -0
- agno/media.py +462 -0
- agno/memory/__init__.py +3 -0
- agno/memory/manager.py +1327 -0
- agno/models/__init__.py +0 -0
- agno/models/aimlapi/__init__.py +5 -0
- agno/models/aimlapi/aimlapi.py +45 -0
- agno/models/anthropic/__init__.py +5 -0
- agno/models/anthropic/claude.py +757 -0
- agno/models/aws/__init__.py +15 -0
- agno/models/aws/bedrock.py +701 -0
- agno/models/aws/claude.py +378 -0
- agno/models/azure/__init__.py +18 -0
- agno/models/azure/ai_foundry.py +485 -0
- agno/models/azure/openai_chat.py +131 -0
- agno/models/base.py +2175 -0
- agno/models/cerebras/__init__.py +12 -0
- agno/models/cerebras/cerebras.py +501 -0
- agno/models/cerebras/cerebras_openai.py +112 -0
- agno/models/cohere/__init__.py +5 -0
- agno/models/cohere/chat.py +389 -0
- agno/models/cometapi/__init__.py +5 -0
- agno/models/cometapi/cometapi.py +57 -0
- agno/models/dashscope/__init__.py +5 -0
- agno/models/dashscope/dashscope.py +91 -0
- agno/models/deepinfra/__init__.py +5 -0
- agno/models/deepinfra/deepinfra.py +28 -0
- agno/models/deepseek/__init__.py +5 -0
- agno/models/deepseek/deepseek.py +61 -0
- agno/models/defaults.py +1 -0
- agno/models/fireworks/__init__.py +5 -0
- agno/models/fireworks/fireworks.py +26 -0
- agno/models/google/__init__.py +5 -0
- agno/models/google/gemini.py +1085 -0
- agno/models/groq/__init__.py +5 -0
- agno/models/groq/groq.py +556 -0
- agno/models/huggingface/__init__.py +5 -0
- agno/models/huggingface/huggingface.py +491 -0
- agno/models/ibm/__init__.py +5 -0
- agno/models/ibm/watsonx.py +422 -0
- agno/models/internlm/__init__.py +3 -0
- agno/models/internlm/internlm.py +26 -0
- agno/models/langdb/__init__.py +1 -0
- agno/models/langdb/langdb.py +48 -0
- agno/models/litellm/__init__.py +14 -0
- agno/models/litellm/chat.py +468 -0
- agno/models/litellm/litellm_openai.py +25 -0
- agno/models/llama_cpp/__init__.py +5 -0
- agno/models/llama_cpp/llama_cpp.py +22 -0
- agno/models/lmstudio/__init__.py +5 -0
- agno/models/lmstudio/lmstudio.py +25 -0
- agno/models/message.py +434 -0
- agno/models/meta/__init__.py +12 -0
- agno/models/meta/llama.py +475 -0
- agno/models/meta/llama_openai.py +78 -0
- agno/models/metrics.py +120 -0
- agno/models/mistral/__init__.py +5 -0
- agno/models/mistral/mistral.py +432 -0
- agno/models/nebius/__init__.py +3 -0
- agno/models/nebius/nebius.py +54 -0
- agno/models/nexus/__init__.py +3 -0
- agno/models/nexus/nexus.py +22 -0
- agno/models/nvidia/__init__.py +5 -0
- agno/models/nvidia/nvidia.py +28 -0
- agno/models/ollama/__init__.py +5 -0
- agno/models/ollama/chat.py +441 -0
- agno/models/openai/__init__.py +9 -0
- agno/models/openai/chat.py +883 -0
- agno/models/openai/like.py +27 -0
- agno/models/openai/responses.py +1050 -0
- agno/models/openrouter/__init__.py +5 -0
- agno/models/openrouter/openrouter.py +66 -0
- agno/models/perplexity/__init__.py +5 -0
- agno/models/perplexity/perplexity.py +187 -0
- agno/models/portkey/__init__.py +3 -0
- agno/models/portkey/portkey.py +81 -0
- agno/models/requesty/__init__.py +5 -0
- agno/models/requesty/requesty.py +52 -0
- agno/models/response.py +199 -0
- agno/models/sambanova/__init__.py +5 -0
- agno/models/sambanova/sambanova.py +28 -0
- agno/models/siliconflow/__init__.py +5 -0
- agno/models/siliconflow/siliconflow.py +25 -0
- agno/models/together/__init__.py +5 -0
- agno/models/together/together.py +25 -0
- agno/models/utils.py +266 -0
- agno/models/vercel/__init__.py +3 -0
- agno/models/vercel/v0.py +26 -0
- agno/models/vertexai/__init__.py +0 -0
- agno/models/vertexai/claude.py +70 -0
- agno/models/vllm/__init__.py +3 -0
- agno/models/vllm/vllm.py +78 -0
- agno/models/xai/__init__.py +3 -0
- agno/models/xai/xai.py +113 -0
- agno/os/__init__.py +3 -0
- agno/os/app.py +876 -0
- agno/os/auth.py +57 -0
- agno/os/config.py +104 -0
- agno/os/interfaces/__init__.py +1 -0
- agno/os/interfaces/a2a/__init__.py +3 -0
- agno/os/interfaces/a2a/a2a.py +42 -0
- agno/os/interfaces/a2a/router.py +250 -0
- agno/os/interfaces/a2a/utils.py +924 -0
- agno/os/interfaces/agui/__init__.py +3 -0
- agno/os/interfaces/agui/agui.py +47 -0
- agno/os/interfaces/agui/router.py +144 -0
- agno/os/interfaces/agui/utils.py +534 -0
- agno/os/interfaces/base.py +25 -0
- agno/os/interfaces/slack/__init__.py +3 -0
- agno/os/interfaces/slack/router.py +148 -0
- agno/os/interfaces/slack/security.py +30 -0
- agno/os/interfaces/slack/slack.py +47 -0
- agno/os/interfaces/whatsapp/__init__.py +3 -0
- agno/os/interfaces/whatsapp/router.py +211 -0
- agno/os/interfaces/whatsapp/security.py +53 -0
- agno/os/interfaces/whatsapp/whatsapp.py +36 -0
- agno/os/mcp.py +292 -0
- agno/os/middleware/__init__.py +7 -0
- agno/os/middleware/jwt.py +233 -0
- agno/os/router.py +1763 -0
- agno/os/routers/__init__.py +3 -0
- agno/os/routers/evals/__init__.py +3 -0
- agno/os/routers/evals/evals.py +430 -0
- agno/os/routers/evals/schemas.py +142 -0
- agno/os/routers/evals/utils.py +162 -0
- agno/os/routers/health.py +31 -0
- agno/os/routers/home.py +52 -0
- agno/os/routers/knowledge/__init__.py +3 -0
- agno/os/routers/knowledge/knowledge.py +997 -0
- agno/os/routers/knowledge/schemas.py +178 -0
- agno/os/routers/memory/__init__.py +3 -0
- agno/os/routers/memory/memory.py +515 -0
- agno/os/routers/memory/schemas.py +62 -0
- agno/os/routers/metrics/__init__.py +3 -0
- agno/os/routers/metrics/metrics.py +190 -0
- agno/os/routers/metrics/schemas.py +47 -0
- agno/os/routers/session/__init__.py +3 -0
- agno/os/routers/session/session.py +997 -0
- agno/os/schema.py +1055 -0
- agno/os/settings.py +43 -0
- agno/os/utils.py +630 -0
- agno/py.typed +0 -0
- agno/reasoning/__init__.py +0 -0
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/azure_ai_foundry.py +67 -0
- agno/reasoning/deepseek.py +63 -0
- agno/reasoning/default.py +97 -0
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/groq.py +71 -0
- agno/reasoning/helpers.py +63 -0
- agno/reasoning/ollama.py +67 -0
- agno/reasoning/openai.py +86 -0
- agno/reasoning/step.py +31 -0
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +787 -0
- agno/run/base.py +229 -0
- agno/run/cancel.py +81 -0
- agno/run/messages.py +32 -0
- agno/run/team.py +753 -0
- agno/run/workflow.py +708 -0
- agno/session/__init__.py +10 -0
- agno/session/agent.py +295 -0
- agno/session/summary.py +265 -0
- agno/session/team.py +392 -0
- agno/session/workflow.py +205 -0
- agno/team/__init__.py +37 -0
- agno/team/team.py +8793 -0
- agno/tools/__init__.py +10 -0
- agno/tools/agentql.py +120 -0
- agno/tools/airflow.py +69 -0
- agno/tools/api.py +122 -0
- agno/tools/apify.py +314 -0
- agno/tools/arxiv.py +127 -0
- agno/tools/aws_lambda.py +53 -0
- agno/tools/aws_ses.py +66 -0
- agno/tools/baidusearch.py +89 -0
- agno/tools/bitbucket.py +292 -0
- agno/tools/brandfetch.py +213 -0
- agno/tools/bravesearch.py +106 -0
- agno/tools/brightdata.py +367 -0
- agno/tools/browserbase.py +209 -0
- agno/tools/calcom.py +255 -0
- agno/tools/calculator.py +151 -0
- agno/tools/cartesia.py +187 -0
- agno/tools/clickup.py +244 -0
- agno/tools/confluence.py +240 -0
- agno/tools/crawl4ai.py +158 -0
- agno/tools/csv_toolkit.py +185 -0
- agno/tools/dalle.py +110 -0
- agno/tools/daytona.py +475 -0
- agno/tools/decorator.py +262 -0
- agno/tools/desi_vocal.py +108 -0
- agno/tools/discord.py +161 -0
- agno/tools/docker.py +716 -0
- agno/tools/duckdb.py +379 -0
- agno/tools/duckduckgo.py +91 -0
- agno/tools/e2b.py +703 -0
- agno/tools/eleven_labs.py +196 -0
- agno/tools/email.py +67 -0
- agno/tools/evm.py +129 -0
- agno/tools/exa.py +396 -0
- agno/tools/fal.py +127 -0
- agno/tools/file.py +240 -0
- agno/tools/file_generation.py +350 -0
- agno/tools/financial_datasets.py +288 -0
- agno/tools/firecrawl.py +143 -0
- agno/tools/function.py +1187 -0
- agno/tools/giphy.py +93 -0
- agno/tools/github.py +1760 -0
- agno/tools/gmail.py +922 -0
- agno/tools/google_bigquery.py +117 -0
- agno/tools/google_drive.py +270 -0
- agno/tools/google_maps.py +253 -0
- agno/tools/googlecalendar.py +674 -0
- agno/tools/googlesearch.py +98 -0
- agno/tools/googlesheets.py +377 -0
- agno/tools/hackernews.py +77 -0
- agno/tools/jina.py +101 -0
- agno/tools/jira.py +170 -0
- agno/tools/knowledge.py +218 -0
- agno/tools/linear.py +426 -0
- agno/tools/linkup.py +58 -0
- agno/tools/local_file_system.py +90 -0
- agno/tools/lumalab.py +183 -0
- agno/tools/mcp/__init__.py +10 -0
- agno/tools/mcp/mcp.py +331 -0
- agno/tools/mcp/multi_mcp.py +347 -0
- agno/tools/mcp/params.py +24 -0
- agno/tools/mcp_toolbox.py +284 -0
- agno/tools/mem0.py +193 -0
- agno/tools/memori.py +339 -0
- agno/tools/memory.py +419 -0
- agno/tools/mlx_transcribe.py +139 -0
- agno/tools/models/__init__.py +0 -0
- agno/tools/models/azure_openai.py +190 -0
- agno/tools/models/gemini.py +203 -0
- agno/tools/models/groq.py +158 -0
- agno/tools/models/morph.py +186 -0
- agno/tools/models/nebius.py +124 -0
- agno/tools/models_labs.py +195 -0
- agno/tools/moviepy_video.py +349 -0
- agno/tools/neo4j.py +134 -0
- agno/tools/newspaper.py +46 -0
- agno/tools/newspaper4k.py +93 -0
- agno/tools/notion.py +204 -0
- agno/tools/openai.py +202 -0
- agno/tools/openbb.py +160 -0
- agno/tools/opencv.py +321 -0
- agno/tools/openweather.py +233 -0
- agno/tools/oxylabs.py +385 -0
- agno/tools/pandas.py +102 -0
- agno/tools/parallel.py +314 -0
- agno/tools/postgres.py +257 -0
- agno/tools/pubmed.py +188 -0
- agno/tools/python.py +205 -0
- agno/tools/reasoning.py +283 -0
- agno/tools/reddit.py +467 -0
- agno/tools/replicate.py +117 -0
- agno/tools/resend.py +62 -0
- agno/tools/scrapegraph.py +222 -0
- agno/tools/searxng.py +152 -0
- agno/tools/serpapi.py +116 -0
- agno/tools/serper.py +255 -0
- agno/tools/shell.py +53 -0
- agno/tools/slack.py +136 -0
- agno/tools/sleep.py +20 -0
- agno/tools/spider.py +116 -0
- agno/tools/sql.py +154 -0
- agno/tools/streamlit/__init__.py +0 -0
- agno/tools/streamlit/components.py +113 -0
- agno/tools/tavily.py +254 -0
- agno/tools/telegram.py +48 -0
- agno/tools/todoist.py +218 -0
- agno/tools/tool_registry.py +1 -0
- agno/tools/toolkit.py +146 -0
- agno/tools/trafilatura.py +388 -0
- agno/tools/trello.py +274 -0
- agno/tools/twilio.py +186 -0
- agno/tools/user_control_flow.py +78 -0
- agno/tools/valyu.py +228 -0
- agno/tools/visualization.py +467 -0
- agno/tools/webbrowser.py +28 -0
- agno/tools/webex.py +76 -0
- agno/tools/website.py +54 -0
- agno/tools/webtools.py +45 -0
- agno/tools/whatsapp.py +286 -0
- agno/tools/wikipedia.py +63 -0
- agno/tools/workflow.py +278 -0
- agno/tools/x.py +335 -0
- agno/tools/yfinance.py +257 -0
- agno/tools/youtube.py +184 -0
- agno/tools/zendesk.py +82 -0
- agno/tools/zep.py +454 -0
- agno/tools/zoom.py +382 -0
- agno/utils/__init__.py +0 -0
- agno/utils/agent.py +820 -0
- agno/utils/audio.py +49 -0
- agno/utils/certs.py +27 -0
- agno/utils/code_execution.py +11 -0
- agno/utils/common.py +132 -0
- agno/utils/dttm.py +13 -0
- agno/utils/enum.py +22 -0
- agno/utils/env.py +11 -0
- agno/utils/events.py +696 -0
- agno/utils/format_str.py +16 -0
- agno/utils/functions.py +166 -0
- agno/utils/gemini.py +426 -0
- agno/utils/hooks.py +57 -0
- agno/utils/http.py +74 -0
- agno/utils/json_schema.py +234 -0
- agno/utils/knowledge.py +36 -0
- agno/utils/location.py +19 -0
- agno/utils/log.py +255 -0
- agno/utils/mcp.py +214 -0
- agno/utils/media.py +352 -0
- agno/utils/merge_dict.py +41 -0
- agno/utils/message.py +118 -0
- agno/utils/models/__init__.py +0 -0
- agno/utils/models/ai_foundry.py +43 -0
- agno/utils/models/claude.py +358 -0
- agno/utils/models/cohere.py +87 -0
- agno/utils/models/llama.py +78 -0
- agno/utils/models/mistral.py +98 -0
- agno/utils/models/openai_responses.py +140 -0
- agno/utils/models/schema_utils.py +153 -0
- agno/utils/models/watsonx.py +41 -0
- agno/utils/openai.py +257 -0
- agno/utils/pickle.py +32 -0
- agno/utils/pprint.py +178 -0
- agno/utils/print_response/__init__.py +0 -0
- agno/utils/print_response/agent.py +842 -0
- agno/utils/print_response/team.py +1724 -0
- agno/utils/print_response/workflow.py +1668 -0
- agno/utils/prompts.py +111 -0
- agno/utils/reasoning.py +108 -0
- agno/utils/response.py +163 -0
- agno/utils/response_iterator.py +17 -0
- agno/utils/safe_formatter.py +24 -0
- agno/utils/serialize.py +32 -0
- agno/utils/shell.py +22 -0
- agno/utils/streamlit.py +487 -0
- agno/utils/string.py +231 -0
- agno/utils/team.py +139 -0
- agno/utils/timer.py +41 -0
- agno/utils/tools.py +102 -0
- agno/utils/web.py +23 -0
- agno/utils/whatsapp.py +305 -0
- agno/utils/yaml_io.py +25 -0
- agno/vectordb/__init__.py +3 -0
- agno/vectordb/base.py +127 -0
- agno/vectordb/cassandra/__init__.py +5 -0
- agno/vectordb/cassandra/cassandra.py +501 -0
- agno/vectordb/cassandra/extra_param_mixin.py +11 -0
- agno/vectordb/cassandra/index.py +13 -0
- agno/vectordb/chroma/__init__.py +5 -0
- agno/vectordb/chroma/chromadb.py +929 -0
- agno/vectordb/clickhouse/__init__.py +9 -0
- agno/vectordb/clickhouse/clickhousedb.py +835 -0
- agno/vectordb/clickhouse/index.py +9 -0
- agno/vectordb/couchbase/__init__.py +3 -0
- agno/vectordb/couchbase/couchbase.py +1442 -0
- agno/vectordb/distance.py +7 -0
- agno/vectordb/lancedb/__init__.py +6 -0
- agno/vectordb/lancedb/lance_db.py +995 -0
- agno/vectordb/langchaindb/__init__.py +5 -0
- agno/vectordb/langchaindb/langchaindb.py +163 -0
- agno/vectordb/lightrag/__init__.py +5 -0
- agno/vectordb/lightrag/lightrag.py +388 -0
- agno/vectordb/llamaindex/__init__.py +3 -0
- agno/vectordb/llamaindex/llamaindexdb.py +166 -0
- agno/vectordb/milvus/__init__.py +4 -0
- agno/vectordb/milvus/milvus.py +1182 -0
- agno/vectordb/mongodb/__init__.py +9 -0
- agno/vectordb/mongodb/mongodb.py +1417 -0
- agno/vectordb/pgvector/__init__.py +12 -0
- agno/vectordb/pgvector/index.py +23 -0
- agno/vectordb/pgvector/pgvector.py +1462 -0
- agno/vectordb/pineconedb/__init__.py +5 -0
- agno/vectordb/pineconedb/pineconedb.py +747 -0
- agno/vectordb/qdrant/__init__.py +5 -0
- agno/vectordb/qdrant/qdrant.py +1134 -0
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +694 -0
- agno/vectordb/search.py +7 -0
- agno/vectordb/singlestore/__init__.py +10 -0
- agno/vectordb/singlestore/index.py +41 -0
- agno/vectordb/singlestore/singlestore.py +763 -0
- agno/vectordb/surrealdb/__init__.py +3 -0
- agno/vectordb/surrealdb/surrealdb.py +699 -0
- agno/vectordb/upstashdb/__init__.py +5 -0
- agno/vectordb/upstashdb/upstashdb.py +718 -0
- agno/vectordb/weaviate/__init__.py +8 -0
- agno/vectordb/weaviate/index.py +15 -0
- agno/vectordb/weaviate/weaviate.py +1005 -0
- agno/workflow/__init__.py +23 -0
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +738 -0
- agno/workflow/loop.py +735 -0
- agno/workflow/parallel.py +824 -0
- agno/workflow/router.py +702 -0
- agno/workflow/step.py +1432 -0
- agno/workflow/steps.py +592 -0
- agno/workflow/types.py +520 -0
- agno/workflow/workflow.py +4321 -0
- agno-2.2.13.dist-info/METADATA +614 -0
- agno-2.2.13.dist-info/RECORD +575 -0
- agno-2.2.13.dist-info/WHEEL +5 -0
- agno-2.2.13.dist-info/licenses/LICENSE +201 -0
- 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}"})
|