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
agno/os/mcp.py
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"""Router for MCP interface providing Model Context Protocol endpoints."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import TYPE_CHECKING, List, Optional, cast
|
|
5
|
+
from uuid import uuid4
|
|
6
|
+
|
|
7
|
+
from fastmcp import FastMCP
|
|
8
|
+
from fastmcp.server.http import (
|
|
9
|
+
StarletteWithLifespan,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
from agno.db.base import AsyncBaseDb, SessionType
|
|
13
|
+
from agno.db.schemas import UserMemory
|
|
14
|
+
from agno.os.routers.memory.schemas import (
|
|
15
|
+
UserMemorySchema,
|
|
16
|
+
)
|
|
17
|
+
from agno.os.schema import (
|
|
18
|
+
AgentSummaryResponse,
|
|
19
|
+
ConfigResponse,
|
|
20
|
+
InterfaceResponse,
|
|
21
|
+
SessionSchema,
|
|
22
|
+
TeamSummaryResponse,
|
|
23
|
+
WorkflowSummaryResponse,
|
|
24
|
+
)
|
|
25
|
+
from agno.os.utils import (
|
|
26
|
+
get_agent_by_id,
|
|
27
|
+
get_db,
|
|
28
|
+
get_team_by_id,
|
|
29
|
+
get_workflow_by_id,
|
|
30
|
+
)
|
|
31
|
+
from agno.run.agent import RunOutput
|
|
32
|
+
from agno.run.team import TeamRunOutput
|
|
33
|
+
from agno.run.workflow import WorkflowRunOutput
|
|
34
|
+
|
|
35
|
+
if TYPE_CHECKING:
|
|
36
|
+
from agno.os.app import AgentOS
|
|
37
|
+
|
|
38
|
+
logger = logging.getLogger(__name__)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_mcp_server(
|
|
42
|
+
os: "AgentOS",
|
|
43
|
+
) -> StarletteWithLifespan:
|
|
44
|
+
"""Attach MCP routes to the provided router."""
|
|
45
|
+
|
|
46
|
+
# Create an MCP server
|
|
47
|
+
mcp = FastMCP(os.name or "AgentOS")
|
|
48
|
+
|
|
49
|
+
@mcp.tool(
|
|
50
|
+
name="get_agentos_config",
|
|
51
|
+
description="Get the configuration of the AgentOS",
|
|
52
|
+
tags={"core"},
|
|
53
|
+
output_schema=ConfigResponse.model_json_schema(),
|
|
54
|
+
) # type: ignore
|
|
55
|
+
async def config() -> ConfigResponse:
|
|
56
|
+
return ConfigResponse(
|
|
57
|
+
os_id=os.id or "AgentOS",
|
|
58
|
+
description=os.description,
|
|
59
|
+
available_models=os.config.available_models if os.config else [],
|
|
60
|
+
databases=[db.id for db_list in os.dbs.values() for db in db_list],
|
|
61
|
+
chat=os.config.chat if os.config else None,
|
|
62
|
+
session=os._get_session_config(),
|
|
63
|
+
memory=os._get_memory_config(),
|
|
64
|
+
knowledge=os._get_knowledge_config(),
|
|
65
|
+
evals=os._get_evals_config(),
|
|
66
|
+
metrics=os._get_metrics_config(),
|
|
67
|
+
agents=[AgentSummaryResponse.from_agent(agent) for agent in os.agents] if os.agents else [],
|
|
68
|
+
teams=[TeamSummaryResponse.from_team(team) for team in os.teams] if os.teams else [],
|
|
69
|
+
workflows=[WorkflowSummaryResponse.from_workflow(w) for w in os.workflows] if os.workflows else [],
|
|
70
|
+
interfaces=[
|
|
71
|
+
InterfaceResponse(type=interface.type, version=interface.version, route=interface.prefix)
|
|
72
|
+
for interface in os.interfaces
|
|
73
|
+
],
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
@mcp.tool(name="run_agent", description="Run an agent", tags={"core"}) # type: ignore
|
|
77
|
+
async def run_agent(agent_id: str, message: str) -> RunOutput:
|
|
78
|
+
agent = get_agent_by_id(agent_id, os.agents)
|
|
79
|
+
if agent is None:
|
|
80
|
+
raise Exception(f"Agent {agent_id} not found")
|
|
81
|
+
return await agent.arun(message)
|
|
82
|
+
|
|
83
|
+
@mcp.tool(name="run_team", description="Run a team", tags={"core"}) # type: ignore
|
|
84
|
+
async def run_team(team_id: str, message: str) -> TeamRunOutput:
|
|
85
|
+
team = get_team_by_id(team_id, os.teams)
|
|
86
|
+
if team is None:
|
|
87
|
+
raise Exception(f"Team {team_id} not found")
|
|
88
|
+
return await team.arun(message)
|
|
89
|
+
|
|
90
|
+
@mcp.tool(name="run_workflow", description="Run a workflow", tags={"core"}) # type: ignore
|
|
91
|
+
async def run_workflow(workflow_id: str, message: str) -> WorkflowRunOutput:
|
|
92
|
+
workflow = get_workflow_by_id(workflow_id, os.workflows)
|
|
93
|
+
if workflow is None:
|
|
94
|
+
raise Exception(f"Workflow {workflow_id} not found")
|
|
95
|
+
return await workflow.arun(message)
|
|
96
|
+
|
|
97
|
+
# Session Management Tools
|
|
98
|
+
@mcp.tool(name="get_sessions_for_agent", description="Get list of sessions for an agent", tags={"session"}) # type: ignore
|
|
99
|
+
async def get_sessions_for_agent(
|
|
100
|
+
agent_id: str,
|
|
101
|
+
db_id: str,
|
|
102
|
+
user_id: Optional[str] = None,
|
|
103
|
+
sort_by: str = "created_at",
|
|
104
|
+
sort_order: str = "desc",
|
|
105
|
+
):
|
|
106
|
+
db = await get_db(os.dbs, db_id)
|
|
107
|
+
if isinstance(db, AsyncBaseDb):
|
|
108
|
+
db = cast(AsyncBaseDb, db)
|
|
109
|
+
sessions = await db.get_sessions(
|
|
110
|
+
session_type=SessionType.AGENT,
|
|
111
|
+
component_id=agent_id,
|
|
112
|
+
user_id=user_id,
|
|
113
|
+
sort_by=sort_by,
|
|
114
|
+
sort_order=sort_order,
|
|
115
|
+
deserialize=False,
|
|
116
|
+
)
|
|
117
|
+
else:
|
|
118
|
+
sessions = db.get_sessions(
|
|
119
|
+
session_type=SessionType.AGENT,
|
|
120
|
+
component_id=agent_id,
|
|
121
|
+
user_id=user_id,
|
|
122
|
+
sort_by=sort_by,
|
|
123
|
+
sort_order=sort_order,
|
|
124
|
+
deserialize=False,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
"data": [SessionSchema.from_dict(session) for session in sessions], # type: ignore
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@mcp.tool(name="get_sessions_for_team", description="Get list of sessions for a team", tags={"session"}) # type: ignore
|
|
132
|
+
async def get_sessions_for_team(
|
|
133
|
+
team_id: str,
|
|
134
|
+
db_id: str,
|
|
135
|
+
user_id: Optional[str] = None,
|
|
136
|
+
sort_by: str = "created_at",
|
|
137
|
+
sort_order: str = "desc",
|
|
138
|
+
):
|
|
139
|
+
db = await get_db(os.dbs, db_id)
|
|
140
|
+
if isinstance(db, AsyncBaseDb):
|
|
141
|
+
db = cast(AsyncBaseDb, db)
|
|
142
|
+
sessions = await db.get_sessions(
|
|
143
|
+
session_type=SessionType.TEAM,
|
|
144
|
+
component_id=team_id,
|
|
145
|
+
user_id=user_id,
|
|
146
|
+
sort_by=sort_by,
|
|
147
|
+
sort_order=sort_order,
|
|
148
|
+
deserialize=False,
|
|
149
|
+
)
|
|
150
|
+
else:
|
|
151
|
+
sessions = db.get_sessions(
|
|
152
|
+
session_type=SessionType.TEAM,
|
|
153
|
+
component_id=team_id,
|
|
154
|
+
user_id=user_id,
|
|
155
|
+
sort_by=sort_by,
|
|
156
|
+
sort_order=sort_order,
|
|
157
|
+
deserialize=False,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
"data": [SessionSchema.from_dict(session) for session in sessions], # type: ignore
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
@mcp.tool(name="get_sessions_for_workflow", description="Get list of sessions for a workflow", tags={"session"}) # type: ignore
|
|
165
|
+
async def get_sessions_for_workflow(
|
|
166
|
+
workflow_id: str,
|
|
167
|
+
db_id: str,
|
|
168
|
+
user_id: Optional[str] = None,
|
|
169
|
+
sort_by: str = "created_at",
|
|
170
|
+
sort_order: str = "desc",
|
|
171
|
+
):
|
|
172
|
+
db = await get_db(os.dbs, db_id)
|
|
173
|
+
if isinstance(db, AsyncBaseDb):
|
|
174
|
+
db = cast(AsyncBaseDb, db)
|
|
175
|
+
sessions = await db.get_sessions(
|
|
176
|
+
session_type=SessionType.WORKFLOW,
|
|
177
|
+
component_id=workflow_id,
|
|
178
|
+
user_id=user_id,
|
|
179
|
+
sort_by=sort_by,
|
|
180
|
+
sort_order=sort_order,
|
|
181
|
+
deserialize=False,
|
|
182
|
+
)
|
|
183
|
+
else:
|
|
184
|
+
sessions = db.get_sessions(
|
|
185
|
+
session_type=SessionType.WORKFLOW,
|
|
186
|
+
component_id=workflow_id,
|
|
187
|
+
user_id=user_id,
|
|
188
|
+
sort_by=sort_by,
|
|
189
|
+
sort_order=sort_order,
|
|
190
|
+
deserialize=False,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
"data": [SessionSchema.from_dict(session) for session in sessions], # type: ignore
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
# Memory Management Tools
|
|
198
|
+
@mcp.tool(name="create_memory", description="Create a new user memory", tags={"memory"}) # type: ignore
|
|
199
|
+
async def create_memory(
|
|
200
|
+
db_id: str,
|
|
201
|
+
memory: str,
|
|
202
|
+
user_id: str,
|
|
203
|
+
topics: Optional[List[str]] = None,
|
|
204
|
+
) -> UserMemorySchema:
|
|
205
|
+
db = await get_db(os.dbs, db_id)
|
|
206
|
+
user_memory = db.upsert_user_memory(
|
|
207
|
+
memory=UserMemory(
|
|
208
|
+
memory_id=str(uuid4()),
|
|
209
|
+
memory=memory,
|
|
210
|
+
topics=topics or [],
|
|
211
|
+
user_id=user_id,
|
|
212
|
+
),
|
|
213
|
+
deserialize=False,
|
|
214
|
+
)
|
|
215
|
+
if not user_memory:
|
|
216
|
+
raise Exception("Failed to create memory")
|
|
217
|
+
|
|
218
|
+
return UserMemorySchema.from_dict(user_memory) # type: ignore
|
|
219
|
+
|
|
220
|
+
@mcp.tool(name="get_memories_for_user", description="Get a list of memories for a user", tags={"memory"}) # type: ignore
|
|
221
|
+
async def get_memories_for_user(
|
|
222
|
+
user_id: str,
|
|
223
|
+
sort_by: str = "updated_at",
|
|
224
|
+
sort_order: str = "desc",
|
|
225
|
+
db_id: Optional[str] = None,
|
|
226
|
+
):
|
|
227
|
+
db = await get_db(os.dbs, db_id)
|
|
228
|
+
if isinstance(db, AsyncBaseDb):
|
|
229
|
+
db = cast(AsyncBaseDb, db)
|
|
230
|
+
user_memories = await db.get_user_memories(
|
|
231
|
+
user_id=user_id,
|
|
232
|
+
sort_by=sort_by,
|
|
233
|
+
sort_order=sort_order,
|
|
234
|
+
deserialize=False,
|
|
235
|
+
)
|
|
236
|
+
else:
|
|
237
|
+
user_memories = db.get_user_memories(
|
|
238
|
+
user_id=user_id,
|
|
239
|
+
sort_by=sort_by,
|
|
240
|
+
sort_order=sort_order,
|
|
241
|
+
deserialize=False,
|
|
242
|
+
)
|
|
243
|
+
return {
|
|
244
|
+
"data": [UserMemorySchema.from_dict(user_memory) for user_memory in user_memories], # type: ignore
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
@mcp.tool(name="update_memory", description="Update a memory", tags={"memory"}) # type: ignore
|
|
248
|
+
async def update_memory(
|
|
249
|
+
db_id: str,
|
|
250
|
+
memory_id: str,
|
|
251
|
+
memory: str,
|
|
252
|
+
user_id: str,
|
|
253
|
+
) -> UserMemorySchema:
|
|
254
|
+
db = await get_db(os.dbs, db_id)
|
|
255
|
+
if isinstance(db, AsyncBaseDb):
|
|
256
|
+
db = cast(AsyncBaseDb, db)
|
|
257
|
+
user_memory = await db.upsert_user_memory(
|
|
258
|
+
memory=UserMemory(
|
|
259
|
+
memory_id=memory_id,
|
|
260
|
+
memory=memory,
|
|
261
|
+
user_id=user_id,
|
|
262
|
+
),
|
|
263
|
+
deserialize=False,
|
|
264
|
+
)
|
|
265
|
+
else:
|
|
266
|
+
user_memory = db.upsert_user_memory(
|
|
267
|
+
memory=UserMemory(
|
|
268
|
+
memory_id=memory_id,
|
|
269
|
+
memory=memory,
|
|
270
|
+
user_id=user_id,
|
|
271
|
+
),
|
|
272
|
+
deserialize=False,
|
|
273
|
+
)
|
|
274
|
+
if not user_memory:
|
|
275
|
+
raise Exception("Failed to update memory")
|
|
276
|
+
|
|
277
|
+
return UserMemorySchema.from_dict(user_memory) # type: ignore
|
|
278
|
+
|
|
279
|
+
@mcp.tool(name="delete_memory", description="Delete a memory by ID", tags={"memory"}) # type: ignore
|
|
280
|
+
async def delete_memory(
|
|
281
|
+
db_id: str,
|
|
282
|
+
memory_id: str,
|
|
283
|
+
) -> None:
|
|
284
|
+
db = await get_db(os.dbs, db_id)
|
|
285
|
+
if isinstance(db, AsyncBaseDb):
|
|
286
|
+
db = cast(AsyncBaseDb, db)
|
|
287
|
+
await db.delete_user_memory(memory_id=memory_id)
|
|
288
|
+
else:
|
|
289
|
+
db.delete_user_memory(memory_id=memory_id)
|
|
290
|
+
|
|
291
|
+
mcp_app = mcp.http_app(path="/mcp")
|
|
292
|
+
return mcp_app
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import fnmatch
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from os import getenv
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
|
|
6
|
+
import jwt
|
|
7
|
+
from fastapi import Request, Response
|
|
8
|
+
from fastapi.responses import JSONResponse
|
|
9
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
10
|
+
|
|
11
|
+
from agno.utils.log import log_debug
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TokenSource(str, Enum):
|
|
15
|
+
"""Enum for JWT token source options."""
|
|
16
|
+
|
|
17
|
+
HEADER = "header"
|
|
18
|
+
COOKIE = "cookie"
|
|
19
|
+
BOTH = "both" # Try header first, then cookie
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class JWTMiddleware(BaseHTTPMiddleware):
|
|
23
|
+
"""
|
|
24
|
+
JWT Middleware for validating tokens and storing JWT claims in request state.
|
|
25
|
+
|
|
26
|
+
This middleware:
|
|
27
|
+
1. Extracts JWT token from Authorization header, cookies, or both
|
|
28
|
+
2. Decodes and validates the token
|
|
29
|
+
3. Stores JWT claims in request.state for easy access in endpoints
|
|
30
|
+
|
|
31
|
+
Token Sources:
|
|
32
|
+
- "header": Extract from Authorization header (default)
|
|
33
|
+
- "cookie": Extract from HTTP cookie
|
|
34
|
+
- "both": Try header first, then cookie as fallback
|
|
35
|
+
|
|
36
|
+
Claims are stored as:
|
|
37
|
+
- request.state.user_id: User ID from configured claim
|
|
38
|
+
- request.state.session_id: Session ID from configured claim
|
|
39
|
+
- request.state.dependencies: Dictionary of dependency claims
|
|
40
|
+
- request.state.session_state: Dictionary of session state claims
|
|
41
|
+
- request.state.authenticated: Boolean authentication status
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
app,
|
|
48
|
+
secret_key: Optional[str] = None,
|
|
49
|
+
algorithm: str = "HS256",
|
|
50
|
+
token_source: TokenSource = TokenSource.HEADER,
|
|
51
|
+
token_header_key: str = "Authorization",
|
|
52
|
+
cookie_name: str = "access_token",
|
|
53
|
+
validate: bool = True,
|
|
54
|
+
excluded_route_paths: Optional[List[str]] = None,
|
|
55
|
+
scopes_claim: Optional[str] = None,
|
|
56
|
+
user_id_claim: str = "sub",
|
|
57
|
+
session_id_claim: str = "session_id",
|
|
58
|
+
dependencies_claims: Optional[List[str]] = None,
|
|
59
|
+
session_state_claims: Optional[List[str]] = None,
|
|
60
|
+
):
|
|
61
|
+
"""
|
|
62
|
+
Initialize the JWT middleware.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
app: The FastAPI app instance
|
|
66
|
+
secret_key: The secret key to use for JWT validation (optional, will use JWT_SECRET_KEY environment variable if not provided)
|
|
67
|
+
algorithm: The algorithm to use for JWT validation
|
|
68
|
+
token_header_key: The key to use for the Authorization header (only used when token_source is header)
|
|
69
|
+
token_source: Where to extract the JWT token from (header, cookie, or both)
|
|
70
|
+
cookie_name: The name of the cookie containing the JWT token (only used when token_source is cookie/both)
|
|
71
|
+
validate: Whether to validate the JWT token
|
|
72
|
+
excluded_route_paths: A list of route paths to exclude from JWT validation
|
|
73
|
+
scopes_claim: The claim to use for scopes extraction
|
|
74
|
+
user_id_claim: The claim to use for user ID extraction
|
|
75
|
+
session_id_claim: The claim to use for session ID extraction
|
|
76
|
+
dependencies_claims: A list of claims to extract from the JWT token for dependencies
|
|
77
|
+
session_state_claims: A list of claims to extract from the JWT token for session state
|
|
78
|
+
"""
|
|
79
|
+
super().__init__(app)
|
|
80
|
+
self.secret_key = secret_key or getenv("JWT_SECRET_KEY")
|
|
81
|
+
if not self.secret_key:
|
|
82
|
+
raise ValueError("Secret key is required")
|
|
83
|
+
self.algorithm = algorithm
|
|
84
|
+
self.token_header_key = token_header_key
|
|
85
|
+
self.token_source = token_source
|
|
86
|
+
self.cookie_name = cookie_name
|
|
87
|
+
self.validate = validate
|
|
88
|
+
self.excluded_route_paths = excluded_route_paths
|
|
89
|
+
self.scopes_claim = scopes_claim
|
|
90
|
+
self.user_id_claim = user_id_claim
|
|
91
|
+
self.session_id_claim = session_id_claim
|
|
92
|
+
self.dependencies_claims = dependencies_claims or []
|
|
93
|
+
self.session_state_claims = session_state_claims or []
|
|
94
|
+
|
|
95
|
+
def _extract_token_from_header(self, request: Request) -> Optional[str]:
|
|
96
|
+
"""Extract JWT token from Authorization header."""
|
|
97
|
+
authorization = request.headers.get(self.token_header_key, "")
|
|
98
|
+
if not authorization:
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
# Remove the "Bearer " prefix (if present)
|
|
103
|
+
_, token = authorization.split(" ", 1)
|
|
104
|
+
return token
|
|
105
|
+
except ValueError:
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
def _extract_token_from_cookie(self, request: Request) -> Optional[str]:
|
|
109
|
+
"""Extract JWT token from cookie."""
|
|
110
|
+
return request.cookies.get(self.cookie_name)
|
|
111
|
+
|
|
112
|
+
def _extract_token(self, request: Request) -> Optional[str]:
|
|
113
|
+
"""Extract JWT token based on configured token source."""
|
|
114
|
+
if self.token_source == TokenSource.HEADER:
|
|
115
|
+
return self._extract_token_from_header(request)
|
|
116
|
+
elif self.token_source == TokenSource.COOKIE:
|
|
117
|
+
return self._extract_token_from_cookie(request)
|
|
118
|
+
elif self.token_source == TokenSource.BOTH:
|
|
119
|
+
# Try header first, then cookie
|
|
120
|
+
token = self._extract_token_from_header(request)
|
|
121
|
+
if token is None:
|
|
122
|
+
token = self._extract_token_from_cookie(request)
|
|
123
|
+
return token
|
|
124
|
+
else:
|
|
125
|
+
log_debug(f"Unknown token source: {self.token_source}")
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
def _get_missing_token_error_message(self) -> str:
|
|
129
|
+
"""Get appropriate error message for missing token based on token source."""
|
|
130
|
+
if self.token_source == TokenSource.HEADER:
|
|
131
|
+
return "Authorization header missing"
|
|
132
|
+
elif self.token_source == TokenSource.COOKIE:
|
|
133
|
+
return f"JWT cookie '{self.cookie_name}' missing"
|
|
134
|
+
elif self.token_source == TokenSource.BOTH:
|
|
135
|
+
return f"JWT token missing from both Authorization header and '{self.cookie_name}' cookie"
|
|
136
|
+
else:
|
|
137
|
+
return "JWT token missing"
|
|
138
|
+
|
|
139
|
+
def _is_route_excluded(self, path: str) -> bool:
|
|
140
|
+
"""Check if a route path matches any of the excluded patterns."""
|
|
141
|
+
if not self.excluded_route_paths:
|
|
142
|
+
return False
|
|
143
|
+
|
|
144
|
+
for excluded_path in self.excluded_route_paths:
|
|
145
|
+
# Support both exact matches and wildcard patterns
|
|
146
|
+
if fnmatch.fnmatch(path, excluded_path):
|
|
147
|
+
return True
|
|
148
|
+
|
|
149
|
+
return False
|
|
150
|
+
|
|
151
|
+
async def dispatch(self, request: Request, call_next) -> Response:
|
|
152
|
+
if self._is_route_excluded(request.url.path):
|
|
153
|
+
return await call_next(request)
|
|
154
|
+
|
|
155
|
+
# Extract JWT token from configured source (header, cookie, or both)
|
|
156
|
+
token = self._extract_token(request)
|
|
157
|
+
|
|
158
|
+
if not token:
|
|
159
|
+
if self.validate:
|
|
160
|
+
error_msg = self._get_missing_token_error_message()
|
|
161
|
+
return JSONResponse(status_code=401, content={"detail": error_msg})
|
|
162
|
+
return await call_next(request)
|
|
163
|
+
|
|
164
|
+
# Decode JWT token
|
|
165
|
+
try:
|
|
166
|
+
payload = jwt.decode(token, self.secret_key, algorithms=[self.algorithm]) # type: ignore
|
|
167
|
+
|
|
168
|
+
# Extract scopes claims
|
|
169
|
+
scopes = []
|
|
170
|
+
if self.scopes_claim in payload:
|
|
171
|
+
extracted_scopes = payload[self.scopes_claim]
|
|
172
|
+
if isinstance(extracted_scopes, str):
|
|
173
|
+
scopes = extracted_scopes.split(" ")
|
|
174
|
+
else:
|
|
175
|
+
scopes = extracted_scopes
|
|
176
|
+
if scopes:
|
|
177
|
+
request.state.scopes = scopes
|
|
178
|
+
|
|
179
|
+
# Extract user information
|
|
180
|
+
if self.user_id_claim in payload:
|
|
181
|
+
user_id = payload[self.user_id_claim]
|
|
182
|
+
request.state.user_id = user_id
|
|
183
|
+
if self.session_id_claim in payload:
|
|
184
|
+
session_id = payload[self.session_id_claim]
|
|
185
|
+
request.state.session_id = session_id
|
|
186
|
+
else:
|
|
187
|
+
session_id = None
|
|
188
|
+
|
|
189
|
+
# Extract dependency claims
|
|
190
|
+
dependencies = {}
|
|
191
|
+
for claim in self.dependencies_claims:
|
|
192
|
+
if claim in payload:
|
|
193
|
+
dependencies[claim] = payload[claim]
|
|
194
|
+
|
|
195
|
+
if dependencies:
|
|
196
|
+
request.state.dependencies = dependencies
|
|
197
|
+
|
|
198
|
+
# Extract session state claims
|
|
199
|
+
session_state = {}
|
|
200
|
+
for claim in self.session_state_claims:
|
|
201
|
+
if claim in payload:
|
|
202
|
+
session_state[claim] = payload[claim]
|
|
203
|
+
|
|
204
|
+
if session_state:
|
|
205
|
+
request.state.session_state = session_state
|
|
206
|
+
|
|
207
|
+
request.state.token = token
|
|
208
|
+
request.state.authenticated = True
|
|
209
|
+
|
|
210
|
+
log_debug(f"JWT decoded successfully for user: {user_id}")
|
|
211
|
+
if dependencies:
|
|
212
|
+
log_debug(f"Extracted dependencies: {dependencies}")
|
|
213
|
+
if session_state:
|
|
214
|
+
log_debug(f"Extracted session state: {session_state}")
|
|
215
|
+
|
|
216
|
+
except jwt.ExpiredSignatureError:
|
|
217
|
+
if self.validate:
|
|
218
|
+
return JSONResponse(status_code=401, content={"detail": "Token has expired"})
|
|
219
|
+
request.state.authenticated = False
|
|
220
|
+
request.state.token = token
|
|
221
|
+
|
|
222
|
+
except jwt.InvalidTokenError as e:
|
|
223
|
+
if self.validate:
|
|
224
|
+
return JSONResponse(status_code=401, content={"detail": f"Invalid token: {str(e)}"})
|
|
225
|
+
request.state.authenticated = False
|
|
226
|
+
request.state.token = token
|
|
227
|
+
except Exception as e:
|
|
228
|
+
if self.validate:
|
|
229
|
+
return JSONResponse(status_code=401, content={"detail": f"Error decoding token: {str(e)}"})
|
|
230
|
+
request.state.authenticated = False
|
|
231
|
+
request.state.token = token
|
|
232
|
+
|
|
233
|
+
return await call_next(request)
|