ai-parrot 0.17.2__cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.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.
- agentui/.prettierrc +15 -0
- agentui/QUICKSTART.md +272 -0
- agentui/README.md +59 -0
- agentui/env.example +16 -0
- agentui/jsconfig.json +14 -0
- agentui/package-lock.json +4242 -0
- agentui/package.json +34 -0
- agentui/scripts/postinstall/apply-patches.mjs +260 -0
- agentui/src/app.css +61 -0
- agentui/src/app.d.ts +13 -0
- agentui/src/app.html +12 -0
- agentui/src/components/LoadingSpinner.svelte +64 -0
- agentui/src/components/ThemeSwitcher.svelte +159 -0
- agentui/src/components/index.js +4 -0
- agentui/src/lib/api/bots.ts +60 -0
- agentui/src/lib/api/chat.ts +22 -0
- agentui/src/lib/api/http.ts +25 -0
- agentui/src/lib/components/BotCard.svelte +33 -0
- agentui/src/lib/components/ChatBubble.svelte +63 -0
- agentui/src/lib/components/Toast.svelte +21 -0
- agentui/src/lib/config.ts +20 -0
- agentui/src/lib/stores/auth.svelte.ts +73 -0
- agentui/src/lib/stores/theme.svelte.js +64 -0
- agentui/src/lib/stores/toast.svelte.ts +31 -0
- agentui/src/lib/utils/conversation.ts +39 -0
- agentui/src/routes/+layout.svelte +20 -0
- agentui/src/routes/+page.svelte +232 -0
- agentui/src/routes/login/+page.svelte +200 -0
- agentui/src/routes/talk/[agentId]/+page.svelte +297 -0
- agentui/src/routes/talk/[agentId]/+page.ts +7 -0
- agentui/static/README.md +1 -0
- agentui/svelte.config.js +11 -0
- agentui/tailwind.config.ts +53 -0
- agentui/tsconfig.json +3 -0
- agentui/vite.config.ts +10 -0
- ai_parrot-0.17.2.dist-info/METADATA +472 -0
- ai_parrot-0.17.2.dist-info/RECORD +535 -0
- ai_parrot-0.17.2.dist-info/WHEEL +6 -0
- ai_parrot-0.17.2.dist-info/entry_points.txt +2 -0
- ai_parrot-0.17.2.dist-info/licenses/LICENSE +21 -0
- ai_parrot-0.17.2.dist-info/top_level.txt +6 -0
- crew-builder/.prettierrc +15 -0
- crew-builder/QUICKSTART.md +259 -0
- crew-builder/README.md +113 -0
- crew-builder/env.example +17 -0
- crew-builder/jsconfig.json +14 -0
- crew-builder/package-lock.json +4182 -0
- crew-builder/package.json +37 -0
- crew-builder/scripts/postinstall/apply-patches.mjs +260 -0
- crew-builder/src/app.css +62 -0
- crew-builder/src/app.d.ts +13 -0
- crew-builder/src/app.html +12 -0
- crew-builder/src/components/LoadingSpinner.svelte +64 -0
- crew-builder/src/components/ThemeSwitcher.svelte +149 -0
- crew-builder/src/components/index.js +9 -0
- crew-builder/src/lib/api/bots.ts +60 -0
- crew-builder/src/lib/api/chat.ts +80 -0
- crew-builder/src/lib/api/client.ts +56 -0
- crew-builder/src/lib/api/crew/crew.ts +136 -0
- crew-builder/src/lib/api/index.ts +5 -0
- crew-builder/src/lib/api/o365/auth.ts +65 -0
- crew-builder/src/lib/auth/auth.ts +54 -0
- crew-builder/src/lib/components/AgentNode.svelte +43 -0
- crew-builder/src/lib/components/BotCard.svelte +33 -0
- crew-builder/src/lib/components/ChatBubble.svelte +67 -0
- crew-builder/src/lib/components/ConfigPanel.svelte +278 -0
- crew-builder/src/lib/components/JsonTreeNode.svelte +76 -0
- crew-builder/src/lib/components/JsonViewer.svelte +24 -0
- crew-builder/src/lib/components/MarkdownEditor.svelte +48 -0
- crew-builder/src/lib/components/ThemeToggle.svelte +36 -0
- crew-builder/src/lib/components/Toast.svelte +67 -0
- crew-builder/src/lib/components/Toolbar.svelte +157 -0
- crew-builder/src/lib/components/index.ts +10 -0
- crew-builder/src/lib/config.ts +8 -0
- crew-builder/src/lib/stores/auth.svelte.ts +228 -0
- crew-builder/src/lib/stores/crewStore.ts +369 -0
- crew-builder/src/lib/stores/theme.svelte.js +145 -0
- crew-builder/src/lib/stores/toast.svelte.ts +69 -0
- crew-builder/src/lib/utils/conversation.ts +39 -0
- crew-builder/src/lib/utils/markdown.ts +122 -0
- crew-builder/src/lib/utils/talkHistory.ts +47 -0
- crew-builder/src/routes/+layout.svelte +20 -0
- crew-builder/src/routes/+page.svelte +539 -0
- crew-builder/src/routes/agents/+page.svelte +247 -0
- crew-builder/src/routes/agents/[agentId]/+page.svelte +288 -0
- crew-builder/src/routes/agents/[agentId]/+page.ts +7 -0
- crew-builder/src/routes/builder/+page.svelte +204 -0
- crew-builder/src/routes/crew/ask/+page.svelte +1052 -0
- crew-builder/src/routes/crew/ask/+page.ts +1 -0
- crew-builder/src/routes/integrations/o365/+page.svelte +304 -0
- crew-builder/src/routes/login/+page.svelte +197 -0
- crew-builder/src/routes/talk/[agentId]/+page.svelte +487 -0
- crew-builder/src/routes/talk/[agentId]/+page.ts +7 -0
- crew-builder/static/README.md +1 -0
- crew-builder/svelte.config.js +11 -0
- crew-builder/tailwind.config.ts +53 -0
- crew-builder/tsconfig.json +3 -0
- crew-builder/vite.config.ts +10 -0
- mcp_servers/calculator_server.py +309 -0
- parrot/__init__.py +27 -0
- parrot/__pycache__/__init__.cpython-310.pyc +0 -0
- parrot/__pycache__/version.cpython-310.pyc +0 -0
- parrot/_version.py +34 -0
- parrot/a2a/__init__.py +48 -0
- parrot/a2a/client.py +658 -0
- parrot/a2a/discovery.py +89 -0
- parrot/a2a/mixin.py +257 -0
- parrot/a2a/models.py +376 -0
- parrot/a2a/server.py +770 -0
- parrot/agents/__init__.py +29 -0
- parrot/bots/__init__.py +12 -0
- parrot/bots/a2a_agent.py +19 -0
- parrot/bots/abstract.py +3139 -0
- parrot/bots/agent.py +1129 -0
- parrot/bots/basic.py +9 -0
- parrot/bots/chatbot.py +669 -0
- parrot/bots/data.py +1618 -0
- parrot/bots/database/__init__.py +5 -0
- parrot/bots/database/abstract.py +3071 -0
- parrot/bots/database/cache.py +286 -0
- parrot/bots/database/models.py +468 -0
- parrot/bots/database/prompts.py +154 -0
- parrot/bots/database/retries.py +98 -0
- parrot/bots/database/router.py +269 -0
- parrot/bots/database/sql.py +41 -0
- parrot/bots/db/__init__.py +6 -0
- parrot/bots/db/abstract.py +556 -0
- parrot/bots/db/bigquery.py +602 -0
- parrot/bots/db/cache.py +85 -0
- parrot/bots/db/documentdb.py +668 -0
- parrot/bots/db/elastic.py +1014 -0
- parrot/bots/db/influx.py +898 -0
- parrot/bots/db/mock.py +96 -0
- parrot/bots/db/multi.py +783 -0
- parrot/bots/db/prompts.py +185 -0
- parrot/bots/db/sql.py +1255 -0
- parrot/bots/db/tools.py +212 -0
- parrot/bots/document.py +680 -0
- parrot/bots/hrbot.py +15 -0
- parrot/bots/kb.py +170 -0
- parrot/bots/mcp.py +36 -0
- parrot/bots/orchestration/README.md +463 -0
- parrot/bots/orchestration/__init__.py +1 -0
- parrot/bots/orchestration/agent.py +155 -0
- parrot/bots/orchestration/crew.py +3330 -0
- parrot/bots/orchestration/fsm.py +1179 -0
- parrot/bots/orchestration/hr.py +434 -0
- parrot/bots/orchestration/storage/__init__.py +4 -0
- parrot/bots/orchestration/storage/memory.py +100 -0
- parrot/bots/orchestration/storage/mixin.py +119 -0
- parrot/bots/orchestration/verify.py +202 -0
- parrot/bots/product.py +204 -0
- parrot/bots/prompts/__init__.py +96 -0
- parrot/bots/prompts/agents.py +155 -0
- parrot/bots/prompts/data.py +216 -0
- parrot/bots/prompts/output_generation.py +8 -0
- parrot/bots/scraper/__init__.py +3 -0
- parrot/bots/scraper/models.py +122 -0
- parrot/bots/scraper/scraper.py +1173 -0
- parrot/bots/scraper/templates.py +115 -0
- parrot/bots/stores/__init__.py +5 -0
- parrot/bots/stores/local.py +172 -0
- parrot/bots/webdev.py +81 -0
- parrot/cli.py +17 -0
- parrot/clients/__init__.py +16 -0
- parrot/clients/base.py +1491 -0
- parrot/clients/claude.py +1191 -0
- parrot/clients/factory.py +129 -0
- parrot/clients/google.py +4567 -0
- parrot/clients/gpt.py +1975 -0
- parrot/clients/grok.py +432 -0
- parrot/clients/groq.py +986 -0
- parrot/clients/hf.py +582 -0
- parrot/clients/models.py +18 -0
- parrot/conf.py +395 -0
- parrot/embeddings/__init__.py +9 -0
- parrot/embeddings/base.py +157 -0
- parrot/embeddings/google.py +98 -0
- parrot/embeddings/huggingface.py +74 -0
- parrot/embeddings/openai.py +84 -0
- parrot/embeddings/processor.py +88 -0
- parrot/exceptions.c +13868 -0
- parrot/exceptions.cpython-310-x86_64-linux-gnu.so +0 -0
- parrot/exceptions.pxd +22 -0
- parrot/exceptions.pxi +15 -0
- parrot/exceptions.pyx +44 -0
- parrot/generators/__init__.py +29 -0
- parrot/generators/base.py +200 -0
- parrot/generators/html.py +293 -0
- parrot/generators/react.py +205 -0
- parrot/generators/streamlit.py +203 -0
- parrot/generators/template.py +105 -0
- parrot/handlers/__init__.py +4 -0
- parrot/handlers/agent.py +861 -0
- parrot/handlers/agents/__init__.py +1 -0
- parrot/handlers/agents/abstract.py +900 -0
- parrot/handlers/bots.py +338 -0
- parrot/handlers/chat.py +915 -0
- parrot/handlers/creation.sql +192 -0
- parrot/handlers/crew/ARCHITECTURE.md +362 -0
- parrot/handlers/crew/README_BOTMANAGER_PERSISTENCE.md +303 -0
- parrot/handlers/crew/README_REDIS_PERSISTENCE.md +366 -0
- parrot/handlers/crew/__init__.py +0 -0
- parrot/handlers/crew/handler.py +801 -0
- parrot/handlers/crew/models.py +229 -0
- parrot/handlers/crew/redis_persistence.py +523 -0
- parrot/handlers/jobs/__init__.py +10 -0
- parrot/handlers/jobs/job.py +384 -0
- parrot/handlers/jobs/mixin.py +627 -0
- parrot/handlers/jobs/models.py +115 -0
- parrot/handlers/jobs/worker.py +31 -0
- parrot/handlers/models.py +596 -0
- parrot/handlers/o365_auth.py +105 -0
- parrot/handlers/stream.py +337 -0
- parrot/interfaces/__init__.py +6 -0
- parrot/interfaces/aws.py +143 -0
- parrot/interfaces/credentials.py +113 -0
- parrot/interfaces/database.py +27 -0
- parrot/interfaces/google.py +1123 -0
- parrot/interfaces/hierarchy.py +1227 -0
- parrot/interfaces/http.py +651 -0
- parrot/interfaces/images/__init__.py +0 -0
- parrot/interfaces/images/plugins/__init__.py +24 -0
- parrot/interfaces/images/plugins/abstract.py +58 -0
- parrot/interfaces/images/plugins/analisys.py +148 -0
- parrot/interfaces/images/plugins/classify.py +150 -0
- parrot/interfaces/images/plugins/classifybase.py +182 -0
- parrot/interfaces/images/plugins/detect.py +150 -0
- parrot/interfaces/images/plugins/exif.py +1103 -0
- parrot/interfaces/images/plugins/hash.py +52 -0
- parrot/interfaces/images/plugins/vision.py +104 -0
- parrot/interfaces/images/plugins/yolo.py +66 -0
- parrot/interfaces/images/plugins/zerodetect.py +197 -0
- parrot/interfaces/o365.py +978 -0
- parrot/interfaces/onedrive.py +822 -0
- parrot/interfaces/sharepoint.py +1435 -0
- parrot/interfaces/soap.py +257 -0
- parrot/loaders/__init__.py +8 -0
- parrot/loaders/abstract.py +1131 -0
- parrot/loaders/audio.py +199 -0
- parrot/loaders/basepdf.py +53 -0
- parrot/loaders/basevideo.py +1568 -0
- parrot/loaders/csv.py +409 -0
- parrot/loaders/docx.py +116 -0
- parrot/loaders/epubloader.py +316 -0
- parrot/loaders/excel.py +199 -0
- parrot/loaders/factory.py +55 -0
- parrot/loaders/files/__init__.py +0 -0
- parrot/loaders/files/abstract.py +39 -0
- parrot/loaders/files/html.py +26 -0
- parrot/loaders/files/text.py +63 -0
- parrot/loaders/html.py +152 -0
- parrot/loaders/markdown.py +442 -0
- parrot/loaders/pdf.py +373 -0
- parrot/loaders/pdfmark.py +320 -0
- parrot/loaders/pdftables.py +506 -0
- parrot/loaders/ppt.py +476 -0
- parrot/loaders/qa.py +63 -0
- parrot/loaders/splitters/__init__.py +10 -0
- parrot/loaders/splitters/base.py +138 -0
- parrot/loaders/splitters/md.py +228 -0
- parrot/loaders/splitters/token.py +143 -0
- parrot/loaders/txt.py +26 -0
- parrot/loaders/video.py +89 -0
- parrot/loaders/videolocal.py +218 -0
- parrot/loaders/videounderstanding.py +377 -0
- parrot/loaders/vimeo.py +167 -0
- parrot/loaders/web.py +599 -0
- parrot/loaders/youtube.py +504 -0
- parrot/manager/__init__.py +5 -0
- parrot/manager/manager.py +1030 -0
- parrot/mcp/__init__.py +28 -0
- parrot/mcp/adapter.py +105 -0
- parrot/mcp/cli.py +174 -0
- parrot/mcp/client.py +119 -0
- parrot/mcp/config.py +75 -0
- parrot/mcp/integration.py +842 -0
- parrot/mcp/oauth.py +933 -0
- parrot/mcp/server.py +225 -0
- parrot/mcp/transports/__init__.py +3 -0
- parrot/mcp/transports/base.py +279 -0
- parrot/mcp/transports/grpc_session.py +163 -0
- parrot/mcp/transports/http.py +312 -0
- parrot/mcp/transports/mcp.proto +108 -0
- parrot/mcp/transports/quic.py +1082 -0
- parrot/mcp/transports/sse.py +330 -0
- parrot/mcp/transports/stdio.py +309 -0
- parrot/mcp/transports/unix.py +395 -0
- parrot/mcp/transports/websocket.py +547 -0
- parrot/memory/__init__.py +16 -0
- parrot/memory/abstract.py +209 -0
- parrot/memory/agent.py +32 -0
- parrot/memory/cache.py +175 -0
- parrot/memory/core.py +555 -0
- parrot/memory/file.py +153 -0
- parrot/memory/mem.py +131 -0
- parrot/memory/redis.py +613 -0
- parrot/models/__init__.py +46 -0
- parrot/models/basic.py +118 -0
- parrot/models/compliance.py +208 -0
- parrot/models/crew.py +395 -0
- parrot/models/detections.py +654 -0
- parrot/models/generation.py +85 -0
- parrot/models/google.py +223 -0
- parrot/models/groq.py +23 -0
- parrot/models/openai.py +30 -0
- parrot/models/outputs.py +285 -0
- parrot/models/responses.py +938 -0
- parrot/notifications/__init__.py +743 -0
- parrot/openapi/__init__.py +3 -0
- parrot/openapi/components.yaml +641 -0
- parrot/openapi/config.py +322 -0
- parrot/outputs/__init__.py +32 -0
- parrot/outputs/formats/__init__.py +108 -0
- parrot/outputs/formats/altair.py +359 -0
- parrot/outputs/formats/application.py +122 -0
- parrot/outputs/formats/base.py +351 -0
- parrot/outputs/formats/bokeh.py +356 -0
- parrot/outputs/formats/card.py +424 -0
- parrot/outputs/formats/chart.py +436 -0
- parrot/outputs/formats/d3.py +255 -0
- parrot/outputs/formats/echarts.py +310 -0
- parrot/outputs/formats/generators/__init__.py +0 -0
- parrot/outputs/formats/generators/abstract.py +61 -0
- parrot/outputs/formats/generators/panel.py +145 -0
- parrot/outputs/formats/generators/streamlit.py +86 -0
- parrot/outputs/formats/generators/terminal.py +63 -0
- parrot/outputs/formats/holoviews.py +310 -0
- parrot/outputs/formats/html.py +147 -0
- parrot/outputs/formats/jinja2.py +46 -0
- parrot/outputs/formats/json.py +87 -0
- parrot/outputs/formats/map.py +933 -0
- parrot/outputs/formats/markdown.py +172 -0
- parrot/outputs/formats/matplotlib.py +237 -0
- parrot/outputs/formats/mixins/__init__.py +0 -0
- parrot/outputs/formats/mixins/emaps.py +855 -0
- parrot/outputs/formats/plotly.py +341 -0
- parrot/outputs/formats/seaborn.py +310 -0
- parrot/outputs/formats/table.py +397 -0
- parrot/outputs/formats/template_report.py +138 -0
- parrot/outputs/formats/yaml.py +125 -0
- parrot/outputs/formatter.py +152 -0
- parrot/outputs/templates/__init__.py +95 -0
- parrot/pipelines/__init__.py +0 -0
- parrot/pipelines/abstract.py +210 -0
- parrot/pipelines/detector.py +124 -0
- parrot/pipelines/models.py +90 -0
- parrot/pipelines/planogram.py +3002 -0
- parrot/pipelines/table.sql +97 -0
- parrot/plugins/__init__.py +106 -0
- parrot/plugins/importer.py +80 -0
- parrot/py.typed +0 -0
- parrot/registry/__init__.py +18 -0
- parrot/registry/registry.py +594 -0
- parrot/scheduler/__init__.py +1189 -0
- parrot/scheduler/models.py +60 -0
- parrot/security/__init__.py +16 -0
- parrot/security/prompt_injection.py +268 -0
- parrot/security/security_events.sql +25 -0
- parrot/services/__init__.py +1 -0
- parrot/services/mcp/__init__.py +8 -0
- parrot/services/mcp/config.py +13 -0
- parrot/services/mcp/server.py +295 -0
- parrot/services/o365_remote_auth.py +235 -0
- parrot/stores/__init__.py +7 -0
- parrot/stores/abstract.py +352 -0
- parrot/stores/arango.py +1090 -0
- parrot/stores/bigquery.py +1377 -0
- parrot/stores/cache.py +106 -0
- parrot/stores/empty.py +10 -0
- parrot/stores/faiss_store.py +1157 -0
- parrot/stores/kb/__init__.py +9 -0
- parrot/stores/kb/abstract.py +68 -0
- parrot/stores/kb/cache.py +165 -0
- parrot/stores/kb/doc.py +325 -0
- parrot/stores/kb/hierarchy.py +346 -0
- parrot/stores/kb/local.py +457 -0
- parrot/stores/kb/prompt.py +28 -0
- parrot/stores/kb/redis.py +659 -0
- parrot/stores/kb/store.py +115 -0
- parrot/stores/kb/user.py +374 -0
- parrot/stores/models.py +59 -0
- parrot/stores/pgvector.py +3 -0
- parrot/stores/postgres.py +2853 -0
- parrot/stores/utils/__init__.py +0 -0
- parrot/stores/utils/chunking.py +197 -0
- parrot/telemetry/__init__.py +3 -0
- parrot/telemetry/mixin.py +111 -0
- parrot/template/__init__.py +3 -0
- parrot/template/engine.py +259 -0
- parrot/tools/__init__.py +23 -0
- parrot/tools/abstract.py +644 -0
- parrot/tools/agent.py +363 -0
- parrot/tools/arangodbsearch.py +537 -0
- parrot/tools/arxiv_tool.py +188 -0
- parrot/tools/calculator/__init__.py +3 -0
- parrot/tools/calculator/operations/__init__.py +38 -0
- parrot/tools/calculator/operations/calculus.py +80 -0
- parrot/tools/calculator/operations/statistics.py +76 -0
- parrot/tools/calculator/tool.py +150 -0
- parrot/tools/cloudwatch.py +988 -0
- parrot/tools/codeinterpreter/__init__.py +127 -0
- parrot/tools/codeinterpreter/executor.py +371 -0
- parrot/tools/codeinterpreter/internals.py +473 -0
- parrot/tools/codeinterpreter/models.py +643 -0
- parrot/tools/codeinterpreter/prompts.py +224 -0
- parrot/tools/codeinterpreter/tool.py +664 -0
- parrot/tools/company_info/__init__.py +6 -0
- parrot/tools/company_info/tool.py +1138 -0
- parrot/tools/correlationanalysis.py +437 -0
- parrot/tools/database/abstract.py +286 -0
- parrot/tools/database/bq.py +115 -0
- parrot/tools/database/cache.py +284 -0
- parrot/tools/database/models.py +95 -0
- parrot/tools/database/pg.py +343 -0
- parrot/tools/databasequery.py +1159 -0
- parrot/tools/db.py +1800 -0
- parrot/tools/ddgo.py +370 -0
- parrot/tools/decorators.py +271 -0
- parrot/tools/dftohtml.py +282 -0
- parrot/tools/document.py +549 -0
- parrot/tools/ecs.py +819 -0
- parrot/tools/edareport.py +368 -0
- parrot/tools/elasticsearch.py +1049 -0
- parrot/tools/employees.py +462 -0
- parrot/tools/epson/__init__.py +96 -0
- parrot/tools/excel.py +683 -0
- parrot/tools/file/__init__.py +13 -0
- parrot/tools/file/abstract.py +76 -0
- parrot/tools/file/gcs.py +378 -0
- parrot/tools/file/local.py +284 -0
- parrot/tools/file/s3.py +511 -0
- parrot/tools/file/tmp.py +309 -0
- parrot/tools/file/tool.py +501 -0
- parrot/tools/file_reader.py +129 -0
- parrot/tools/flowtask/__init__.py +19 -0
- parrot/tools/flowtask/tool.py +761 -0
- parrot/tools/gittoolkit.py +508 -0
- parrot/tools/google/__init__.py +18 -0
- parrot/tools/google/base.py +169 -0
- parrot/tools/google/tools.py +1251 -0
- parrot/tools/googlelocation.py +5 -0
- parrot/tools/googleroutes.py +5 -0
- parrot/tools/googlesearch.py +5 -0
- parrot/tools/googlesitesearch.py +5 -0
- parrot/tools/googlevoice.py +2 -0
- parrot/tools/gvoice.py +695 -0
- parrot/tools/ibisworld/README.md +225 -0
- parrot/tools/ibisworld/__init__.py +11 -0
- parrot/tools/ibisworld/tool.py +366 -0
- parrot/tools/jiratoolkit.py +1718 -0
- parrot/tools/manager.py +1098 -0
- parrot/tools/math.py +152 -0
- parrot/tools/metadata.py +476 -0
- parrot/tools/msteams.py +1621 -0
- parrot/tools/msword.py +635 -0
- parrot/tools/multidb.py +580 -0
- parrot/tools/multistoresearch.py +369 -0
- parrot/tools/networkninja.py +167 -0
- parrot/tools/nextstop/__init__.py +4 -0
- parrot/tools/nextstop/base.py +286 -0
- parrot/tools/nextstop/employee.py +733 -0
- parrot/tools/nextstop/store.py +462 -0
- parrot/tools/notification.py +435 -0
- parrot/tools/o365/__init__.py +42 -0
- parrot/tools/o365/base.py +295 -0
- parrot/tools/o365/bundle.py +522 -0
- parrot/tools/o365/events.py +554 -0
- parrot/tools/o365/mail.py +992 -0
- parrot/tools/o365/onedrive.py +497 -0
- parrot/tools/o365/sharepoint.py +641 -0
- parrot/tools/openapi_toolkit.py +904 -0
- parrot/tools/openweather.py +527 -0
- parrot/tools/pdfprint.py +1001 -0
- parrot/tools/powerbi.py +518 -0
- parrot/tools/powerpoint.py +1113 -0
- parrot/tools/pricestool.py +146 -0
- parrot/tools/products/__init__.py +246 -0
- parrot/tools/prophet_tool.py +171 -0
- parrot/tools/pythonpandas.py +630 -0
- parrot/tools/pythonrepl.py +910 -0
- parrot/tools/qsource.py +436 -0
- parrot/tools/querytoolkit.py +395 -0
- parrot/tools/quickeda.py +827 -0
- parrot/tools/resttool.py +553 -0
- parrot/tools/retail/__init__.py +0 -0
- parrot/tools/retail/bby.py +528 -0
- parrot/tools/sandboxtool.py +703 -0
- parrot/tools/sassie/__init__.py +352 -0
- parrot/tools/scraping/__init__.py +7 -0
- parrot/tools/scraping/docs/select.md +466 -0
- parrot/tools/scraping/documentation.md +1278 -0
- parrot/tools/scraping/driver.py +436 -0
- parrot/tools/scraping/models.py +576 -0
- parrot/tools/scraping/options.py +85 -0
- parrot/tools/scraping/orchestrator.py +517 -0
- parrot/tools/scraping/readme.md +740 -0
- parrot/tools/scraping/tool.py +3115 -0
- parrot/tools/seasonaldetection.py +642 -0
- parrot/tools/shell_tool/__init__.py +5 -0
- parrot/tools/shell_tool/actions.py +408 -0
- parrot/tools/shell_tool/engine.py +155 -0
- parrot/tools/shell_tool/models.py +322 -0
- parrot/tools/shell_tool/tool.py +442 -0
- parrot/tools/site_search.py +214 -0
- parrot/tools/textfile.py +418 -0
- parrot/tools/think.py +378 -0
- parrot/tools/toolkit.py +298 -0
- parrot/tools/webapp_tool.py +187 -0
- parrot/tools/whatif.py +1279 -0
- parrot/tools/workday/MULTI_WSDL_EXAMPLE.md +249 -0
- parrot/tools/workday/__init__.py +6 -0
- parrot/tools/workday/models.py +1389 -0
- parrot/tools/workday/tool.py +1293 -0
- parrot/tools/yfinance_tool.py +306 -0
- parrot/tools/zipcode.py +217 -0
- parrot/utils/__init__.py +2 -0
- parrot/utils/helpers.py +73 -0
- parrot/utils/parsers/__init__.py +5 -0
- parrot/utils/parsers/toml.c +12078 -0
- parrot/utils/parsers/toml.cpython-310-x86_64-linux-gnu.so +0 -0
- parrot/utils/parsers/toml.pyx +21 -0
- parrot/utils/toml.py +11 -0
- parrot/utils/types.cpp +20936 -0
- parrot/utils/types.cpython-310-x86_64-linux-gnu.so +0 -0
- parrot/utils/types.pyx +213 -0
- parrot/utils/uv.py +11 -0
- parrot/version.py +10 -0
- parrot/yaml-rs/Cargo.lock +350 -0
- parrot/yaml-rs/Cargo.toml +19 -0
- parrot/yaml-rs/pyproject.toml +19 -0
- parrot/yaml-rs/python/yaml_rs/__init__.py +81 -0
- parrot/yaml-rs/src/lib.rs +222 -0
- requirements/docker-compose.yml +24 -0
- requirements/requirements-dev.txt +21 -0
|
@@ -0,0 +1,988 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AWS CloudWatch Tool for AI-Parrot
|
|
3
|
+
Enables AI agents to query CloudWatch logs and metrics
|
|
4
|
+
"""
|
|
5
|
+
from typing import Optional, List, Dict, Any, Literal
|
|
6
|
+
from datetime import datetime, timedelta, timezone
|
|
7
|
+
from enum import Enum
|
|
8
|
+
import contextlib
|
|
9
|
+
import re
|
|
10
|
+
import asyncio
|
|
11
|
+
from pydantic import Field, field_validator
|
|
12
|
+
from botocore.exceptions import ClientError
|
|
13
|
+
from ..interfaces.aws import AWSInterface
|
|
14
|
+
from ..conf import AWS_DEFAULT_CLOUDWATCH_LOG_GROUP
|
|
15
|
+
from .abstract import AbstractTool, AbstractToolArgsSchema, ToolResult
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CloudWatchOperation(str, Enum):
|
|
19
|
+
"""Available CloudWatch operations"""
|
|
20
|
+
QUERY_LOGS = "query_logs"
|
|
21
|
+
GET_METRICS = "get_metrics"
|
|
22
|
+
LIST_LOG_GROUPS = "list_log_groups"
|
|
23
|
+
LIST_LOG_STREAMS = "list_log_streams"
|
|
24
|
+
GET_LOG_EVENTS = "get_log_events"
|
|
25
|
+
PUT_METRIC_DATA = "put_metric_data"
|
|
26
|
+
DESCRIBE_ALARMS = "describe_alarms"
|
|
27
|
+
LOG_SUMMARY = "log_summary"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class CloudWatchToolArgs(AbstractToolArgsSchema):
|
|
31
|
+
"""Arguments schema for CloudWatch operations"""
|
|
32
|
+
|
|
33
|
+
operation: CloudWatchOperation = Field(
|
|
34
|
+
...,
|
|
35
|
+
description=(
|
|
36
|
+
"CloudWatch operation to perform:\n"
|
|
37
|
+
"- 'query_logs': Run CloudWatch Logs Insights query\n"
|
|
38
|
+
"- 'get_metrics': Get metric statistics\n"
|
|
39
|
+
"- 'list_log_groups': List available log groups\n"
|
|
40
|
+
"- 'list_log_streams': List log streams in a log group\n"
|
|
41
|
+
"- 'get_log_events': Get recent log events from a stream\n"
|
|
42
|
+
"- 'put_metric_data': Publish custom metric data\n"
|
|
43
|
+
"- 'describe_alarms': List CloudWatch alarms\n"
|
|
44
|
+
"- 'log_summary': Get summarized log events with parsed facility, time, and truncated messages"
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Log query parameters
|
|
49
|
+
log_group_name: Optional[str] = Field(
|
|
50
|
+
None,
|
|
51
|
+
description="CloudWatch log group name (e.g., '/aws/lambda/my-function')"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
query_string: Optional[str] = Field(
|
|
55
|
+
None,
|
|
56
|
+
description=(
|
|
57
|
+
"CloudWatch Logs Insights query. Examples:\n"
|
|
58
|
+
"- 'fields @timestamp, @message | sort @timestamp desc | limit 20'\n"
|
|
59
|
+
"- 'filter @message like /ERROR/ | stats count() by bin(5m)'"
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
log_stream_name: Optional[str] = Field(
|
|
64
|
+
None,
|
|
65
|
+
description="Specific log stream name within the log group"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Time range parameters
|
|
69
|
+
start_time: Optional[str] = Field(
|
|
70
|
+
None,
|
|
71
|
+
description=(
|
|
72
|
+
"Start time for query (ISO format or relative like '-1h', '-30m', '-7d'). "
|
|
73
|
+
"Examples: '2024-01-01T00:00:00', '-1h', '-24h'"
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
end_time: Optional[str] = Field(
|
|
78
|
+
None,
|
|
79
|
+
description="End time for query (ISO format or 'now'). Default: now"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Metric parameters
|
|
83
|
+
namespace: Optional[str] = Field(
|
|
84
|
+
None,
|
|
85
|
+
description="CloudWatch metric namespace (e.g., 'AWS/Lambda', 'AWS/EC2', custom namespace)"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
metric_name: Optional[str] = Field(
|
|
89
|
+
None,
|
|
90
|
+
description="Metric name to query (e.g., 'Duration', 'Invocations', 'CPUUtilization')"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
dimensions: Optional[List[Dict[str, str]]] = Field(
|
|
94
|
+
None,
|
|
95
|
+
description=(
|
|
96
|
+
"Metric dimensions as list of {Name: ..., Value: ...} dicts. "
|
|
97
|
+
"Example: [{'Name': 'FunctionName', 'Value': 'my-function'}]"
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
statistic: Optional[Literal["Average", "Sum", "Minimum", "Maximum", "SampleCount"]] = Field(
|
|
102
|
+
"Average",
|
|
103
|
+
description="Statistic to retrieve for metrics"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
period: Optional[int] = Field(
|
|
107
|
+
60,
|
|
108
|
+
description="Period in seconds for metric data points (60, 300, 3600, etc.)"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# General parameters
|
|
112
|
+
limit: Optional[int] = Field(
|
|
113
|
+
50,
|
|
114
|
+
description="Maximum number of results to return"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
pattern: Optional[str] = Field(
|
|
118
|
+
None,
|
|
119
|
+
description="Filter pattern for log groups/streams (supports wildcards)"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Custom metric publishing
|
|
123
|
+
metric_value: Optional[float] = Field(
|
|
124
|
+
None,
|
|
125
|
+
description="Metric value to publish (for put_metric_data operation)"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
unit: Optional[str] = Field(
|
|
129
|
+
None,
|
|
130
|
+
description="Metric unit (e.g., 'Seconds', 'Count', 'Bytes')"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
max_message_length: Optional[int] = Field(
|
|
134
|
+
500,
|
|
135
|
+
description="Maximum length for log messages in log_summary operation (default: 500 characters)"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
@field_validator('start_time', mode='before')
|
|
139
|
+
@classmethod
|
|
140
|
+
def parse_time(cls, v):
|
|
141
|
+
"""Parse time string to timestamp"""
|
|
142
|
+
if v is None or v == 'now':
|
|
143
|
+
return v
|
|
144
|
+
return v # Will be parsed in the tool
|
|
145
|
+
|
|
146
|
+
@field_validator('end_time', mode='before')
|
|
147
|
+
@classmethod
|
|
148
|
+
def parse_end_time(cls, v):
|
|
149
|
+
"""Parse end time string to timestamp"""
|
|
150
|
+
if v is None or v == 'now':
|
|
151
|
+
return v
|
|
152
|
+
return v # Will be parsed in the tool
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class CloudWatchTool(AbstractTool):
|
|
156
|
+
"""
|
|
157
|
+
Tool for querying AWS CloudWatch logs and metrics.
|
|
158
|
+
|
|
159
|
+
Capabilities:
|
|
160
|
+
- Query logs using CloudWatch Logs Insights
|
|
161
|
+
- Retrieve metric statistics and timeseries data
|
|
162
|
+
- List and explore log groups and streams
|
|
163
|
+
- Get recent log events
|
|
164
|
+
- Get summarized log events with parsed facility and truncated messages
|
|
165
|
+
- Publish custom metrics
|
|
166
|
+
- Check alarm status
|
|
167
|
+
|
|
168
|
+
Example Usage:
|
|
169
|
+
# Query logs for errors in last hour
|
|
170
|
+
{
|
|
171
|
+
"operation": "query_logs",
|
|
172
|
+
"log_group_name": "/aws/lambda/my-function",
|
|
173
|
+
"query_string": "fields @timestamp, @message | filter @message like /ERROR/ | limit 50",
|
|
174
|
+
"start_time": "-1h"
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
# Get Lambda duration metrics
|
|
178
|
+
{
|
|
179
|
+
"operation": "get_metrics",
|
|
180
|
+
"namespace": "AWS/Lambda",
|
|
181
|
+
"metric_name": "Duration",
|
|
182
|
+
"dimensions": [{"Name": "FunctionName", "Value": "my-function"}],
|
|
183
|
+
"statistic": "Average",
|
|
184
|
+
"start_time": "-24h"
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
# Get summarized logs with truncated messages
|
|
188
|
+
{
|
|
189
|
+
"operation": "log_summary",
|
|
190
|
+
"log_group_name": "/aws/lambda/my-function",
|
|
191
|
+
"limit": 50,
|
|
192
|
+
"max_message_length": 200,
|
|
193
|
+
"start_time": "-1h"
|
|
194
|
+
}
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
name: str = "cloudwatch"
|
|
198
|
+
description: str = (
|
|
199
|
+
"Query AWS CloudWatch logs and metrics. "
|
|
200
|
+
"Supports log queries, metric retrieval, and monitoring operations."
|
|
201
|
+
)
|
|
202
|
+
args_schema: type[AbstractToolArgsSchema] = CloudWatchToolArgs
|
|
203
|
+
|
|
204
|
+
def __init__(
|
|
205
|
+
self,
|
|
206
|
+
aws_id: str = 'cloudwatch',
|
|
207
|
+
region_name: Optional[str] = None,
|
|
208
|
+
default_log_group: Optional[str] = None,
|
|
209
|
+
max_query_wait: int = 30,
|
|
210
|
+
**kwargs
|
|
211
|
+
):
|
|
212
|
+
"""
|
|
213
|
+
Initialize CloudWatch tool.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
aws_id: AWS credentials identifier
|
|
217
|
+
region_name: AWS region
|
|
218
|
+
default_log_group: Default log group for queries
|
|
219
|
+
max_query_wait: Maximum seconds to wait for Insights query completion
|
|
220
|
+
**kwargs: Additional AWS interface parameters
|
|
221
|
+
"""
|
|
222
|
+
super().__init__()
|
|
223
|
+
self.aws = AWSInterface(
|
|
224
|
+
aws_id=aws_id,
|
|
225
|
+
region_name=region_name,
|
|
226
|
+
**kwargs
|
|
227
|
+
)
|
|
228
|
+
self.default_log_group = default_log_group or AWS_DEFAULT_CLOUDWATCH_LOG_GROUP
|
|
229
|
+
self.max_query_wait = max_query_wait
|
|
230
|
+
|
|
231
|
+
def _parse_relative_time(self, time_str: str) -> datetime:
|
|
232
|
+
"""
|
|
233
|
+
Parse relative time strings like '-1h', '-30m', '-7d'.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
time_str: Time string (e.g., '-1h', '-24h', '-7d')
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
datetime object
|
|
240
|
+
"""
|
|
241
|
+
if time_str == 'now' or time_str is None:
|
|
242
|
+
return datetime.now(timezone.utc)
|
|
243
|
+
|
|
244
|
+
# Try parsing as ISO format first
|
|
245
|
+
with contextlib.suppress(ValueError, AttributeError):
|
|
246
|
+
return datetime.fromisoformat(time_str.replace('Z', '+00:00'))
|
|
247
|
+
|
|
248
|
+
# Parse relative time
|
|
249
|
+
if time_str.startswith('-'):
|
|
250
|
+
time_str = time_str[1:]
|
|
251
|
+
|
|
252
|
+
# Extract number and unit
|
|
253
|
+
match = re.match(r'(\d+)([smhd])', time_str)
|
|
254
|
+
if not match:
|
|
255
|
+
raise ValueError(f"Invalid time format: {time_str}")
|
|
256
|
+
|
|
257
|
+
amount, unit = match.groups()
|
|
258
|
+
amount = int(amount)
|
|
259
|
+
|
|
260
|
+
if unit == 's':
|
|
261
|
+
delta = timedelta(seconds=amount)
|
|
262
|
+
elif unit == 'm':
|
|
263
|
+
delta = timedelta(minutes=amount)
|
|
264
|
+
elif unit == 'h':
|
|
265
|
+
delta = timedelta(hours=amount)
|
|
266
|
+
elif unit == 'd':
|
|
267
|
+
delta = timedelta(days=amount)
|
|
268
|
+
else:
|
|
269
|
+
raise ValueError(f"Unknown time unit: {unit}")
|
|
270
|
+
|
|
271
|
+
return datetime.now(timezone.utc) - delta
|
|
272
|
+
|
|
273
|
+
raise ValueError(f"Invalid time format: {time_str}")
|
|
274
|
+
|
|
275
|
+
def _parse_log_message(self, message: str, timestamp: str) -> Dict[str, Any]:
|
|
276
|
+
"""
|
|
277
|
+
Parse log message to extract facility, time, and message.
|
|
278
|
+
|
|
279
|
+
Supports multiple log formats:
|
|
280
|
+
- Rails/Ruby logger: I, [timestamp#pid] LEVEL -- : message
|
|
281
|
+
- Standard syslog: LEVEL: message
|
|
282
|
+
- Plain messages
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
message: Raw log message
|
|
286
|
+
timestamp: ISO timestamp of the log event
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
Dict with 'facility', 'timestamp', and 'message' keys
|
|
290
|
+
"""
|
|
291
|
+
# Default values
|
|
292
|
+
facility = "INFO"
|
|
293
|
+
parsed_message = message
|
|
294
|
+
|
|
295
|
+
# Try to parse Rails/Ruby logger format
|
|
296
|
+
# Example: I, [2025-12-03T00:17:10.802698#1-142140] INFO -- : Running job...
|
|
297
|
+
rails_pattern = r'^([A-Z]),\s*\[([^\]]+)\]\s*(\w+)\s*--\s*:\s*(.+)$'
|
|
298
|
+
match = re.match(rails_pattern, message)
|
|
299
|
+
if match:
|
|
300
|
+
level_code, log_timestamp, level_name, msg = match.groups()
|
|
301
|
+
facility = level_name
|
|
302
|
+
parsed_message = msg.strip()
|
|
303
|
+
return {
|
|
304
|
+
'facility': facility,
|
|
305
|
+
'timestamp': timestamp,
|
|
306
|
+
'message': parsed_message
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
# Try to parse standard log format with level prefix
|
|
310
|
+
# Example: ERROR: Something went wrong
|
|
311
|
+
# Example: [ERROR] Something went wrong
|
|
312
|
+
# Example: 2025-12-03 ERROR: Something went wrong
|
|
313
|
+
level_patterns = [
|
|
314
|
+
r'^\[?(DEBUG|INFO|WARN|WARNING|ERROR|FATAL|CRITICAL)\]?\s*:?\s*(.+)$',
|
|
315
|
+
r'^\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}[^\s]*\s+\[?(DEBUG|INFO|WARN|WARNING|ERROR|FATAL|CRITICAL)\]?\s*:?\s*(.+)$'
|
|
316
|
+
]
|
|
317
|
+
|
|
318
|
+
for pattern in level_patterns:
|
|
319
|
+
match = re.match(pattern, message, re.IGNORECASE)
|
|
320
|
+
if match:
|
|
321
|
+
groups = match.groups()
|
|
322
|
+
if len(groups) == 2:
|
|
323
|
+
facility = groups[0].upper()
|
|
324
|
+
parsed_message = groups[1].strip()
|
|
325
|
+
else: # Has timestamp in message
|
|
326
|
+
facility = groups[1].upper()
|
|
327
|
+
parsed_message = groups[2].strip()
|
|
328
|
+
break
|
|
329
|
+
|
|
330
|
+
# Clean up common log artifacts
|
|
331
|
+
parsed_message = parsed_message.replace('\\n', ' ').replace('\\t', ' ')
|
|
332
|
+
parsed_message = re.sub(r'\s+', ' ', parsed_message).strip()
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
'facility': facility,
|
|
336
|
+
'timestamp': timestamp,
|
|
337
|
+
'message': parsed_message
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
def _truncate_message(self, message: str, max_length: int) -> str:
|
|
341
|
+
"""
|
|
342
|
+
Truncate message to max_length, adding ellipsis if truncated.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
message: Message to truncate
|
|
346
|
+
max_length: Maximum length
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
Truncated message
|
|
350
|
+
"""
|
|
351
|
+
if len(message) <= max_length:
|
|
352
|
+
return message
|
|
353
|
+
return message[:max_length - 3] + "..."
|
|
354
|
+
|
|
355
|
+
async def _log_summary(
|
|
356
|
+
self,
|
|
357
|
+
log_group_name: str,
|
|
358
|
+
log_stream_name: Optional[str] = None,
|
|
359
|
+
start_time: Optional[datetime] = None,
|
|
360
|
+
limit: int = 100,
|
|
361
|
+
max_message_length: int = 500
|
|
362
|
+
) -> List[Dict[str, Any]]:
|
|
363
|
+
"""
|
|
364
|
+
Get summarized log events with parsed facility, time, and truncated messages.
|
|
365
|
+
|
|
366
|
+
Args:
|
|
367
|
+
log_group_name: CloudWatch log group name
|
|
368
|
+
log_stream_name: Optional specific log stream
|
|
369
|
+
start_time: Optional start time for filtering
|
|
370
|
+
limit: Maximum number of log events
|
|
371
|
+
max_message_length: Maximum length for messages
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
List of summarized log events
|
|
375
|
+
"""
|
|
376
|
+
# Get log events
|
|
377
|
+
if log_stream_name:
|
|
378
|
+
events = await self._get_log_events(
|
|
379
|
+
log_group_name=log_group_name,
|
|
380
|
+
log_stream_name=log_stream_name,
|
|
381
|
+
start_time=start_time,
|
|
382
|
+
limit=limit
|
|
383
|
+
)
|
|
384
|
+
else:
|
|
385
|
+
# If no stream specified, get events from the most recent stream
|
|
386
|
+
streams = await self._list_log_streams(
|
|
387
|
+
log_group_name=log_group_name,
|
|
388
|
+
limit=1
|
|
389
|
+
)
|
|
390
|
+
if not streams:
|
|
391
|
+
return []
|
|
392
|
+
|
|
393
|
+
events = await self._get_log_events(
|
|
394
|
+
log_group_name=log_group_name,
|
|
395
|
+
log_stream_name=streams[0]['name'],
|
|
396
|
+
start_time=start_time,
|
|
397
|
+
limit=limit
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
# Parse and summarize each event
|
|
401
|
+
summarized_events = []
|
|
402
|
+
for event in events:
|
|
403
|
+
parsed = self._parse_log_message(
|
|
404
|
+
event['message'],
|
|
405
|
+
event['timestamp']
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
summarized_events.append({
|
|
409
|
+
'timestamp': parsed['timestamp'],
|
|
410
|
+
'facility': parsed['facility'],
|
|
411
|
+
'message': self._truncate_message(
|
|
412
|
+
parsed['message'],
|
|
413
|
+
max_message_length
|
|
414
|
+
)
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
return summarized_events
|
|
418
|
+
|
|
419
|
+
async def _query_logs_insights(
|
|
420
|
+
self,
|
|
421
|
+
log_group_name: str,
|
|
422
|
+
query_string: str,
|
|
423
|
+
start_time: datetime,
|
|
424
|
+
end_time: datetime,
|
|
425
|
+
limit: int
|
|
426
|
+
) -> List[Dict[str, Any]]:
|
|
427
|
+
"""Execute CloudWatch Logs Insights query"""
|
|
428
|
+
|
|
429
|
+
async with self.aws.client('logs') as logs:
|
|
430
|
+
# Start query
|
|
431
|
+
response = await logs.start_query(
|
|
432
|
+
logGroupName=log_group_name,
|
|
433
|
+
startTime=int(start_time.timestamp()),
|
|
434
|
+
endTime=int(end_time.timestamp()),
|
|
435
|
+
queryString=query_string,
|
|
436
|
+
limit=limit
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
query_id = response['queryId']
|
|
440
|
+
|
|
441
|
+
# Poll for results
|
|
442
|
+
for _ in range(self.max_query_wait):
|
|
443
|
+
result = await logs.get_query_results(queryId=query_id)
|
|
444
|
+
|
|
445
|
+
status = result['status']
|
|
446
|
+
|
|
447
|
+
if status == 'Complete':
|
|
448
|
+
# Parse results
|
|
449
|
+
parsed_results = []
|
|
450
|
+
for record in result.get('results', []):
|
|
451
|
+
parsed_record = {field['field']: field['value'] for field in record}
|
|
452
|
+
parsed_results.append(parsed_record)
|
|
453
|
+
|
|
454
|
+
return parsed_results
|
|
455
|
+
|
|
456
|
+
elif status in ['Failed', 'Cancelled']:
|
|
457
|
+
raise RuntimeError(f"Query failed with status: {status}")
|
|
458
|
+
|
|
459
|
+
await asyncio.sleep(1)
|
|
460
|
+
|
|
461
|
+
raise TimeoutError(f"Query did not complete within {self.max_query_wait} seconds")
|
|
462
|
+
|
|
463
|
+
async def _get_metrics(
|
|
464
|
+
self,
|
|
465
|
+
namespace: str,
|
|
466
|
+
metric_name: str,
|
|
467
|
+
dimensions: List[Dict[str, str]],
|
|
468
|
+
statistic: str,
|
|
469
|
+
start_time: datetime,
|
|
470
|
+
end_time: datetime,
|
|
471
|
+
period: int
|
|
472
|
+
) -> Dict[str, Any]:
|
|
473
|
+
"""Get metric statistics"""
|
|
474
|
+
|
|
475
|
+
async with self.aws.client('cloudwatch') as cloudwatch:
|
|
476
|
+
# Convert dimensions to proper format
|
|
477
|
+
dims = [
|
|
478
|
+
{'Name': d['Name'], 'Value': d['Value']}
|
|
479
|
+
for d in (dimensions or [])
|
|
480
|
+
]
|
|
481
|
+
|
|
482
|
+
response = await cloudwatch.get_metric_statistics(
|
|
483
|
+
Namespace=namespace,
|
|
484
|
+
MetricName=metric_name,
|
|
485
|
+
Dimensions=dims,
|
|
486
|
+
StartTime=start_time,
|
|
487
|
+
EndTime=end_time,
|
|
488
|
+
Period=period,
|
|
489
|
+
Statistics=[statistic]
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
# Sort datapoints by timestamp
|
|
493
|
+
datapoints = sorted(
|
|
494
|
+
response.get('Datapoints', []),
|
|
495
|
+
key=lambda x: x['Timestamp']
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
return {
|
|
499
|
+
'label': response.get('Label', metric_name),
|
|
500
|
+
'datapoints': [
|
|
501
|
+
{
|
|
502
|
+
'timestamp': dp['Timestamp'].isoformat(),
|
|
503
|
+
'value': dp.get(statistic),
|
|
504
|
+
'unit': dp.get('Unit')
|
|
505
|
+
}
|
|
506
|
+
for dp in datapoints
|
|
507
|
+
]
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
async def _list_log_groups(
|
|
511
|
+
self,
|
|
512
|
+
pattern: Optional[str] = None,
|
|
513
|
+
limit: int = 50
|
|
514
|
+
) -> List[Dict[str, Any]]:
|
|
515
|
+
"""List CloudWatch log groups"""
|
|
516
|
+
|
|
517
|
+
async with self.aws.client('logs') as logs:
|
|
518
|
+
params: Dict[str, Any] = {}
|
|
519
|
+
if pattern:
|
|
520
|
+
params['logGroupNamePrefix'] = pattern
|
|
521
|
+
|
|
522
|
+
log_groups: List[Dict[str, Any]] = []
|
|
523
|
+
paginator = logs.get_paginator('describe_log_groups')
|
|
524
|
+
|
|
525
|
+
async for page in paginator.paginate(**params):
|
|
526
|
+
for lg in page.get('logGroups', []):
|
|
527
|
+
log_groups.append({
|
|
528
|
+
'name': lg['logGroupName'],
|
|
529
|
+
'creation_time': datetime.fromtimestamp(
|
|
530
|
+
lg['creationTime'] / 1000
|
|
531
|
+
).isoformat(),
|
|
532
|
+
'stored_bytes': lg.get('storedBytes', 0),
|
|
533
|
+
'retention_days': lg.get('retentionInDays')
|
|
534
|
+
})
|
|
535
|
+
|
|
536
|
+
if len(log_groups) >= limit:
|
|
537
|
+
break
|
|
538
|
+
|
|
539
|
+
return log_groups[:limit]
|
|
540
|
+
|
|
541
|
+
async def _list_log_streams(
|
|
542
|
+
self,
|
|
543
|
+
log_group_name: str,
|
|
544
|
+
limit: int = 50
|
|
545
|
+
) -> List[Dict[str, Any]]:
|
|
546
|
+
"""List log streams in a log group"""
|
|
547
|
+
|
|
548
|
+
async with self.aws.client('logs') as logs:
|
|
549
|
+
response = await logs.describe_log_streams(
|
|
550
|
+
logGroupName=log_group_name,
|
|
551
|
+
orderBy='LastEventTime',
|
|
552
|
+
descending=True,
|
|
553
|
+
limit=limit
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
return [
|
|
557
|
+
{
|
|
558
|
+
'name': ls['logStreamName'],
|
|
559
|
+
'creation_time': datetime.fromtimestamp(
|
|
560
|
+
ls['creationTime'] / 1000
|
|
561
|
+
).isoformat(),
|
|
562
|
+
'last_event_time': datetime.fromtimestamp(
|
|
563
|
+
ls.get('lastEventTimestamp', ls['creationTime']) / 1000
|
|
564
|
+
).isoformat() if ls.get('lastEventTimestamp') else None,
|
|
565
|
+
'stored_bytes': ls.get('storedBytes', 0)
|
|
566
|
+
}
|
|
567
|
+
for ls in response.get('logStreams', [])
|
|
568
|
+
]
|
|
569
|
+
|
|
570
|
+
async def _get_log_events(
|
|
571
|
+
self,
|
|
572
|
+
log_group_name: str,
|
|
573
|
+
log_stream_name: str,
|
|
574
|
+
start_time: Optional[datetime] = None,
|
|
575
|
+
limit: int = 100
|
|
576
|
+
) -> List[Dict[str, Any]]:
|
|
577
|
+
"""Get log events from a specific log stream"""
|
|
578
|
+
|
|
579
|
+
async with self.aws.client('logs') as logs:
|
|
580
|
+
params = {
|
|
581
|
+
'logGroupName': log_group_name,
|
|
582
|
+
'logStreamName': log_stream_name,
|
|
583
|
+
'limit': limit,
|
|
584
|
+
'startFromHead': False # Get most recent events first
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if start_time:
|
|
588
|
+
params['startTime'] = int(start_time.timestamp() * 1000)
|
|
589
|
+
|
|
590
|
+
response = await logs.get_log_events(**params)
|
|
591
|
+
|
|
592
|
+
return [
|
|
593
|
+
{
|
|
594
|
+
'timestamp': datetime.fromtimestamp(
|
|
595
|
+
event['timestamp'] / 1000
|
|
596
|
+
).isoformat(),
|
|
597
|
+
'message': event['message']
|
|
598
|
+
}
|
|
599
|
+
for event in response.get('events', [])
|
|
600
|
+
]
|
|
601
|
+
|
|
602
|
+
async def _put_metric_data(
|
|
603
|
+
self,
|
|
604
|
+
namespace: str,
|
|
605
|
+
metric_name: str,
|
|
606
|
+
metric_value: float,
|
|
607
|
+
dimensions: Optional[List[Dict[str, str]]] = None,
|
|
608
|
+
unit: Optional[str] = None
|
|
609
|
+
) -> bool:
|
|
610
|
+
"""Publish custom metric data"""
|
|
611
|
+
|
|
612
|
+
async with self.aws.client('cloudwatch') as cloudwatch:
|
|
613
|
+
metric_data = {
|
|
614
|
+
'MetricName': metric_name,
|
|
615
|
+
'Value': metric_value,
|
|
616
|
+
'Timestamp': datetime.utcnow()
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
if dimensions:
|
|
620
|
+
metric_data['Dimensions'] = [
|
|
621
|
+
{'Name': d['Name'], 'Value': d['Value']}
|
|
622
|
+
for d in dimensions
|
|
623
|
+
]
|
|
624
|
+
|
|
625
|
+
if unit:
|
|
626
|
+
metric_data['Unit'] = unit
|
|
627
|
+
|
|
628
|
+
await cloudwatch.put_metric_data(
|
|
629
|
+
Namespace=namespace,
|
|
630
|
+
MetricData=[metric_data]
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
return True
|
|
634
|
+
|
|
635
|
+
async def _describe_alarms(
|
|
636
|
+
self,
|
|
637
|
+
pattern: Optional[str] = None,
|
|
638
|
+
limit: int = 50
|
|
639
|
+
) -> List[Dict[str, Any]]:
|
|
640
|
+
"""List CloudWatch alarms"""
|
|
641
|
+
|
|
642
|
+
async with self.aws.client('cloudwatch') as cloudwatch:
|
|
643
|
+
params = {'MaxRecords': limit}
|
|
644
|
+
if pattern:
|
|
645
|
+
params['AlarmNamePrefix'] = pattern
|
|
646
|
+
|
|
647
|
+
response = await cloudwatch.describe_alarms(**params)
|
|
648
|
+
|
|
649
|
+
return [
|
|
650
|
+
{
|
|
651
|
+
'name': alarm['AlarmName'],
|
|
652
|
+
'description': alarm.get('AlarmDescription'),
|
|
653
|
+
'state': alarm['StateValue'],
|
|
654
|
+
'state_reason': alarm.get('StateReason'),
|
|
655
|
+
'metric_name': alarm.get('MetricName'),
|
|
656
|
+
'namespace': alarm.get('Namespace'),
|
|
657
|
+
'comparison': alarm.get('ComparisonOperator'),
|
|
658
|
+
'threshold': alarm.get('Threshold'),
|
|
659
|
+
'evaluation_periods': alarm.get('EvaluationPeriods')
|
|
660
|
+
}
|
|
661
|
+
for alarm in response.get('MetricAlarms', [])
|
|
662
|
+
]
|
|
663
|
+
|
|
664
|
+
async def _execute(self, **kwargs) -> ToolResult:
|
|
665
|
+
"""Execute CloudWatch operation"""
|
|
666
|
+
|
|
667
|
+
try:
|
|
668
|
+
operation = kwargs['operation']
|
|
669
|
+
|
|
670
|
+
# Parse time parameters
|
|
671
|
+
start_time = self._parse_relative_time(
|
|
672
|
+
kwargs.get('start_time', '-1h')
|
|
673
|
+
)
|
|
674
|
+
end_time = self._parse_relative_time(
|
|
675
|
+
kwargs.get('end_time', 'now')
|
|
676
|
+
)
|
|
677
|
+
|
|
678
|
+
# Route to appropriate method
|
|
679
|
+
if operation == CloudWatchOperation.QUERY_LOGS:
|
|
680
|
+
log_group = kwargs.get('log_group_name') or self.default_log_group
|
|
681
|
+
if not log_group:
|
|
682
|
+
return ToolResult(
|
|
683
|
+
success=False,
|
|
684
|
+
status="error",
|
|
685
|
+
result=None,
|
|
686
|
+
error="log_group_name is required for query_logs operation",
|
|
687
|
+
metadata={},
|
|
688
|
+
timestamp=datetime.now(timezone.utc).isoformat()
|
|
689
|
+
)
|
|
690
|
+
|
|
691
|
+
query_string = kwargs.get('query_string')
|
|
692
|
+
if not query_string:
|
|
693
|
+
# Default query if none provided
|
|
694
|
+
query_string = (
|
|
695
|
+
"fields @timestamp, @message "
|
|
696
|
+
"| sort @timestamp desc "
|
|
697
|
+
f"| limit {kwargs.get('limit', 100)}"
|
|
698
|
+
)
|
|
699
|
+
|
|
700
|
+
results = await self._query_logs_insights(
|
|
701
|
+
log_group_name=log_group,
|
|
702
|
+
query_string=query_string,
|
|
703
|
+
start_time=start_time,
|
|
704
|
+
end_time=end_time,
|
|
705
|
+
limit=kwargs.get('limit', 100)
|
|
706
|
+
)
|
|
707
|
+
|
|
708
|
+
return ToolResult(
|
|
709
|
+
success=True,
|
|
710
|
+
status="completed",
|
|
711
|
+
result={
|
|
712
|
+
'log_group': log_group,
|
|
713
|
+
'query': query_string,
|
|
714
|
+
'time_range': {
|
|
715
|
+
'start': start_time.isoformat(),
|
|
716
|
+
'end': end_time.isoformat()
|
|
717
|
+
},
|
|
718
|
+
'results': results,
|
|
719
|
+
'count': len(results)
|
|
720
|
+
},
|
|
721
|
+
error=None,
|
|
722
|
+
metadata={
|
|
723
|
+
'operation': 'query_logs',
|
|
724
|
+
'log_group': log_group
|
|
725
|
+
},
|
|
726
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
727
|
+
)
|
|
728
|
+
|
|
729
|
+
elif operation == CloudWatchOperation.GET_METRICS:
|
|
730
|
+
if not kwargs.get('namespace') or not kwargs.get('metric_name'):
|
|
731
|
+
return ToolResult(
|
|
732
|
+
success=False,
|
|
733
|
+
status="error",
|
|
734
|
+
result=None,
|
|
735
|
+
error="namespace and metric_name are required for get_metrics",
|
|
736
|
+
metadata={},
|
|
737
|
+
timestamp=datetime.now(timezone.utc).isoformat()
|
|
738
|
+
)
|
|
739
|
+
|
|
740
|
+
metrics = await self._get_metrics(
|
|
741
|
+
namespace=kwargs['namespace'],
|
|
742
|
+
metric_name=kwargs['metric_name'],
|
|
743
|
+
dimensions=kwargs.get('dimensions', []),
|
|
744
|
+
statistic=kwargs.get('statistic', 'Average'),
|
|
745
|
+
start_time=start_time,
|
|
746
|
+
end_time=end_time,
|
|
747
|
+
period=kwargs.get('period', 60)
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
return ToolResult(
|
|
751
|
+
success=True,
|
|
752
|
+
status="completed",
|
|
753
|
+
result=metrics,
|
|
754
|
+
error=None,
|
|
755
|
+
metadata={
|
|
756
|
+
'operation': 'get_metrics',
|
|
757
|
+
'namespace': kwargs['namespace'],
|
|
758
|
+
'metric_name': kwargs['metric_name']
|
|
759
|
+
},
|
|
760
|
+
timestamp=datetime.now(timezone.utc).isoformat()
|
|
761
|
+
)
|
|
762
|
+
|
|
763
|
+
elif operation == CloudWatchOperation.LIST_LOG_GROUPS:
|
|
764
|
+
log_groups = await self._list_log_groups(
|
|
765
|
+
pattern=kwargs.get('pattern'),
|
|
766
|
+
limit=kwargs.get('limit', 50)
|
|
767
|
+
)
|
|
768
|
+
|
|
769
|
+
return ToolResult(
|
|
770
|
+
success=True,
|
|
771
|
+
status="completed",
|
|
772
|
+
result={
|
|
773
|
+
'log_groups': log_groups,
|
|
774
|
+
'count': len(log_groups)
|
|
775
|
+
},
|
|
776
|
+
error=None,
|
|
777
|
+
metadata={
|
|
778
|
+
'operation': 'list_log_groups',
|
|
779
|
+
'pattern': kwargs.get('pattern')
|
|
780
|
+
},
|
|
781
|
+
timestamp=datetime.now(timezone.utc).isoformat()
|
|
782
|
+
)
|
|
783
|
+
|
|
784
|
+
elif operation == CloudWatchOperation.LIST_LOG_STREAMS:
|
|
785
|
+
if not kwargs.get('log_group_name'):
|
|
786
|
+
return ToolResult(
|
|
787
|
+
success=False,
|
|
788
|
+
status="error",
|
|
789
|
+
result=None,
|
|
790
|
+
error="log_group_name is required for list_log_streams",
|
|
791
|
+
metadata={},
|
|
792
|
+
timestamp=datetime.now(timezone.utc).isoformat()
|
|
793
|
+
)
|
|
794
|
+
|
|
795
|
+
log_streams = await self._list_log_streams(
|
|
796
|
+
log_group_name=kwargs['log_group_name'],
|
|
797
|
+
limit=kwargs.get('limit', 50)
|
|
798
|
+
)
|
|
799
|
+
|
|
800
|
+
return ToolResult(
|
|
801
|
+
success=True,
|
|
802
|
+
status="completed",
|
|
803
|
+
result={
|
|
804
|
+
'log_group': kwargs['log_group_name'],
|
|
805
|
+
'log_streams': log_streams,
|
|
806
|
+
'count': len(log_streams)
|
|
807
|
+
},
|
|
808
|
+
error=None,
|
|
809
|
+
metadata={
|
|
810
|
+
'operation': 'list_log_streams',
|
|
811
|
+
'log_group': kwargs['log_group_name']
|
|
812
|
+
},
|
|
813
|
+
timestamp=datetime.now(timezone.utc).isoformat()
|
|
814
|
+
)
|
|
815
|
+
|
|
816
|
+
elif operation == CloudWatchOperation.GET_LOG_EVENTS:
|
|
817
|
+
if not kwargs.get('log_group_name') or not kwargs.get('log_stream_name'):
|
|
818
|
+
return ToolResult(
|
|
819
|
+
success=False,
|
|
820
|
+
status="error",
|
|
821
|
+
result=None,
|
|
822
|
+
error="log_group_name and log_stream_name are required",
|
|
823
|
+
metadata={},
|
|
824
|
+
timestamp=datetime.now(timezone.utc).isoformat()
|
|
825
|
+
)
|
|
826
|
+
|
|
827
|
+
events = await self._get_log_events(
|
|
828
|
+
log_group_name=kwargs['log_group_name'],
|
|
829
|
+
log_stream_name=kwargs['log_stream_name'],
|
|
830
|
+
start_time=start_time if kwargs.get('start_time') else None,
|
|
831
|
+
limit=kwargs.get('limit', 100)
|
|
832
|
+
)
|
|
833
|
+
|
|
834
|
+
return ToolResult(
|
|
835
|
+
success=True,
|
|
836
|
+
status="completed",
|
|
837
|
+
result={
|
|
838
|
+
'log_group': kwargs['log_group_name'],
|
|
839
|
+
'log_stream': kwargs['log_stream_name'],
|
|
840
|
+
'events': events,
|
|
841
|
+
'count': len(events)
|
|
842
|
+
},
|
|
843
|
+
error=None,
|
|
844
|
+
metadata={
|
|
845
|
+
'operation': 'get_log_events',
|
|
846
|
+
'log_group': kwargs['log_group_name'],
|
|
847
|
+
'log_stream': kwargs['log_stream_name']
|
|
848
|
+
},
|
|
849
|
+
timestamp=datetime.now(timezone.utc).isoformat()
|
|
850
|
+
)
|
|
851
|
+
|
|
852
|
+
elif operation == CloudWatchOperation.LOG_SUMMARY:
|
|
853
|
+
if not kwargs.get('log_group_name'):
|
|
854
|
+
return ToolResult(
|
|
855
|
+
success=False,
|
|
856
|
+
status="error",
|
|
857
|
+
result=None,
|
|
858
|
+
error="log_group_name is required for log_summary",
|
|
859
|
+
metadata={},
|
|
860
|
+
timestamp=datetime.now(timezone.utc).isoformat()
|
|
861
|
+
)
|
|
862
|
+
|
|
863
|
+
summary = await self._log_summary(
|
|
864
|
+
log_group_name=kwargs['log_group_name'],
|
|
865
|
+
log_stream_name=kwargs.get('log_stream_name'),
|
|
866
|
+
start_time=start_time if kwargs.get('start_time') else None,
|
|
867
|
+
limit=kwargs.get('limit', 100),
|
|
868
|
+
max_message_length=kwargs.get('max_message_length', 500)
|
|
869
|
+
)
|
|
870
|
+
|
|
871
|
+
return ToolResult(
|
|
872
|
+
success=True,
|
|
873
|
+
status="completed",
|
|
874
|
+
result={
|
|
875
|
+
'log_group': kwargs['log_group_name'],
|
|
876
|
+
'log_stream': kwargs.get('log_stream_name'),
|
|
877
|
+
'summary': summary,
|
|
878
|
+
'count': len(summary),
|
|
879
|
+
'max_message_length': kwargs.get('max_message_length', 500)
|
|
880
|
+
},
|
|
881
|
+
error=None,
|
|
882
|
+
metadata={
|
|
883
|
+
'operation': 'log_summary',
|
|
884
|
+
'log_group': kwargs['log_group_name'],
|
|
885
|
+
'log_stream': kwargs.get('log_stream_name')
|
|
886
|
+
},
|
|
887
|
+
timestamp=datetime.now(timezone.utc).isoformat()
|
|
888
|
+
)
|
|
889
|
+
|
|
890
|
+
elif operation == CloudWatchOperation.PUT_METRIC_DATA:
|
|
891
|
+
if not all([
|
|
892
|
+
kwargs.get('namespace'),
|
|
893
|
+
kwargs.get('metric_name'),
|
|
894
|
+
kwargs.get('metric_value') is not None
|
|
895
|
+
]):
|
|
896
|
+
return ToolResult(
|
|
897
|
+
success=False,
|
|
898
|
+
status="error",
|
|
899
|
+
result=None,
|
|
900
|
+
error="namespace, metric_name, and metric_value are required",
|
|
901
|
+
metadata={},
|
|
902
|
+
timestamp=datetime.now(timezone.utc).isoformat()
|
|
903
|
+
)
|
|
904
|
+
|
|
905
|
+
success = await self._put_metric_data(
|
|
906
|
+
namespace=kwargs['namespace'],
|
|
907
|
+
metric_name=kwargs['metric_name'],
|
|
908
|
+
metric_value=kwargs['metric_value'],
|
|
909
|
+
dimensions=kwargs.get('dimensions'),
|
|
910
|
+
unit=kwargs.get('unit')
|
|
911
|
+
)
|
|
912
|
+
|
|
913
|
+
return ToolResult(
|
|
914
|
+
success=True,
|
|
915
|
+
status="completed",
|
|
916
|
+
result={
|
|
917
|
+
'message': 'Metric data published successfully',
|
|
918
|
+
'namespace': kwargs['namespace'],
|
|
919
|
+
'metric_name': kwargs['metric_name'],
|
|
920
|
+
'value': kwargs['metric_value']
|
|
921
|
+
},
|
|
922
|
+
error=None,
|
|
923
|
+
metadata={
|
|
924
|
+
'operation': 'put_metric_data',
|
|
925
|
+
'namespace': kwargs['namespace'],
|
|
926
|
+
'metric_name': kwargs['metric_name']
|
|
927
|
+
},
|
|
928
|
+
timestamp=datetime.now(timezone.utc).isoformat()
|
|
929
|
+
)
|
|
930
|
+
|
|
931
|
+
elif operation == CloudWatchOperation.DESCRIBE_ALARMS:
|
|
932
|
+
alarms = await self._describe_alarms(
|
|
933
|
+
pattern=kwargs.get('pattern'),
|
|
934
|
+
limit=kwargs.get('limit', 50)
|
|
935
|
+
)
|
|
936
|
+
|
|
937
|
+
return ToolResult(
|
|
938
|
+
success=True,
|
|
939
|
+
status="completed",
|
|
940
|
+
result={
|
|
941
|
+
'alarms': alarms,
|
|
942
|
+
'count': len(alarms)
|
|
943
|
+
},
|
|
944
|
+
error=None,
|
|
945
|
+
metadata={
|
|
946
|
+
'operation': 'describe_alarms',
|
|
947
|
+
'pattern': kwargs.get('pattern')
|
|
948
|
+
},
|
|
949
|
+
timestamp=datetime.now(timezone.utc).isoformat()
|
|
950
|
+
)
|
|
951
|
+
|
|
952
|
+
else:
|
|
953
|
+
return ToolResult(
|
|
954
|
+
success=False,
|
|
955
|
+
status="error",
|
|
956
|
+
result=None,
|
|
957
|
+
error=f"Unknown operation: {operation}",
|
|
958
|
+
metadata={'operation': str(operation)},
|
|
959
|
+
timestamp=datetime.now(timezone.utc).isoformat()
|
|
960
|
+
)
|
|
961
|
+
|
|
962
|
+
except ClientError as e:
|
|
963
|
+
error_code = e.response['Error']['Code']
|
|
964
|
+
error_msg = e.response['Error']['Message']
|
|
965
|
+
return ToolResult(
|
|
966
|
+
success=False,
|
|
967
|
+
status="aws_error",
|
|
968
|
+
result=None,
|
|
969
|
+
error=f"AWS Error ({error_code}): {error_msg}",
|
|
970
|
+
metadata={
|
|
971
|
+
'error_code': error_code,
|
|
972
|
+
'operation': kwargs.get('operation', 'unknown')
|
|
973
|
+
},
|
|
974
|
+
timestamp=datetime.now(timezone.utc).isoformat()
|
|
975
|
+
)
|
|
976
|
+
|
|
977
|
+
except Exception as e:
|
|
978
|
+
return ToolResult(
|
|
979
|
+
success=False,
|
|
980
|
+
status="error",
|
|
981
|
+
result=None,
|
|
982
|
+
error=f"CloudWatch operation failed: {str(e)}",
|
|
983
|
+
metadata={
|
|
984
|
+
'operation': kwargs.get('operation', 'unknown'),
|
|
985
|
+
'exception_type': type(e).__name__
|
|
986
|
+
},
|
|
987
|
+
timestamp=datetime.now(timezone.utc).isoformat()
|
|
988
|
+
)
|