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
parrot/a2a/client.py
ADDED
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
# parrot/a2a/client.py
|
|
2
|
+
"""
|
|
3
|
+
A2A Client - Connect to remote A2A agents from AI-Parrot.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
import json
|
|
7
|
+
import asyncio
|
|
8
|
+
from typing import Dict, List, Optional, Any, AsyncIterator, Type
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
import aiohttp
|
|
11
|
+
from pydantic import BaseModel, Field
|
|
12
|
+
from navconfig.logging import logging
|
|
13
|
+
from ..tools.abstract import AbstractTool, AbstractToolArgsSchema
|
|
14
|
+
from .models import (
|
|
15
|
+
AgentCard,
|
|
16
|
+
AgentSkill,
|
|
17
|
+
Task,
|
|
18
|
+
TaskStatus,
|
|
19
|
+
TaskState,
|
|
20
|
+
Artifact,
|
|
21
|
+
Message,
|
|
22
|
+
Part,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class A2AAgentConnection:
|
|
29
|
+
"""Represents a connection to a remote A2A agent."""
|
|
30
|
+
url: str
|
|
31
|
+
card: AgentCard
|
|
32
|
+
client: "A2AClient"
|
|
33
|
+
name: str = ""
|
|
34
|
+
|
|
35
|
+
def __post_init__(self):
|
|
36
|
+
self.name = self.card.name
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class A2AClient:
|
|
40
|
+
"""
|
|
41
|
+
Client for communicating with remote A2A agents.
|
|
42
|
+
|
|
43
|
+
Example:
|
|
44
|
+
async with A2AClient("https://remote-agent:8080") as client:
|
|
45
|
+
# Discover agent
|
|
46
|
+
card = await client.discover()
|
|
47
|
+
print(f"Connected to: {card.name}")
|
|
48
|
+
|
|
49
|
+
# Send message
|
|
50
|
+
task = await client.send_message("Hello!")
|
|
51
|
+
print(task.artifacts[0].parts[0].text)
|
|
52
|
+
|
|
53
|
+
# Stream response
|
|
54
|
+
async for chunk in client.stream_message("Explain quantum computing"):
|
|
55
|
+
print(chunk, end="", flush=True)
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
base_url: str,
|
|
61
|
+
*,
|
|
62
|
+
timeout: float = 60.0,
|
|
63
|
+
headers: Optional[Dict[str, str]] = None,
|
|
64
|
+
auth_token: Optional[str] = None,
|
|
65
|
+
api_key: Optional[str] = None,
|
|
66
|
+
):
|
|
67
|
+
"""
|
|
68
|
+
Initialize A2A client.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
base_url: Base URL of the A2A agent (e.g., "https://agent.example.com")
|
|
72
|
+
timeout: Request timeout in seconds
|
|
73
|
+
headers: Additional headers to send with requests
|
|
74
|
+
auth_token: Bearer token for authentication
|
|
75
|
+
api_key: API key for authentication (sent as X-API-Key header)
|
|
76
|
+
"""
|
|
77
|
+
self.base_url = base_url.rstrip("/")
|
|
78
|
+
self.timeout = aiohttp.ClientTimeout(total=timeout)
|
|
79
|
+
self._session: Optional[aiohttp.ClientSession] = None
|
|
80
|
+
self._agent_card: Optional[AgentCard] = None
|
|
81
|
+
self._owns_session = False
|
|
82
|
+
|
|
83
|
+
# Build headers
|
|
84
|
+
self.headers = {"Content-Type": "application/json"}
|
|
85
|
+
if headers:
|
|
86
|
+
self.headers.update(headers)
|
|
87
|
+
if auth_token:
|
|
88
|
+
self.headers["Authorization"] = f"Bearer {auth_token}"
|
|
89
|
+
if api_key:
|
|
90
|
+
self.headers["X-API-Key"] = api_key
|
|
91
|
+
|
|
92
|
+
self.logger = logging.getLogger(f"A2AClient.{base_url}")
|
|
93
|
+
|
|
94
|
+
async def __aenter__(self) -> "A2AClient":
|
|
95
|
+
await self.connect()
|
|
96
|
+
return self
|
|
97
|
+
|
|
98
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
99
|
+
await self.disconnect()
|
|
100
|
+
|
|
101
|
+
async def connect(self, session: Optional[aiohttp.ClientSession] = None) -> None:
|
|
102
|
+
"""Establish connection and discover remote agent."""
|
|
103
|
+
if session:
|
|
104
|
+
self._session = session
|
|
105
|
+
self._owns_session = False
|
|
106
|
+
else:
|
|
107
|
+
self._session = aiohttp.ClientSession(
|
|
108
|
+
timeout=self.timeout,
|
|
109
|
+
headers=self.headers
|
|
110
|
+
)
|
|
111
|
+
self._owns_session = True
|
|
112
|
+
|
|
113
|
+
# Discover agent card
|
|
114
|
+
self._agent_card = await self.discover()
|
|
115
|
+
self.logger.info(f"Connected to A2A agent: {self._agent_card.name}")
|
|
116
|
+
|
|
117
|
+
async def disconnect(self) -> None:
|
|
118
|
+
"""Close the connection."""
|
|
119
|
+
if self._session and self._owns_session:
|
|
120
|
+
await self._session.close()
|
|
121
|
+
self._session = None
|
|
122
|
+
self._agent_card = None
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def agent_card(self) -> Optional[AgentCard]:
|
|
126
|
+
"""Get the cached agent card."""
|
|
127
|
+
return self._agent_card
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def is_connected(self) -> bool:
|
|
131
|
+
return self._session is not None and not self._session.closed
|
|
132
|
+
|
|
133
|
+
# ─────────────────────────────────────────────────────────────
|
|
134
|
+
# Discovery
|
|
135
|
+
# ─────────────────────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
async def discover(self) -> AgentCard:
|
|
138
|
+
"""Fetch the remote agent's card."""
|
|
139
|
+
url = f"{self.base_url}/.well-known/agent.json"
|
|
140
|
+
|
|
141
|
+
async with self._session.get(url) as resp:
|
|
142
|
+
resp.raise_for_status()
|
|
143
|
+
data = await resp.json()
|
|
144
|
+
|
|
145
|
+
# Parse into AgentCard
|
|
146
|
+
skills = [
|
|
147
|
+
AgentSkill(
|
|
148
|
+
id=s.get("id", ""),
|
|
149
|
+
name=s.get("name", ""),
|
|
150
|
+
description=s.get("description", ""),
|
|
151
|
+
tags=s.get("tags", []),
|
|
152
|
+
input_schema=s.get("inputSchema"),
|
|
153
|
+
)
|
|
154
|
+
for s in data.get("skills", [])
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
return AgentCard(
|
|
158
|
+
name=data.get("name", "Unknown"),
|
|
159
|
+
description=data.get("description", ""),
|
|
160
|
+
version=data.get("version", "1.0.0"),
|
|
161
|
+
url=data.get("url") or self.base_url,
|
|
162
|
+
skills=skills,
|
|
163
|
+
protocol_version=data.get("protocolVersion", "0.3"),
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
def get_skills(self) -> List[AgentSkill]:
|
|
167
|
+
"""Get available skills from the remote agent."""
|
|
168
|
+
if not self._agent_card:
|
|
169
|
+
return []
|
|
170
|
+
return self._agent_card.skills
|
|
171
|
+
|
|
172
|
+
def get_skill(self, skill_id: str) -> Optional[AgentSkill]:
|
|
173
|
+
"""Get a specific skill by ID."""
|
|
174
|
+
for skill in self.get_skills():
|
|
175
|
+
if skill.id == skill_id:
|
|
176
|
+
return skill
|
|
177
|
+
return None
|
|
178
|
+
|
|
179
|
+
# ─────────────────────────────────────────────────────────────
|
|
180
|
+
# Messaging
|
|
181
|
+
# ─────────────────────────────────────────────────────────────
|
|
182
|
+
|
|
183
|
+
async def send_message(
|
|
184
|
+
self,
|
|
185
|
+
content: str,
|
|
186
|
+
*,
|
|
187
|
+
context_id: Optional[str] = None,
|
|
188
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
189
|
+
) -> Task:
|
|
190
|
+
"""
|
|
191
|
+
Send a message to the remote agent and wait for response.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
content: The message content
|
|
195
|
+
context_id: Optional context ID for multi-turn conversations
|
|
196
|
+
metadata: Optional metadata to include
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Task with the response
|
|
200
|
+
"""
|
|
201
|
+
url = f"{self.base_url}/a2a/message/send"
|
|
202
|
+
|
|
203
|
+
message = Message.user(content, context_id=context_id, metadata=metadata)
|
|
204
|
+
|
|
205
|
+
payload = {
|
|
206
|
+
"message": message.to_dict()
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async with self._session.post(url, json=payload) as resp:
|
|
210
|
+
resp.raise_for_status()
|
|
211
|
+
data = await resp.json()
|
|
212
|
+
|
|
213
|
+
return self._parse_task(data)
|
|
214
|
+
|
|
215
|
+
async def stream_message(
|
|
216
|
+
self,
|
|
217
|
+
content: str,
|
|
218
|
+
*,
|
|
219
|
+
context_id: Optional[str] = None,
|
|
220
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
221
|
+
) -> AsyncIterator[str]:
|
|
222
|
+
"""
|
|
223
|
+
Send a message and stream the response.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
content: The message content
|
|
227
|
+
context_id: Optional context ID
|
|
228
|
+
metadata: Optional metadata
|
|
229
|
+
|
|
230
|
+
Yields:
|
|
231
|
+
Text chunks as they arrive
|
|
232
|
+
"""
|
|
233
|
+
url = f"{self.base_url}/a2a/message/stream"
|
|
234
|
+
|
|
235
|
+
message = Message.user(content, context_id=context_id, metadata=metadata)
|
|
236
|
+
|
|
237
|
+
payload = {
|
|
238
|
+
"message": message.to_dict()
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async with self._session.post(
|
|
242
|
+
url,
|
|
243
|
+
json=payload,
|
|
244
|
+
headers={"Accept": "text/event-stream"}
|
|
245
|
+
) as resp:
|
|
246
|
+
resp.raise_for_status()
|
|
247
|
+
|
|
248
|
+
async for line in resp.content:
|
|
249
|
+
line = line.decode("utf-8").strip()
|
|
250
|
+
|
|
251
|
+
if not line or not line.startswith("data:"):
|
|
252
|
+
continue
|
|
253
|
+
|
|
254
|
+
try:
|
|
255
|
+
data = json.loads(line[5:].strip())
|
|
256
|
+
|
|
257
|
+
# Extract text from artifact updates
|
|
258
|
+
if "artifactUpdate" in data:
|
|
259
|
+
artifact = data["artifactUpdate"].get("artifact", {})
|
|
260
|
+
parts = artifact.get("parts", [])
|
|
261
|
+
for part in parts:
|
|
262
|
+
if "text" in part:
|
|
263
|
+
yield part["text"]
|
|
264
|
+
|
|
265
|
+
# Check for completion/failure
|
|
266
|
+
if "statusUpdate" in data:
|
|
267
|
+
status = data["statusUpdate"]
|
|
268
|
+
if status.get("final"):
|
|
269
|
+
state = status.get("status", {}).get("state")
|
|
270
|
+
if state == "failed":
|
|
271
|
+
error_msg = status.get("status", {}).get("message", {})
|
|
272
|
+
error_text = error_msg.get("parts", [{}])[0].get("text", "Unknown error")
|
|
273
|
+
raise RuntimeError(f"Remote agent failed: {error_text}")
|
|
274
|
+
break
|
|
275
|
+
|
|
276
|
+
except json.JSONDecodeError:
|
|
277
|
+
continue
|
|
278
|
+
|
|
279
|
+
async def invoke_skill(
|
|
280
|
+
self,
|
|
281
|
+
skill_id: str,
|
|
282
|
+
params: Optional[Dict[str, Any]] = None,
|
|
283
|
+
*,
|
|
284
|
+
context_id: Optional[str] = None,
|
|
285
|
+
) -> Any:
|
|
286
|
+
"""
|
|
287
|
+
Invoke a specific skill on the remote agent.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
skill_id: The skill ID to invoke
|
|
291
|
+
params: Parameters to pass to the skill
|
|
292
|
+
context_id: Optional context ID
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
The skill result (extracted from artifacts)
|
|
296
|
+
"""
|
|
297
|
+
message = Message.user(
|
|
298
|
+
{"skill": skill_id, "params": params or {}},
|
|
299
|
+
context_id=context_id
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
url = f"{self.base_url}/a2a/message/send"
|
|
303
|
+
payload = {"message": message.to_dict()}
|
|
304
|
+
|
|
305
|
+
async with self._session.post(url, json=payload) as resp:
|
|
306
|
+
resp.raise_for_status()
|
|
307
|
+
data = await resp.json()
|
|
308
|
+
|
|
309
|
+
task = self._parse_task(data)
|
|
310
|
+
|
|
311
|
+
if task.status.state == TaskState.FAILED:
|
|
312
|
+
error_msg = task.status.message.get_text() if task.status.message else "Unknown error"
|
|
313
|
+
raise RuntimeError(f"Skill invocation failed: {error_msg}")
|
|
314
|
+
|
|
315
|
+
# Extract result from artifacts
|
|
316
|
+
if task.artifacts:
|
|
317
|
+
artifact = task.artifacts[0]
|
|
318
|
+
if artifact.parts:
|
|
319
|
+
part = artifact.parts[0]
|
|
320
|
+
if part.data:
|
|
321
|
+
return part.data
|
|
322
|
+
return part.text
|
|
323
|
+
|
|
324
|
+
return None
|
|
325
|
+
|
|
326
|
+
# ─────────────────────────────────────────────────────────────
|
|
327
|
+
# Task Management
|
|
328
|
+
# ─────────────────────────────────────────────────────────────
|
|
329
|
+
|
|
330
|
+
async def get_task(self, task_id: str) -> Task:
|
|
331
|
+
"""Get a task by ID."""
|
|
332
|
+
url = f"{self.base_url}/a2a/tasks/{task_id}"
|
|
333
|
+
|
|
334
|
+
async with self._session.get(url) as resp:
|
|
335
|
+
resp.raise_for_status()
|
|
336
|
+
data = await resp.json()
|
|
337
|
+
|
|
338
|
+
return self._parse_task(data)
|
|
339
|
+
|
|
340
|
+
async def list_tasks(
|
|
341
|
+
self,
|
|
342
|
+
context_id: Optional[str] = None,
|
|
343
|
+
status: Optional[str] = None,
|
|
344
|
+
page_size: int = 50,
|
|
345
|
+
) -> List[Task]:
|
|
346
|
+
"""List tasks with optional filtering."""
|
|
347
|
+
url = f"{self.base_url}/a2a/tasks"
|
|
348
|
+
params = {"pageSize": page_size}
|
|
349
|
+
if context_id:
|
|
350
|
+
params["contextId"] = context_id
|
|
351
|
+
if status:
|
|
352
|
+
params["status"] = status
|
|
353
|
+
|
|
354
|
+
async with self._session.get(url, params=params) as resp:
|
|
355
|
+
resp.raise_for_status()
|
|
356
|
+
data = await resp.json()
|
|
357
|
+
|
|
358
|
+
return [self._parse_task(t) for t in data.get("tasks", [])]
|
|
359
|
+
|
|
360
|
+
async def cancel_task(self, task_id: str) -> Task:
|
|
361
|
+
"""Cancel a running task."""
|
|
362
|
+
url = f"{self.base_url}/a2a/tasks/{task_id}/cancel"
|
|
363
|
+
|
|
364
|
+
async with self._session.post(url) as resp:
|
|
365
|
+
resp.raise_for_status()
|
|
366
|
+
data = await resp.json()
|
|
367
|
+
|
|
368
|
+
return self._parse_task(data)
|
|
369
|
+
|
|
370
|
+
# ─────────────────────────────────────────────────────────────
|
|
371
|
+
# JSON-RPC
|
|
372
|
+
# ─────────────────────────────────────────────────────────────
|
|
373
|
+
|
|
374
|
+
async def rpc_call(
|
|
375
|
+
self,
|
|
376
|
+
method: str,
|
|
377
|
+
params: Optional[Dict[str, Any]] = None,
|
|
378
|
+
) -> Any:
|
|
379
|
+
"""Make a JSON-RPC call to the remote agent."""
|
|
380
|
+
url = f"{self.base_url}/a2a/rpc"
|
|
381
|
+
|
|
382
|
+
payload = {
|
|
383
|
+
"jsonrpc": "2.0",
|
|
384
|
+
"id": str(id(self)),
|
|
385
|
+
"method": method,
|
|
386
|
+
"params": params or {}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async with self._session.post(url, json=payload) as resp:
|
|
390
|
+
resp.raise_for_status()
|
|
391
|
+
data = await resp.json()
|
|
392
|
+
|
|
393
|
+
if "error" in data:
|
|
394
|
+
raise RuntimeError(f"RPC error: {data['error']}")
|
|
395
|
+
|
|
396
|
+
return data.get("result")
|
|
397
|
+
|
|
398
|
+
# ─────────────────────────────────────────────────────────────
|
|
399
|
+
# Helpers
|
|
400
|
+
# ─────────────────────────────────────────────────────────────
|
|
401
|
+
|
|
402
|
+
def _parse_task(self, data: Dict[str, Any]) -> Task:
|
|
403
|
+
"""Parse a task from JSON response."""
|
|
404
|
+
status_data = data.get("status", {})
|
|
405
|
+
status = TaskStatus(
|
|
406
|
+
state=TaskState(status_data.get("state", "submitted")),
|
|
407
|
+
message=Message.from_dict(status_data["message"]) if status_data.get("message") else None,
|
|
408
|
+
timestamp=status_data.get("timestamp"),
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
artifacts = []
|
|
412
|
+
for a in data.get("artifacts", []):
|
|
413
|
+
parts = [Part.from_dict(p) for p in a.get("parts", [])]
|
|
414
|
+
artifacts.append(Artifact(
|
|
415
|
+
artifact_id=a.get("artifactId", ""),
|
|
416
|
+
name=a.get("name"),
|
|
417
|
+
description=a.get("description"),
|
|
418
|
+
parts=parts,
|
|
419
|
+
metadata=a.get("metadata"),
|
|
420
|
+
))
|
|
421
|
+
|
|
422
|
+
history = [Message.from_dict(m) for m in data.get("history", [])]
|
|
423
|
+
|
|
424
|
+
return Task(
|
|
425
|
+
id=data.get("id", ""),
|
|
426
|
+
context_id=data.get("contextId", ""),
|
|
427
|
+
status=status,
|
|
428
|
+
artifacts=artifacts,
|
|
429
|
+
history=history,
|
|
430
|
+
metadata=data.get("metadata"),
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
# ─────────────────────────────────────────────────────────────
|
|
435
|
+
# A2A Remote Tools - AbstractTool Implementations
|
|
436
|
+
# ─────────────────────────────────────────────────────────────
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
class A2ARemoteAgentInput(AbstractToolArgsSchema):
|
|
440
|
+
"""Input schema for A2A remote agent tool."""
|
|
441
|
+
question: str = Field(
|
|
442
|
+
...,
|
|
443
|
+
description="The question to ask the remote A2A agent"
|
|
444
|
+
)
|
|
445
|
+
context_id: Optional[str] = Field(
|
|
446
|
+
None,
|
|
447
|
+
description="Optional context ID for multi-turn conversations"
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
class A2ARemoteAgentTool(AbstractTool):
|
|
452
|
+
"""
|
|
453
|
+
Wraps a remote A2A agent as a tool that can be used by local agents.
|
|
454
|
+
|
|
455
|
+
This creates a tool that, when invoked, sends the query to the remote agent.
|
|
456
|
+
Properly inherits from AbstractTool for ToolManager compatibility.
|
|
457
|
+
"""
|
|
458
|
+
|
|
459
|
+
name: str = "ask_remote_agent"
|
|
460
|
+
description: str = "Ask a question to a remote A2A agent"
|
|
461
|
+
args_schema: Type[BaseModel] = A2ARemoteAgentInput
|
|
462
|
+
|
|
463
|
+
def __init__(
|
|
464
|
+
self,
|
|
465
|
+
client: A2AClient,
|
|
466
|
+
*,
|
|
467
|
+
tool_name: Optional[str] = None,
|
|
468
|
+
tool_description: Optional[str] = None,
|
|
469
|
+
use_streaming: bool = False,
|
|
470
|
+
**kwargs
|
|
471
|
+
):
|
|
472
|
+
"""
|
|
473
|
+
Initialize A2A Remote Agent Tool.
|
|
474
|
+
|
|
475
|
+
Args:
|
|
476
|
+
client: Connected A2AClient instance
|
|
477
|
+
tool_name: Custom name for the tool (defaults to ask_<agent_name>)
|
|
478
|
+
tool_description: Custom description (defaults to agent card description)
|
|
479
|
+
use_streaming: Whether to use streaming for responses
|
|
480
|
+
"""
|
|
481
|
+
self.client = client
|
|
482
|
+
self.use_streaming = use_streaming
|
|
483
|
+
self.tags = ["a2a", "remote-agent"]
|
|
484
|
+
|
|
485
|
+
card = client.agent_card
|
|
486
|
+
name = tool_name or f"ask_{card.name.lower().replace(' ', '_')}"
|
|
487
|
+
description = tool_description or (
|
|
488
|
+
f"Ask the remote agent '{card.name}': {card.description}"
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
# Store for cloning
|
|
492
|
+
self._tool_name_override = tool_name
|
|
493
|
+
self._tool_description_override = tool_description
|
|
494
|
+
|
|
495
|
+
super().__init__(name=name, description=description, **kwargs)
|
|
496
|
+
|
|
497
|
+
def _get_clone_kwargs(self) -> Dict[str, Any]:
|
|
498
|
+
"""Get kwargs for cloning this tool."""
|
|
499
|
+
base_kwargs = super()._get_clone_kwargs()
|
|
500
|
+
base_kwargs.update({
|
|
501
|
+
'tool_name': self._tool_name_override,
|
|
502
|
+
'tool_description': self._tool_description_override,
|
|
503
|
+
'use_streaming': self.use_streaming,
|
|
504
|
+
})
|
|
505
|
+
return base_kwargs
|
|
506
|
+
|
|
507
|
+
def clone(self) -> "A2ARemoteAgentTool":
|
|
508
|
+
"""Clone this tool (shares the client reference)."""
|
|
509
|
+
clone_kwargs = self._get_clone_kwargs()
|
|
510
|
+
# Remove standard AbstractTool params that we handle differently
|
|
511
|
+
clone_kwargs.pop('name', None)
|
|
512
|
+
clone_kwargs.pop('description', None)
|
|
513
|
+
return A2ARemoteAgentTool(client=self.client, **clone_kwargs)
|
|
514
|
+
|
|
515
|
+
async def _execute(self, question: str, context_id: Optional[str] = None, **kwargs) -> str:
|
|
516
|
+
"""Execute the tool by sending a message to the remote agent."""
|
|
517
|
+
# Also support session_id as an alias
|
|
518
|
+
ctx_id = context_id or kwargs.get("session_id")
|
|
519
|
+
|
|
520
|
+
if self.use_streaming:
|
|
521
|
+
chunks = []
|
|
522
|
+
async for chunk in self.client.stream_message(question, context_id=ctx_id):
|
|
523
|
+
chunks.append(chunk)
|
|
524
|
+
return "".join(chunks)
|
|
525
|
+
else:
|
|
526
|
+
task = await self.client.send_message(question, context_id=ctx_id)
|
|
527
|
+
|
|
528
|
+
if task.status.state == TaskState.FAILED:
|
|
529
|
+
error = task.status.message.get_text() if task.status.message else "Unknown error"
|
|
530
|
+
return f"Error from remote agent: {error}"
|
|
531
|
+
|
|
532
|
+
if task.artifacts:
|
|
533
|
+
return task.artifacts[0].parts[0].text if task.artifacts[0].parts else ""
|
|
534
|
+
|
|
535
|
+
return ""
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def _create_skill_input_model(skill: AgentSkill) -> Type[AbstractToolArgsSchema]:
|
|
539
|
+
"""
|
|
540
|
+
Dynamically create a Pydantic model from an AgentSkill's input_schema.
|
|
541
|
+
|
|
542
|
+
Args:
|
|
543
|
+
skill: The AgentSkill with optional input_schema
|
|
544
|
+
|
|
545
|
+
Returns:
|
|
546
|
+
A Pydantic model class for the skill's inputs
|
|
547
|
+
"""
|
|
548
|
+
# Base fields that are always available
|
|
549
|
+
field_definitions: Dict[str, Any] = {
|
|
550
|
+
'context_id': (
|
|
551
|
+
Optional[str],
|
|
552
|
+
Field(None, description="Optional context ID for multi-turn conversations")
|
|
553
|
+
),
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
# Parse input_schema if available
|
|
557
|
+
if skill.input_schema and isinstance(skill.input_schema, dict):
|
|
558
|
+
properties = skill.input_schema.get('properties', {})
|
|
559
|
+
required = skill.input_schema.get('required', [])
|
|
560
|
+
|
|
561
|
+
for prop_name, prop_info in properties.items():
|
|
562
|
+
if prop_name == 'context_id':
|
|
563
|
+
continue # Already handled
|
|
564
|
+
|
|
565
|
+
prop_type = prop_info.get('type', 'string')
|
|
566
|
+
prop_desc = prop_info.get('description', f"Parameter: {prop_name}")
|
|
567
|
+
|
|
568
|
+
# Map JSON schema types to Python types
|
|
569
|
+
type_mapping = {
|
|
570
|
+
'string': str,
|
|
571
|
+
'integer': int,
|
|
572
|
+
'number': float,
|
|
573
|
+
'boolean': bool,
|
|
574
|
+
'array': list,
|
|
575
|
+
'object': dict,
|
|
576
|
+
}
|
|
577
|
+
python_type = type_mapping.get(prop_type, str)
|
|
578
|
+
|
|
579
|
+
if prop_name in required:
|
|
580
|
+
field_definitions[prop_name] = (
|
|
581
|
+
python_type,
|
|
582
|
+
Field(..., description=prop_desc)
|
|
583
|
+
)
|
|
584
|
+
else:
|
|
585
|
+
field_definitions[prop_name] = (
|
|
586
|
+
Optional[python_type],
|
|
587
|
+
Field(None, description=prop_desc)
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
# Create dynamic model
|
|
591
|
+
model_name = f"{skill.name.replace(' ', '')}Input"
|
|
592
|
+
# Use create_model from pydantic if available, otherwise use type
|
|
593
|
+
try:
|
|
594
|
+
from pydantic import create_model
|
|
595
|
+
return create_model(model_name, __base__=AbstractToolArgsSchema, **field_definitions)
|
|
596
|
+
except ImportError:
|
|
597
|
+
# Fallback: create a simple class
|
|
598
|
+
return type(model_name, (AbstractToolArgsSchema,), {
|
|
599
|
+
'__annotations__': {k: v[0] for k, v in field_definitions.items()}
|
|
600
|
+
})
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
class A2ARemoteSkillTool(AbstractTool):
|
|
604
|
+
"""
|
|
605
|
+
Wraps a specific skill from a remote A2A agent as a tool.
|
|
606
|
+
|
|
607
|
+
Properly inherits from AbstractTool for ToolManager compatibility.
|
|
608
|
+
Dynamically generates input schema from the skill's input_schema.
|
|
609
|
+
"""
|
|
610
|
+
|
|
611
|
+
name: str = "remote_skill"
|
|
612
|
+
description: str = "Invoke a remote skill"
|
|
613
|
+
args_schema: Type[BaseModel] = AbstractToolArgsSchema
|
|
614
|
+
|
|
615
|
+
def __init__(
|
|
616
|
+
self,
|
|
617
|
+
client: A2AClient,
|
|
618
|
+
skill: AgentSkill,
|
|
619
|
+
**kwargs
|
|
620
|
+
):
|
|
621
|
+
"""
|
|
622
|
+
Initialize A2A Remote Skill Tool.
|
|
623
|
+
|
|
624
|
+
Args:
|
|
625
|
+
client: Connected A2AClient instance
|
|
626
|
+
skill: The AgentSkill to wrap as a tool
|
|
627
|
+
"""
|
|
628
|
+
self.client = client
|
|
629
|
+
self.skill = skill
|
|
630
|
+
self.tags = ["a2a", "remote-skill"] + skill.tags
|
|
631
|
+
|
|
632
|
+
name = f"remote_{skill.id}"
|
|
633
|
+
description = skill.description or f"Invoke remote skill: {skill.name}"
|
|
634
|
+
|
|
635
|
+
# Dynamically create the args schema from skill.input_schema
|
|
636
|
+
self.args_schema = _create_skill_input_model(skill)
|
|
637
|
+
|
|
638
|
+
super().__init__(name=name, description=description, **kwargs)
|
|
639
|
+
|
|
640
|
+
def _get_clone_kwargs(self) -> Dict[str, Any]:
|
|
641
|
+
"""Get kwargs for cloning this tool."""
|
|
642
|
+
base_kwargs = super()._get_clone_kwargs()
|
|
643
|
+
# Remove AbstractTool params we handle in __init__
|
|
644
|
+
base_kwargs.pop('name', None)
|
|
645
|
+
base_kwargs.pop('description', None)
|
|
646
|
+
return base_kwargs
|
|
647
|
+
|
|
648
|
+
def clone(self) -> "A2ARemoteSkillTool":
|
|
649
|
+
"""Clone this tool (shares the client and skill references)."""
|
|
650
|
+
clone_kwargs = self._get_clone_kwargs()
|
|
651
|
+
return A2ARemoteSkillTool(client=self.client, skill=self.skill, **clone_kwargs)
|
|
652
|
+
|
|
653
|
+
async def _execute(self, context_id: Optional[str] = None, **kwargs) -> Any:
|
|
654
|
+
"""Execute the remote skill."""
|
|
655
|
+
# Also support session_id as an alias
|
|
656
|
+
ctx_id = context_id or kwargs.pop("session_id", None)
|
|
657
|
+
return await self.client.invoke_skill(self.skill.id, kwargs, context_id=ctx_id)
|
|
658
|
+
|