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,594 @@
|
|
|
1
|
+
# agents/registry.py
|
|
2
|
+
"""
|
|
3
|
+
Agent Auto-Registration System for AI-Parrot.
|
|
4
|
+
|
|
5
|
+
This module provides multiple approaches for automatically discovering
|
|
6
|
+
and registering agents from the agents/ directory.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
import sys
|
|
10
|
+
import asyncio
|
|
11
|
+
from typing import Dict, Iterable, List, Type, Set, Union, Optional, Any, Protocol
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from types import ModuleType
|
|
14
|
+
import importlib
|
|
15
|
+
import inspect
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
import yaml
|
|
18
|
+
import hashlib
|
|
19
|
+
from navconfig.logging import logging
|
|
20
|
+
from navconfig import BASE_DIR
|
|
21
|
+
from ..bots.abstract import AbstractBot
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AgentFactory(Protocol):
|
|
25
|
+
"""Protocol for agent factory callable."""
|
|
26
|
+
def __call__(self, **kwargs: Any) -> AbstractBot: ...
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(slots=True)
|
|
30
|
+
class BotMetadata:
|
|
31
|
+
"""
|
|
32
|
+
Metadata about a discovered Bot or Agent.
|
|
33
|
+
|
|
34
|
+
This class holds information about agents found during discovery,
|
|
35
|
+
making it easier to manage and validate them before registration.
|
|
36
|
+
"""
|
|
37
|
+
name: str
|
|
38
|
+
factory: Union[Type[AbstractBot], AgentFactory]
|
|
39
|
+
module_path: str
|
|
40
|
+
file_path: Path
|
|
41
|
+
singleton: bool = False
|
|
42
|
+
tags: Optional[Set[str]] = field(default_factory=set)
|
|
43
|
+
priority: int = 0
|
|
44
|
+
at_startup: bool = False
|
|
45
|
+
dependencies: List[str] = field(default_factory=list)
|
|
46
|
+
startup_config: Dict[str, Any] = field(default_factory=dict) # Config for startup instantiation
|
|
47
|
+
_instance: Optional[AbstractBot] = None
|
|
48
|
+
_lock: asyncio.Lock = field(default_factory=asyncio.Lock, repr=False)
|
|
49
|
+
|
|
50
|
+
def __post_init__(self):
|
|
51
|
+
"""Validate bot metadata after creation."""
|
|
52
|
+
if not issubclass(self.factory, AbstractBot):
|
|
53
|
+
raise ValueError(
|
|
54
|
+
f"Bot {self.name} must inherit from AbstractBot"
|
|
55
|
+
)
|
|
56
|
+
# If at_startup=True, automatically make it singleton
|
|
57
|
+
if self.at_startup:
|
|
58
|
+
self.singleton = True
|
|
59
|
+
|
|
60
|
+
async def get_instance(self, *args, **kwargs) -> AbstractBot:
|
|
61
|
+
"""
|
|
62
|
+
Get or create an instance of the bot.
|
|
63
|
+
|
|
64
|
+
This implements lazy instantiation - instances are only created when needed.
|
|
65
|
+
For singleton bots, the same instance is returned on subsequent calls.
|
|
66
|
+
"""
|
|
67
|
+
# Singleton path
|
|
68
|
+
if self.singleton and self._instance is not None:
|
|
69
|
+
return self._instance
|
|
70
|
+
|
|
71
|
+
async with self._lock:
|
|
72
|
+
# Double-check pattern for singletons
|
|
73
|
+
if self.singleton and self._instance is not None:
|
|
74
|
+
return self._instance
|
|
75
|
+
|
|
76
|
+
# Merge startup config with runtime kwargs
|
|
77
|
+
merged_kwargs = {**self.startup_config, **kwargs}
|
|
78
|
+
# Create new instance
|
|
79
|
+
instance = self.factory(name=self.name, **merged_kwargs)
|
|
80
|
+
if not isinstance(instance, AbstractBot):
|
|
81
|
+
raise ValueError(
|
|
82
|
+
f"Factory for {self.name} returned {type(instance)!r}, expected AbstractBot."
|
|
83
|
+
)
|
|
84
|
+
# Configure instance if needed:
|
|
85
|
+
if not self.at_startup:
|
|
86
|
+
await instance.configure()
|
|
87
|
+
# Store instance if singleton
|
|
88
|
+
if self.singleton:
|
|
89
|
+
self._instance = instance
|
|
90
|
+
|
|
91
|
+
return instance
|
|
92
|
+
|
|
93
|
+
@dataclass
|
|
94
|
+
class BotConfig:
|
|
95
|
+
"""Configuration for the bot in config-based discovery."""
|
|
96
|
+
name: str
|
|
97
|
+
class_name: str
|
|
98
|
+
module: str
|
|
99
|
+
enabled: bool = True
|
|
100
|
+
config: Dict[str, Any] = field(default_factory=dict)
|
|
101
|
+
tags: Optional[Set[str]] = field(default_factory=set)
|
|
102
|
+
singleton: bool = False
|
|
103
|
+
at_startup: bool = False
|
|
104
|
+
startup_config: Dict[str, Any] = field(default_factory=dict)
|
|
105
|
+
priority: int = 0
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class AgentRegistry:
|
|
109
|
+
"""
|
|
110
|
+
Central registry for managing Bo/Agent discovery and registration.
|
|
111
|
+
|
|
112
|
+
This class maintains a registry of all discovered agents and provides
|
|
113
|
+
methods for discovering, validating, and instantiating them.
|
|
114
|
+
|
|
115
|
+
- register(): programmatic registration
|
|
116
|
+
- register_agent decorator: declarative registration on class definition
|
|
117
|
+
|
|
118
|
+
We can use several strategies for discovery:
|
|
119
|
+
- decorators to mark classes for auto-registration.
|
|
120
|
+
- Configuration-Based Discovery, use a YAML config to define agents.
|
|
121
|
+
|
|
122
|
+
Decorator Usage:
|
|
123
|
+
@register_agent(name="MySpecialAgent", priority=10)
|
|
124
|
+
class MyAgent(AbstractBot):
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
# Programmatic registration
|
|
128
|
+
agent_registry.register("CustomAgent", CustomAgentClass)
|
|
129
|
+
|
|
130
|
+
Configuration agents.yaml:
|
|
131
|
+
agents:
|
|
132
|
+
- name: "ReportGenerator"
|
|
133
|
+
class_name: "ReportGeneratorAgent"
|
|
134
|
+
module: "agents.reporting"
|
|
135
|
+
enabled: true
|
|
136
|
+
config:
|
|
137
|
+
templates_dir: "./templates"
|
|
138
|
+
- name: "DataAnalyzer"
|
|
139
|
+
class_name: "DataAnalyzerAgent"
|
|
140
|
+
module: "agents.analysis"
|
|
141
|
+
enabled: true
|
|
142
|
+
|
|
143
|
+
# Get instances
|
|
144
|
+
agent = await agent_registry.get_instance("MyAgent")
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
def __init__(
|
|
148
|
+
self,
|
|
149
|
+
agents_dir: Optional[Path] = None,
|
|
150
|
+
*,
|
|
151
|
+
extra_agent_dirs: Optional[Iterable[Path]] = None,
|
|
152
|
+
):
|
|
153
|
+
self.logger = logging.getLogger('Parrot.AgentRegistry')
|
|
154
|
+
self.agents_dir = agents_dir or BASE_DIR / "agents"
|
|
155
|
+
self._registered_agents: Dict[str, BotMetadata] = {}
|
|
156
|
+
self._config_file: Optional[Path] = None
|
|
157
|
+
self._discovery_paths: List[Path] = []
|
|
158
|
+
|
|
159
|
+
# Ensure primary discovery directory exists
|
|
160
|
+
primary_dir = self._prepare_discovery_dir(self.agents_dir)
|
|
161
|
+
self._discovery_paths.append(primary_dir)
|
|
162
|
+
|
|
163
|
+
self._extra_agent_dirs: List[Path] = []
|
|
164
|
+
if extra_agent_dirs:
|
|
165
|
+
for directory in extra_agent_dirs:
|
|
166
|
+
prepared_dir = self._prepare_discovery_dir(directory)
|
|
167
|
+
self._extra_agent_dirs.append(prepared_dir)
|
|
168
|
+
self._discovery_paths.append(prepared_dir)
|
|
169
|
+
# Create config file if it doesn't exist
|
|
170
|
+
self._config_file: Optional[Path] = self.agents_dir / "agents.yaml"
|
|
171
|
+
if not self._config_file.exists():
|
|
172
|
+
self._config_file.write_text(
|
|
173
|
+
"# Auto-generated agents configuration\nagents: []\n"
|
|
174
|
+
)
|
|
175
|
+
self.logger.notice(
|
|
176
|
+
f"AgentRegistry initialized with agents_dir={self.agents_dir}, config_file={self._config_file}"
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
def _prepare_discovery_dir(self, directory: Path) -> Path:
|
|
180
|
+
"""Ensure a discovery directory exists and is importable."""
|
|
181
|
+
resolved = Path(directory).resolve()
|
|
182
|
+
resolved.mkdir(parents=True, exist_ok=True)
|
|
183
|
+
init_file = resolved / "__init__.py"
|
|
184
|
+
if not init_file.exists():
|
|
185
|
+
init_file.write_text("# Auto-generated agents module")
|
|
186
|
+
if str(resolved) not in sys.path:
|
|
187
|
+
sys.path.append(str(resolved))
|
|
188
|
+
return resolved
|
|
189
|
+
|
|
190
|
+
def get_bot_instance(self, name: str) -> Optional[AbstractBot]:
|
|
191
|
+
"""Get an instantiated bot by name."""
|
|
192
|
+
return self._registered_agents.get(name)
|
|
193
|
+
|
|
194
|
+
def get_metadata(self, name: str) -> Optional[BotMetadata]:
|
|
195
|
+
return self._registered_agents.get(name)
|
|
196
|
+
|
|
197
|
+
def register(
|
|
198
|
+
self,
|
|
199
|
+
name: str,
|
|
200
|
+
factory: Type[AbstractBot],
|
|
201
|
+
*,
|
|
202
|
+
singleton: bool = False,
|
|
203
|
+
tags: Optional[Iterable[str]] = None,
|
|
204
|
+
priority: int = 0,
|
|
205
|
+
dependencies: Optional[List[str]] = None,
|
|
206
|
+
replace: bool = False,
|
|
207
|
+
at_startup: bool = False,
|
|
208
|
+
startup_config: Optional[Dict[str, Any]] = None,
|
|
209
|
+
**kwargs: Any
|
|
210
|
+
) -> None:
|
|
211
|
+
"""
|
|
212
|
+
Register a bot class with the registry.
|
|
213
|
+
|
|
214
|
+
This is the core registration method that both decorator and config-based
|
|
215
|
+
registration ultimately call.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
name: Unique name for the bot
|
|
219
|
+
factory: Bot class (subclass of AbstractBot)
|
|
220
|
+
singleton: Whether to enforce singleton instance
|
|
221
|
+
tags: Optional tags for categorization
|
|
222
|
+
priority: Registration priority (higher = earlier)
|
|
223
|
+
dependencies: List of required dependencies
|
|
224
|
+
replace: Whether to replace an existing registration
|
|
225
|
+
startup_config: Configuration to use during startup instantiation
|
|
226
|
+
**kwargs: Additional metadata
|
|
227
|
+
"""
|
|
228
|
+
if name in self._registered_agents and not replace:
|
|
229
|
+
self.logger.warning(
|
|
230
|
+
f"Bot {name} already registered, use replace=True to overwrite"
|
|
231
|
+
)
|
|
232
|
+
return
|
|
233
|
+
|
|
234
|
+
if not issubclass(factory, AbstractBot):
|
|
235
|
+
raise ValueError(
|
|
236
|
+
f"Bot {name} must inherit from AbstractBot"
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
# Get module information
|
|
240
|
+
module = inspect.getmodule(factory)
|
|
241
|
+
module_path = module.__name__ if module else "unknown"
|
|
242
|
+
file_path = Path(module.__file__) if module and module.__file__ else Path("unknown")
|
|
243
|
+
|
|
244
|
+
if not startup_config:
|
|
245
|
+
startup_config = {}
|
|
246
|
+
merged_kwargs = {**startup_config, **kwargs}
|
|
247
|
+
|
|
248
|
+
metadata = BotMetadata(
|
|
249
|
+
name=name,
|
|
250
|
+
factory=factory,
|
|
251
|
+
module_path=module_path,
|
|
252
|
+
file_path=file_path,
|
|
253
|
+
singleton=singleton,
|
|
254
|
+
at_startup=at_startup,
|
|
255
|
+
startup_config=merged_kwargs or {},
|
|
256
|
+
tags=set(tags or []),
|
|
257
|
+
priority=priority,
|
|
258
|
+
dependencies=dependencies or [],
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
self._registered_agents[name] = metadata
|
|
262
|
+
self.logger.info(
|
|
263
|
+
f"Registered bot: {name}"
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
def has(self, name: str) -> bool:
|
|
267
|
+
return name in self._registered_agents
|
|
268
|
+
|
|
269
|
+
async def get_instance(self, name: str, **kwargs) -> Optional[AbstractBot]:
|
|
270
|
+
"""
|
|
271
|
+
Get an instance of a registered bot.
|
|
272
|
+
|
|
273
|
+
This method handles lazy instantiation - bots are only created when needed.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
name: Name of the bot to instantiate
|
|
277
|
+
**kwargs: Additional arguments to pass to the bot constructor
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
Bot instance or None if not found
|
|
281
|
+
"""
|
|
282
|
+
if name not in self._registered_agents:
|
|
283
|
+
self.logger.warning(f"Bot {name} not found in registry")
|
|
284
|
+
return None
|
|
285
|
+
|
|
286
|
+
metadata = self._registered_agents[name]
|
|
287
|
+
try:
|
|
288
|
+
instance = await metadata.get_instance(**kwargs)
|
|
289
|
+
self.logger.debug(f"Retrieved instance for bot: {name}")
|
|
290
|
+
return instance
|
|
291
|
+
except Exception as e:
|
|
292
|
+
self.logger.error(f"Failed to instantiate bot {name}: {str(e)}")
|
|
293
|
+
return None
|
|
294
|
+
|
|
295
|
+
def load_config(self) -> List[BotConfig]:
|
|
296
|
+
"""Load bot configuration from YAML file."""
|
|
297
|
+
if not self._config_file or not self._config_file.exists():
|
|
298
|
+
self.logger.debug(
|
|
299
|
+
"No config file found, skipping config-based discovery"
|
|
300
|
+
)
|
|
301
|
+
return []
|
|
302
|
+
|
|
303
|
+
try:
|
|
304
|
+
with open(self._config_file, 'r') as f:
|
|
305
|
+
config_data = yaml.safe_load(f)
|
|
306
|
+
|
|
307
|
+
configs = []
|
|
308
|
+
for agent_data in config_data.get('agents', []):
|
|
309
|
+
try:
|
|
310
|
+
config = BotConfig(**agent_data)
|
|
311
|
+
if config.enabled:
|
|
312
|
+
configs.append(config)
|
|
313
|
+
except Exception as e:
|
|
314
|
+
self.logger.error(
|
|
315
|
+
f"Invalid config entry: {agent_data}, error: {e}"
|
|
316
|
+
)
|
|
317
|
+
continue
|
|
318
|
+
|
|
319
|
+
return configs
|
|
320
|
+
|
|
321
|
+
except ImportError:
|
|
322
|
+
self.logger.error("PyYAML not installed. Install with: pip install pyyaml")
|
|
323
|
+
return []
|
|
324
|
+
except Exception as e:
|
|
325
|
+
self.logger.error(f"Failed to load config: {str(e)}")
|
|
326
|
+
return []
|
|
327
|
+
|
|
328
|
+
def discover_config_agents(self) -> List[BotMetadata]:
|
|
329
|
+
"""
|
|
330
|
+
Register agents from configuration file.
|
|
331
|
+
|
|
332
|
+
This method loads the config file and registers all enabled agents.
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
Number of agents successfully registered from config
|
|
336
|
+
"""
|
|
337
|
+
configs = self.load_config()
|
|
338
|
+
registered_count = 0
|
|
339
|
+
|
|
340
|
+
for config in configs:
|
|
341
|
+
if not config.enabled:
|
|
342
|
+
continue
|
|
343
|
+
|
|
344
|
+
try:
|
|
345
|
+
# Import the module
|
|
346
|
+
module = importlib.import_module(config.module)
|
|
347
|
+
|
|
348
|
+
# Get the class
|
|
349
|
+
agent_class = getattr(module, config.class_name)
|
|
350
|
+
|
|
351
|
+
# Validate it's an AbstractBot subclass
|
|
352
|
+
if not issubclass(agent_class, AbstractBot):
|
|
353
|
+
self.logger.error(
|
|
354
|
+
f"{config.class_name} is not an AbstractBot subclass"
|
|
355
|
+
)
|
|
356
|
+
continue
|
|
357
|
+
|
|
358
|
+
# Register using core register method
|
|
359
|
+
self.register(
|
|
360
|
+
name=config.name,
|
|
361
|
+
factory=agent_class,
|
|
362
|
+
singleton=config.singleton,
|
|
363
|
+
tags=config.tags,
|
|
364
|
+
priority=config.priority,
|
|
365
|
+
at_startup=config.at_startup,
|
|
366
|
+
startup_config=config.config,
|
|
367
|
+
replace=True
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
registered_count += 1
|
|
371
|
+
self.logger.info(
|
|
372
|
+
f"Registered bot from config: {config.name}"
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
except Exception as e:
|
|
376
|
+
self.logger.error(
|
|
377
|
+
f"Failed to load bot {config.name}: {str(e)}"
|
|
378
|
+
)
|
|
379
|
+
continue
|
|
380
|
+
|
|
381
|
+
return registered_count
|
|
382
|
+
|
|
383
|
+
def _import_module_from_path(
|
|
384
|
+
self,
|
|
385
|
+
path: Path,
|
|
386
|
+
*,
|
|
387
|
+
base_dir: Optional[Path] = None,
|
|
388
|
+
package_hint: str = "parrot.dynamic_agents",
|
|
389
|
+
) -> ModuleType:
|
|
390
|
+
"""
|
|
391
|
+
Import a Python module from an arbitrary filesystem path.
|
|
392
|
+
Ensures decorators run at import time.
|
|
393
|
+
"""
|
|
394
|
+
base = (base_dir or self.agents_dir).resolve()
|
|
395
|
+
resolved_path = path.resolve()
|
|
396
|
+
try:
|
|
397
|
+
rel = resolved_path.relative_to(base)
|
|
398
|
+
except ValueError:
|
|
399
|
+
rel = Path(resolved_path.name)
|
|
400
|
+
rel_path = rel if isinstance(rel, Path) else Path(rel)
|
|
401
|
+
module_suffix = ".".join(rel_path.with_suffix('').parts)
|
|
402
|
+
if module_suffix:
|
|
403
|
+
mod_name = f"{package_hint}.{module_suffix}"
|
|
404
|
+
else:
|
|
405
|
+
mod_name = package_hint
|
|
406
|
+
|
|
407
|
+
spec = importlib.util.spec_from_file_location(mod_name, str(path))
|
|
408
|
+
if spec is None or spec.loader is None:
|
|
409
|
+
raise ImportError(
|
|
410
|
+
f"Could not load spec for {path}"
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
module = importlib.util.module_from_spec(spec)
|
|
414
|
+
sys.modules[mod_name] = module
|
|
415
|
+
spec.loader.exec_module(module) # type: ignore[attr-defined]
|
|
416
|
+
self.logger.debug(
|
|
417
|
+
f"Imported agent module: {mod_name} from {path}"
|
|
418
|
+
)
|
|
419
|
+
return module
|
|
420
|
+
|
|
421
|
+
def _namespace_for_directory(self, directory: Path) -> str:
|
|
422
|
+
digest = hashlib.md5(str(directory.resolve()).encode('utf-8')).hexdigest()
|
|
423
|
+
return f"parrot.dynamic_agents.dir_{digest}"
|
|
424
|
+
|
|
425
|
+
def _load_modules_from_directory(self, directory: Path) -> int:
|
|
426
|
+
if not directory.exists() or not directory.is_dir():
|
|
427
|
+
self.logger.debug(
|
|
428
|
+
f"Agents directory {directory} does not exist, skipping"
|
|
429
|
+
)
|
|
430
|
+
return 0
|
|
431
|
+
|
|
432
|
+
package_hint = self._namespace_for_directory(directory)
|
|
433
|
+
module_files = list(directory.glob("*.py"))
|
|
434
|
+
imported_count = 0
|
|
435
|
+
|
|
436
|
+
for file_path in module_files:
|
|
437
|
+
if file_path.name == "__init__.py":
|
|
438
|
+
continue # Skip __init__.py
|
|
439
|
+
|
|
440
|
+
try:
|
|
441
|
+
self._import_module_from_path(
|
|
442
|
+
file_path,
|
|
443
|
+
base_dir=directory,
|
|
444
|
+
package_hint=package_hint
|
|
445
|
+
)
|
|
446
|
+
imported_count += 1
|
|
447
|
+
except Exception as e:
|
|
448
|
+
self.logger.error(f"Failed to import {file_path}: {e}")
|
|
449
|
+
return imported_count
|
|
450
|
+
|
|
451
|
+
async def load_modules(self) -> int:
|
|
452
|
+
"""
|
|
453
|
+
Dynamically import all Python modules from every discovery directory.
|
|
454
|
+
|
|
455
|
+
This triggers any decorators in those modules to register agents.
|
|
456
|
+
"""
|
|
457
|
+
total_imported = 0
|
|
458
|
+
for directory in self._discovery_paths:
|
|
459
|
+
total_imported += self._load_modules_from_directory(directory)
|
|
460
|
+
|
|
461
|
+
self.logger.info(
|
|
462
|
+
f"Discovered (decorator) agent modules: {total_imported} across {len(self._discovery_paths)} directories"
|
|
463
|
+
)
|
|
464
|
+
return total_imported
|
|
465
|
+
|
|
466
|
+
def register_bot_decorator(
|
|
467
|
+
self,
|
|
468
|
+
*,
|
|
469
|
+
name: Optional[str] = None,
|
|
470
|
+
priority: int = 0,
|
|
471
|
+
dependencies: Optional[List[str]] = None,
|
|
472
|
+
singleton: bool = False,
|
|
473
|
+
at_startup: bool = False,
|
|
474
|
+
startup_config: Optional[Dict[str, Any]] = None,
|
|
475
|
+
tags: Optional[Iterable[str]] = None,
|
|
476
|
+
**kwargs
|
|
477
|
+
):
|
|
478
|
+
"""
|
|
479
|
+
Decorator to register an AbstractBot subclass.
|
|
480
|
+
|
|
481
|
+
This decorator immediately calls self.register() to register the agent,
|
|
482
|
+
rather than storing it separately for later processing.
|
|
483
|
+
|
|
484
|
+
Args:
|
|
485
|
+
name: Agent name (defaults to class name)
|
|
486
|
+
priority: Registration priority (higher = earlier)
|
|
487
|
+
dependencies: List of required dependencies
|
|
488
|
+
singleton: Whether to enforce singleton instance
|
|
489
|
+
tags: Optional tags for categorization
|
|
490
|
+
**kwargs: Additional registration parameters
|
|
491
|
+
|
|
492
|
+
Usage:
|
|
493
|
+
@register_agent(name="CriticalAgent", at_startup=True, startup_config={"db_pool_size": 10})
|
|
494
|
+
class MyBot(AbstractBot):
|
|
495
|
+
pass
|
|
496
|
+
"""
|
|
497
|
+
def _decorator(cls: Type[AbstractBot]) -> Type[AbstractBot]:
|
|
498
|
+
if not inspect.isclass(cls):
|
|
499
|
+
raise TypeError("@register_agent can only be used on classes.")
|
|
500
|
+
|
|
501
|
+
if not issubclass(cls, AbstractBot):
|
|
502
|
+
raise TypeError("@register_agent can only be used on AbstractBot subclasses.")
|
|
503
|
+
|
|
504
|
+
# Determine agent name
|
|
505
|
+
bot_name = (name or cls.__name__).strip()
|
|
506
|
+
# Register immediately using the core register method
|
|
507
|
+
self.register(
|
|
508
|
+
name=bot_name,
|
|
509
|
+
factory=cls,
|
|
510
|
+
singleton=singleton,
|
|
511
|
+
at_startup=at_startup,
|
|
512
|
+
startup_config=startup_config,
|
|
513
|
+
tags=tags,
|
|
514
|
+
priority=priority,
|
|
515
|
+
dependencies=dependencies,
|
|
516
|
+
**kwargs
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
# Mark the class with metadata for introspection
|
|
520
|
+
cls._parrot_agent_metadata = self._registered_agents[bot_name]
|
|
521
|
+
|
|
522
|
+
return cls
|
|
523
|
+
|
|
524
|
+
return _decorator
|
|
525
|
+
|
|
526
|
+
def list_bots_by_priority(self) -> List[BotMetadata]:
|
|
527
|
+
"""Get all registered bots sorted by priority (highest first)."""
|
|
528
|
+
return sorted(
|
|
529
|
+
self._registered_agents.values(),
|
|
530
|
+
key=lambda x: x.priority,
|
|
531
|
+
reverse=True
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
def get_bots_by_tag(self, tag: str) -> List[BotMetadata]:
|
|
535
|
+
"""Get all bots that have a specific tag."""
|
|
536
|
+
return [
|
|
537
|
+
metadata for metadata in self._registered_agents.values()
|
|
538
|
+
if tag in metadata.tags
|
|
539
|
+
]
|
|
540
|
+
|
|
541
|
+
def clear_registry(self) -> None:
|
|
542
|
+
"""Clear all registered bots. Useful for testing."""
|
|
543
|
+
self._registered_agents.clear()
|
|
544
|
+
self.logger.info("Registry cleared")
|
|
545
|
+
|
|
546
|
+
def get_registration_info(self) -> Dict[str, Any]:
|
|
547
|
+
"""Get detailed information about the registry state."""
|
|
548
|
+
return {
|
|
549
|
+
"total_registered": len(self._registered_agents),
|
|
550
|
+
"by_priority": {
|
|
551
|
+
metadata.name: metadata.priority
|
|
552
|
+
for metadata in self._registered_agents.values()
|
|
553
|
+
},
|
|
554
|
+
"by_tags": {
|
|
555
|
+
tag: [name for name, metadata in self._registered_agents.items() if tag in metadata.tags]
|
|
556
|
+
for tag in set().union(*(metadata.tags for metadata in self._registered_agents.values()))
|
|
557
|
+
},
|
|
558
|
+
"singletons": [
|
|
559
|
+
name for name, metadata in self._registered_agents.items()
|
|
560
|
+
if metadata.singleton
|
|
561
|
+
]
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
async def instantiate_startup_agents(self, app: Optional[Any] = None, **kwargs: Any) -> Dict[str, Any]:
|
|
565
|
+
"""
|
|
566
|
+
Create instances for agents marked at_startup=True (implies singleton).
|
|
567
|
+
"""
|
|
568
|
+
results = {}
|
|
569
|
+
startup_agents = [bot for bot in self.list_bots_by_priority() if bot.at_startup]
|
|
570
|
+
|
|
571
|
+
startup_agents.sort(
|
|
572
|
+
key=lambda meta: meta.priority,
|
|
573
|
+
reverse=True
|
|
574
|
+
)
|
|
575
|
+
for metadata in startup_agents:
|
|
576
|
+
try:
|
|
577
|
+
instance = await metadata.get_instance(**kwargs)
|
|
578
|
+
if callable(getattr(instance, 'configure', None)):
|
|
579
|
+
await instance.configure(app)
|
|
580
|
+
results[metadata.name] = {
|
|
581
|
+
"status": "success",
|
|
582
|
+
"instance": instance,
|
|
583
|
+
"instance_id": id(instance),
|
|
584
|
+
"priority": metadata.priority
|
|
585
|
+
}
|
|
586
|
+
except Exception as e:
|
|
587
|
+
self.logger.error(
|
|
588
|
+
f"Failed startup instantiate {metadata.name}: {e}"
|
|
589
|
+
)
|
|
590
|
+
results[metadata.name] = {
|
|
591
|
+
"status": "error",
|
|
592
|
+
"error": str(e)
|
|
593
|
+
}
|
|
594
|
+
return results
|