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,442 @@
|
|
|
1
|
+
# parrot/tools/shell_tool.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from typing import Any, Dict, List, Optional, Union
|
|
4
|
+
import asyncio
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import json
|
|
8
|
+
from ..abstract import AbstractTool
|
|
9
|
+
from .models import CommandObject, ShellToolArgs, PlanStep
|
|
10
|
+
from .actions import (
|
|
11
|
+
BaseAction,
|
|
12
|
+
ActionResult,
|
|
13
|
+
RunCommand,
|
|
14
|
+
ExecFile,
|
|
15
|
+
ListFiles,
|
|
16
|
+
ReadFile,
|
|
17
|
+
WriteFile,
|
|
18
|
+
DeleteFile,
|
|
19
|
+
CopyFile,
|
|
20
|
+
MoveFile,
|
|
21
|
+
CheckExists
|
|
22
|
+
)
|
|
23
|
+
from .engine import EvalAction
|
|
24
|
+
|
|
25
|
+
class ShellTool(AbstractTool):
|
|
26
|
+
"""
|
|
27
|
+
Interactive Shell tool with optional PTY support.
|
|
28
|
+
|
|
29
|
+
Features:
|
|
30
|
+
- Accepts single string, list of strings, or list of command objects
|
|
31
|
+
- Plan-mode (tiny sequential DAG) with `uses` and templating
|
|
32
|
+
- Sequential or parallel execution
|
|
33
|
+
- Per-command and global timeouts
|
|
34
|
+
- Global and per-command work_dir, env
|
|
35
|
+
- Optional PTY mode for interactive programs (merged stdout/stderr)
|
|
36
|
+
- Live output callback hook
|
|
37
|
+
"""
|
|
38
|
+
name: str = "shell"
|
|
39
|
+
description: str = "Execute shell commands with optional PTY, sequential/parallel control, and rich command objects."
|
|
40
|
+
args_schema = ShellToolArgs
|
|
41
|
+
|
|
42
|
+
def _default_output_dir(self):
|
|
43
|
+
# This tool doesn't produce files by default
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
async def _execute(
|
|
47
|
+
self,
|
|
48
|
+
command: Optional[Union[str, List[Union[str, Dict[str, Any]]]]] = None,
|
|
49
|
+
plan: Optional[List[Dict[str, Any]]] = None,
|
|
50
|
+
parallel: bool = False,
|
|
51
|
+
ignore_errors: bool = False,
|
|
52
|
+
timeout: Optional[int] = None,
|
|
53
|
+
work_dir: Optional[str] = None,
|
|
54
|
+
pty: bool = False,
|
|
55
|
+
env: Optional[Dict[str, str]] = None,
|
|
56
|
+
non_interactive: bool = False
|
|
57
|
+
) -> Dict[str, Any]:
|
|
58
|
+
if plan:
|
|
59
|
+
return await self._run_plan(
|
|
60
|
+
plan=plan,
|
|
61
|
+
ignore_errors=ignore_errors,
|
|
62
|
+
timeout=timeout,
|
|
63
|
+
work_dir=work_dir or os.getcwd(),
|
|
64
|
+
pty=pty,
|
|
65
|
+
env=env or {},
|
|
66
|
+
non_interactive=non_interactive
|
|
67
|
+
)
|
|
68
|
+
if command is not None:
|
|
69
|
+
return await self._run_commands(
|
|
70
|
+
command=command,
|
|
71
|
+
parallel=parallel,
|
|
72
|
+
ignore_errors=ignore_errors,
|
|
73
|
+
timeout=timeout,
|
|
74
|
+
work_dir=work_dir or os.getcwd(),
|
|
75
|
+
pty=pty,
|
|
76
|
+
env=env or {},
|
|
77
|
+
non_interactive=non_interactive
|
|
78
|
+
)
|
|
79
|
+
return {"ok": True, "results": []}
|
|
80
|
+
|
|
81
|
+
# ---- helpers ----
|
|
82
|
+
def _normalize_to_objects(
|
|
83
|
+
self,
|
|
84
|
+
command: Union[str, List[Union[str, CommandObject, Dict[str, Any]]]]
|
|
85
|
+
) -> List[CommandObject]:
|
|
86
|
+
if isinstance(command, str):
|
|
87
|
+
return [CommandObject(command=command)]
|
|
88
|
+
objs: List[CommandObject] = []
|
|
89
|
+
for item in command:
|
|
90
|
+
if isinstance(item, str):
|
|
91
|
+
objs.append(CommandObject(command=item))
|
|
92
|
+
elif isinstance(item, dict):
|
|
93
|
+
objs.append(CommandObject(**item))
|
|
94
|
+
elif isinstance(item, CommandObject):
|
|
95
|
+
objs.append(item)
|
|
96
|
+
else:
|
|
97
|
+
raise ValueError(f"Unsupported command element: {type(item)}")
|
|
98
|
+
return objs
|
|
99
|
+
|
|
100
|
+
def _live_cb(self, line: str, is_stderr: bool, cmd: str):
|
|
101
|
+
try:
|
|
102
|
+
self.logger.debug(f"[{cmd}] {'STDERR' if is_stderr else 'STDOUT'}: {line.rstrip()}")
|
|
103
|
+
except Exception:
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
# ---- classic command mode ----
|
|
107
|
+
async def _run_commands(
|
|
108
|
+
self,
|
|
109
|
+
*,
|
|
110
|
+
command: Union[str, List[Union[str, Dict[str, Any]]]],
|
|
111
|
+
parallel: bool,
|
|
112
|
+
ignore_errors: bool,
|
|
113
|
+
timeout: Optional[int],
|
|
114
|
+
work_dir: str,
|
|
115
|
+
pty: bool,
|
|
116
|
+
env: Dict[str, str],
|
|
117
|
+
non_interactive: bool
|
|
118
|
+
) -> Dict[str, Any]:
|
|
119
|
+
cmds = self._normalize_to_objects(command)
|
|
120
|
+
actions = [
|
|
121
|
+
self._make_action_from_cmdobj(
|
|
122
|
+
c, timeout, work_dir, env, pty, non_interactive, ignore_errors
|
|
123
|
+
) for c in cmds
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
results: List[ActionResult] = []
|
|
127
|
+
if parallel and len(actions) > 1:
|
|
128
|
+
results = await asyncio.gather(*[a.run() for a in actions])
|
|
129
|
+
else:
|
|
130
|
+
for act in actions:
|
|
131
|
+
r = await act.run()
|
|
132
|
+
results.append(r)
|
|
133
|
+
if not r.ok and not (ignore_errors or act.ignore_errors):
|
|
134
|
+
break
|
|
135
|
+
|
|
136
|
+
payload = [self._result_to_dict(r) for r in results]
|
|
137
|
+
overall_ok = all(item["ok"] for item in payload) if payload else True
|
|
138
|
+
return {"ok": overall_ok, "parallel": parallel, "results": payload}
|
|
139
|
+
|
|
140
|
+
def _make_action_from_cmdobj(
|
|
141
|
+
self,
|
|
142
|
+
spec: CommandObject,
|
|
143
|
+
default_timeout: Optional[int],
|
|
144
|
+
base_work_dir: str,
|
|
145
|
+
base_env: Dict[str, str],
|
|
146
|
+
tool_level_pty: bool,
|
|
147
|
+
tool_level_non_interactive: bool,
|
|
148
|
+
tool_level_ignore_errors: bool
|
|
149
|
+
) -> BaseAction:
|
|
150
|
+
timeout = spec.timeout if spec.timeout is not None else default_timeout
|
|
151
|
+
work_dir = spec.work_dir or base_work_dir
|
|
152
|
+
env = dict(base_env)
|
|
153
|
+
if spec.env:
|
|
154
|
+
env.update(spec.env)
|
|
155
|
+
pty_mode = tool_level_pty if spec.pty is None else bool(spec.pty)
|
|
156
|
+
non_interactive = tool_level_non_interactive if spec.non_interactive is None else bool(spec.non_interactive)
|
|
157
|
+
ignore_errors = tool_level_ignore_errors if spec.ignore_errors is None else bool(spec.ignore_errors)
|
|
158
|
+
|
|
159
|
+
raw = spec.command.strip()
|
|
160
|
+
if raw.startswith("ls") or raw == "ls":
|
|
161
|
+
return ListFiles(
|
|
162
|
+
type_name="list_files", cmd=raw, work_dir=work_dir, timeout=timeout,
|
|
163
|
+
env=env, pty_mode=pty_mode, stdin_lines=spec.stdin or [],
|
|
164
|
+
non_interactive=non_interactive, ignore_errors=ignore_errors,
|
|
165
|
+
live_callback=self._live_cb
|
|
166
|
+
)
|
|
167
|
+
elif raw.endswith(".sh") or raw.startswith("./") or raw.startswith("/"):
|
|
168
|
+
return ExecFile(
|
|
169
|
+
type_name="exec_file", cmd=raw, work_dir=work_dir, timeout=timeout,
|
|
170
|
+
env=env, pty_mode=pty_mode, stdin_lines=spec.stdin or [],
|
|
171
|
+
non_interactive=non_interactive, ignore_errors=ignore_errors,
|
|
172
|
+
live_callback=self._live_cb
|
|
173
|
+
)
|
|
174
|
+
else:
|
|
175
|
+
return RunCommand(
|
|
176
|
+
type_name="run_command", cmd=raw, work_dir=work_dir, timeout=timeout,
|
|
177
|
+
env=env, pty_mode=pty_mode, stdin_lines=spec.stdin or [],
|
|
178
|
+
non_interactive=non_interactive, ignore_errors=ignore_errors,
|
|
179
|
+
live_callback=self._live_cb
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
def _result_to_dict(self, r: ActionResult) -> Dict[str, Any]:
|
|
183
|
+
return {
|
|
184
|
+
"type": r.type,
|
|
185
|
+
"cmd": r.cmd,
|
|
186
|
+
"work_dir": r.work_dir,
|
|
187
|
+
"ok": r.ok,
|
|
188
|
+
"exit_code": r.exit_code,
|
|
189
|
+
"timed_out": r.timed_out,
|
|
190
|
+
"duration": round(r.duration, 4),
|
|
191
|
+
"stdout": r.stdout,
|
|
192
|
+
"stderr": r.stderr,
|
|
193
|
+
"metadata": r.metadata
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
# ---- plan mode ----
|
|
197
|
+
async def _run_plan(
|
|
198
|
+
self,
|
|
199
|
+
*,
|
|
200
|
+
plan: List[Dict[str, Any]],
|
|
201
|
+
ignore_errors: bool,
|
|
202
|
+
timeout: Optional[int],
|
|
203
|
+
work_dir: str,
|
|
204
|
+
pty: bool,
|
|
205
|
+
env: Dict[str, str],
|
|
206
|
+
non_interactive: bool
|
|
207
|
+
) -> Dict[str, Any]:
|
|
208
|
+
steps = [PlanStep(**s) if not isinstance(s, PlanStep) else s for s in plan]
|
|
209
|
+
results: List[ActionResult] = []
|
|
210
|
+
base_ctx = {"results": results} # live context for templating / uses
|
|
211
|
+
|
|
212
|
+
for idx, step in enumerate(steps):
|
|
213
|
+
# Resolve per-step overrides
|
|
214
|
+
step_timeout = step.timeout if step.timeout is not None else timeout
|
|
215
|
+
step_work_dir = step.work_dir or work_dir
|
|
216
|
+
step_env = dict(env)
|
|
217
|
+
if step.env:
|
|
218
|
+
step_env.update(step.env)
|
|
219
|
+
step_pty = pty if step.pty is None else bool(step.pty)
|
|
220
|
+
step_non_interactive = non_interactive if step.non_interactive is None else bool(step.non_interactive)
|
|
221
|
+
step_ignore = ignore_errors if step.ignore_errors is None else bool(step.ignore_errors)
|
|
222
|
+
|
|
223
|
+
# Prepare context for templating / uses
|
|
224
|
+
src = self._resolve_uses(step.uses, results) if step.uses else None
|
|
225
|
+
rendered = self._render_template(step.template, src, results) if step.template else None
|
|
226
|
+
|
|
227
|
+
# Build action by type
|
|
228
|
+
action: BaseAction
|
|
229
|
+
if step.type in ("run_command", "exec_file", "list_files"):
|
|
230
|
+
# unify into command list
|
|
231
|
+
command_spec = step.command or rendered or ""
|
|
232
|
+
objs = self._normalize_to_objects(command_spec)
|
|
233
|
+
# Only one command allowed per plan step for now to keep step/result alignment clean
|
|
234
|
+
if len(objs) != 1:
|
|
235
|
+
raise ValueError(f"Plan step {idx} expects exactly one command; got {len(objs)}.")
|
|
236
|
+
action = self._make_action_from_cmdobj(
|
|
237
|
+
objs[0], step_timeout, step_work_dir, step_env, step_pty, step_non_interactive, step_ignore
|
|
238
|
+
)
|
|
239
|
+
# Force type to requested for accurate step labeling
|
|
240
|
+
action.type_name = step.type
|
|
241
|
+
|
|
242
|
+
elif step.type == "check_exists":
|
|
243
|
+
target = (step.path or rendered or "").strip()
|
|
244
|
+
action = CheckExists(
|
|
245
|
+
type_name="check_exists",
|
|
246
|
+
cmd=target,
|
|
247
|
+
work_dir=step_work_dir,
|
|
248
|
+
ignore_errors=step_ignore
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
elif step.type == "read_file":
|
|
252
|
+
target = (step.path or rendered or "").strip()
|
|
253
|
+
rf = ReadFile(
|
|
254
|
+
type_name="read_file",
|
|
255
|
+
cmd=target,
|
|
256
|
+
work_dir=step_work_dir,
|
|
257
|
+
ignore_errors=step_ignore
|
|
258
|
+
)
|
|
259
|
+
# attach options
|
|
260
|
+
setattr(rf, "_max_bytes", step.max_bytes)
|
|
261
|
+
setattr(rf, "_encoding", step.encoding or "utf-8")
|
|
262
|
+
action = rf
|
|
263
|
+
elif step.type == "write_file":
|
|
264
|
+
target = (step.path or rendered or "").strip()
|
|
265
|
+
if not target:
|
|
266
|
+
raise ValueError(f"Plan step {idx}: write_file requires 'path'.")
|
|
267
|
+
content_src: Any = rendered if rendered is not None else (src if src is not None else step.content or "")
|
|
268
|
+
# If content is not a string, JSON-dump it for safety
|
|
269
|
+
if not isinstance(content_src, str):
|
|
270
|
+
try:
|
|
271
|
+
content_str = json.dumps(content_src, ensure_ascii=False)
|
|
272
|
+
except Exception:
|
|
273
|
+
content_str = str(content_src)
|
|
274
|
+
else:
|
|
275
|
+
content_str = content_src
|
|
276
|
+
|
|
277
|
+
action = WriteFile(
|
|
278
|
+
path=target,
|
|
279
|
+
content=content_str,
|
|
280
|
+
encoding=step.encoding or "utf-8",
|
|
281
|
+
append=bool(step.append),
|
|
282
|
+
make_dirs=bool(step.make_dirs if step.make_dirs is not None else True),
|
|
283
|
+
overwrite=bool(step.overwrite if step.overwrite is not None else True),
|
|
284
|
+
work_dir=step_work_dir,
|
|
285
|
+
ignore_errors=step_ignore
|
|
286
|
+
)
|
|
287
|
+
elif step.type == "delete_file":
|
|
288
|
+
target = (step.path or rendered or "").strip()
|
|
289
|
+
if not target:
|
|
290
|
+
raise ValueError(f"Plan step {idx}: delete_file requires 'path'.")
|
|
291
|
+
action = DeleteFile(
|
|
292
|
+
path=target,
|
|
293
|
+
recursive=bool(step.recursive),
|
|
294
|
+
missing_ok=bool(step.missing_ok if step.missing_ok is not None else True),
|
|
295
|
+
work_dir=step_work_dir,
|
|
296
|
+
ignore_errors=step_ignore
|
|
297
|
+
)
|
|
298
|
+
elif step.type == "copy_file":
|
|
299
|
+
src = (step.path or rendered or "").strip()
|
|
300
|
+
dest = (step.dest or "").strip()
|
|
301
|
+
if not src or not dest:
|
|
302
|
+
raise ValueError(f"Plan step {idx}: copy_file requires 'path' (src) and 'dest'.")
|
|
303
|
+
action = CopyFile(
|
|
304
|
+
src=src,
|
|
305
|
+
dest=dest,
|
|
306
|
+
recursive=bool(step.recursive),
|
|
307
|
+
overwrite=bool(step.overwrite if step.overwrite is not None else True),
|
|
308
|
+
make_dirs=bool(step.make_dirs if step.make_dirs is not None else True),
|
|
309
|
+
work_dir=step_work_dir,
|
|
310
|
+
ignore_errors=step_ignore
|
|
311
|
+
)
|
|
312
|
+
elif step.type == "move_file":
|
|
313
|
+
src = (step.path or rendered or "").strip()
|
|
314
|
+
dest = (step.dest or "").strip()
|
|
315
|
+
if not src or not dest:
|
|
316
|
+
raise ValueError(f"Plan step {idx}: move_file requires 'path' (src) and 'dest'.")
|
|
317
|
+
action = MoveFile(
|
|
318
|
+
src=src,
|
|
319
|
+
dest=dest,
|
|
320
|
+
recursive=bool(step.recursive if step.recursive is not None else True),
|
|
321
|
+
overwrite=bool(step.overwrite if step.overwrite is not None else True),
|
|
322
|
+
make_dirs=bool(step.make_dirs if step.make_dirs is not None else True),
|
|
323
|
+
work_dir=step_work_dir,
|
|
324
|
+
ignore_errors=step_ignore
|
|
325
|
+
)
|
|
326
|
+
elif step.type == "eval":
|
|
327
|
+
eval_src: Any = rendered if rendered is not None else src
|
|
328
|
+
action = EvalAction(
|
|
329
|
+
eval_type=step.eval_type or "regex",
|
|
330
|
+
expr=step.expr or "",
|
|
331
|
+
group=step.group,
|
|
332
|
+
src_text_or_obj=eval_src,
|
|
333
|
+
as_json=bool(step.as_json),
|
|
334
|
+
work_dir=step_work_dir,
|
|
335
|
+
ignore_errors=step_ignore
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
else:
|
|
339
|
+
raise ValueError(
|
|
340
|
+
f"Unsupported plan step type: {step.type}"
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
# Run step
|
|
344
|
+
r = await action.run()
|
|
345
|
+
results.append(r)
|
|
346
|
+
if not r.ok and not (ignore_errors or step_ignore):
|
|
347
|
+
break
|
|
348
|
+
|
|
349
|
+
payload = [self._result_to_dict(r) for r in results]
|
|
350
|
+
overall_ok = all(item["ok"] for item in payload) if payload else True
|
|
351
|
+
return {"ok": overall_ok, "results": payload}
|
|
352
|
+
|
|
353
|
+
# ---- uses & templating ----
|
|
354
|
+
def _resolve_uses(self, uses: str, results: List[ActionResult]) -> Any:
|
|
355
|
+
"""
|
|
356
|
+
Accepts strings like:
|
|
357
|
+
- "prev.stdout"
|
|
358
|
+
- "prev"
|
|
359
|
+
- "result[0].stdout"
|
|
360
|
+
- "result[-1]"
|
|
361
|
+
Returns the object or string (stdout) referenced.
|
|
362
|
+
"""
|
|
363
|
+
uses = uses.strip()
|
|
364
|
+
if uses.startswith("prev"):
|
|
365
|
+
base = results[-1] if results else None
|
|
366
|
+
if base is None:
|
|
367
|
+
return ""
|
|
368
|
+
if uses == "prev":
|
|
369
|
+
return self._ar_to_src(base)
|
|
370
|
+
elif uses == "prev.stdout":
|
|
371
|
+
return base.stdout
|
|
372
|
+
elif uses == "prev.stderr":
|
|
373
|
+
return base.stderr
|
|
374
|
+
else:
|
|
375
|
+
return self._attr_path(self._ar_to_src(base), uses[len("prev."):])
|
|
376
|
+
m = re.match(r"result\[(\-?\d+)\](?:\.(stdout|stderr))?$", uses)
|
|
377
|
+
if m:
|
|
378
|
+
i = int(m.group(1))
|
|
379
|
+
if not results:
|
|
380
|
+
return ""
|
|
381
|
+
try:
|
|
382
|
+
base = results[i]
|
|
383
|
+
except IndexError:
|
|
384
|
+
return ""
|
|
385
|
+
stream = m.group(2)
|
|
386
|
+
if stream == "stdout":
|
|
387
|
+
return base.stdout
|
|
388
|
+
if stream == "stderr":
|
|
389
|
+
return base.stderr
|
|
390
|
+
return self._ar_to_src(base)
|
|
391
|
+
# Fallback: direct literal
|
|
392
|
+
return uses
|
|
393
|
+
|
|
394
|
+
@staticmethod
|
|
395
|
+
def _ar_to_src(ar: ActionResult) -> Dict[str, Any]:
|
|
396
|
+
return {
|
|
397
|
+
"type": ar.type,
|
|
398
|
+
"stdout": ar.stdout,
|
|
399
|
+
"stderr": ar.stderr,
|
|
400
|
+
"ok": ar.ok,
|
|
401
|
+
"exit_code": ar.exit_code,
|
|
402
|
+
"metadata": ar.metadata,
|
|
403
|
+
"cmd": ar.cmd,
|
|
404
|
+
"work_dir": ar.work_dir
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
@staticmethod
|
|
408
|
+
def _attr_path(obj: Any, path: str) -> Any:
|
|
409
|
+
# Just a minimal dot path for dicts
|
|
410
|
+
cur = obj
|
|
411
|
+
for part in path.split("."):
|
|
412
|
+
if isinstance(cur, dict) and part in cur:
|
|
413
|
+
cur = cur[part]
|
|
414
|
+
else:
|
|
415
|
+
return ""
|
|
416
|
+
return cur
|
|
417
|
+
|
|
418
|
+
def _render_template(self, template: str, src: Any, results: List[ActionResult]) -> str:
|
|
419
|
+
"""
|
|
420
|
+
Tiny Jinja-lite:
|
|
421
|
+
- {{ prev.stdout }}
|
|
422
|
+
- {{ result[-1].stdout }}
|
|
423
|
+
- {{ json(results) }} # dumps
|
|
424
|
+
"""
|
|
425
|
+
out = template
|
|
426
|
+
|
|
427
|
+
def repl_prev(m):
|
|
428
|
+
key = m.group(1).strip()
|
|
429
|
+
if key == "prev":
|
|
430
|
+
return json.dumps(self._ar_to_src(results[-1])) if results else ""
|
|
431
|
+
if key.startswith("prev."):
|
|
432
|
+
return str(self._resolve_uses(key, results) or "")
|
|
433
|
+
if key.startswith("result["):
|
|
434
|
+
return str(self._resolve_uses(key, results) or "")
|
|
435
|
+
if key == "json(results)":
|
|
436
|
+
return json.dumps([self._ar_to_src(r) for r in results], ensure_ascii=False)
|
|
437
|
+
if key == "src":
|
|
438
|
+
return json.dumps(src) if isinstance(src, (dict, list)) else (src or "")
|
|
439
|
+
return ""
|
|
440
|
+
# {{ ... }}
|
|
441
|
+
out = re.sub(r"\{\{\s*(.*?)\s*\}\}", repl_prev, out)
|
|
442
|
+
return out
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""SiteSearch tool for site-specific crawling with markdown output."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
import tempfile
|
|
6
|
+
import urllib.parse
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
import aiohttp
|
|
11
|
+
from bs4 import BeautifulSoup
|
|
12
|
+
from markitdown import MarkItDown
|
|
13
|
+
from pydantic import BaseModel, Field
|
|
14
|
+
|
|
15
|
+
from .google.tools import GoogleSiteSearchTool
|
|
16
|
+
from .scraping.driver import SeleniumSetup
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SiteSearchArgs(BaseModel):
|
|
20
|
+
"""Arguments schema for :class:`SiteSearch`."""
|
|
21
|
+
|
|
22
|
+
url: str = Field(
|
|
23
|
+
description="Base URL of the site to explore (e.g., https://www.statista.com/)",
|
|
24
|
+
)
|
|
25
|
+
query: str = Field(
|
|
26
|
+
description="Terms to search for within the provided site",
|
|
27
|
+
)
|
|
28
|
+
selectors: Optional[List[str]] = Field(
|
|
29
|
+
default=None,
|
|
30
|
+
description="Optional CSS selectors to extract specific page areas after rendering",
|
|
31
|
+
)
|
|
32
|
+
max_results: int = Field(
|
|
33
|
+
default=3,
|
|
34
|
+
ge=1,
|
|
35
|
+
le=10,
|
|
36
|
+
description="Maximum number of search results to process",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class SiteSearch(GoogleSiteSearchTool):
|
|
41
|
+
"""Perform Google-powered site searches and return rendered content as markdown."""
|
|
42
|
+
|
|
43
|
+
name = "site_search"
|
|
44
|
+
description = (
|
|
45
|
+
"Search within a given site and return fully-rendered page content as markdown, "
|
|
46
|
+
"including PDF conversion when encountered."
|
|
47
|
+
)
|
|
48
|
+
args_schema = SiteSearchArgs
|
|
49
|
+
|
|
50
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
51
|
+
super().__init__(**kwargs)
|
|
52
|
+
self._markitdown = MarkItDown()
|
|
53
|
+
self._selenium_setup: Optional[SeleniumSetup] = None
|
|
54
|
+
self._driver = None
|
|
55
|
+
|
|
56
|
+
async def _execute(
|
|
57
|
+
self,
|
|
58
|
+
url: str,
|
|
59
|
+
query: str,
|
|
60
|
+
selectors: Optional[List[str]] = None,
|
|
61
|
+
max_results: int = 3,
|
|
62
|
+
**_: Any,
|
|
63
|
+
) -> Dict[str, Any]:
|
|
64
|
+
site = self._extract_site(url)
|
|
65
|
+
if not site:
|
|
66
|
+
raise ValueError(f"Could not extract site from URL: {url}")
|
|
67
|
+
|
|
68
|
+
search_results = await super()._execute(
|
|
69
|
+
query=query,
|
|
70
|
+
site=site,
|
|
71
|
+
max_results=max_results,
|
|
72
|
+
preview=False,
|
|
73
|
+
preview_method="aiohttp",
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
processed_results = []
|
|
77
|
+
try:
|
|
78
|
+
for item in search_results.get("results", [])[:max_results]:
|
|
79
|
+
link = item.get("link")
|
|
80
|
+
if not link:
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
if await self._is_pdf(link):
|
|
84
|
+
markdown, content_type = await self._convert_pdf(link)
|
|
85
|
+
else:
|
|
86
|
+
markdown, content_type = await self._render_with_selenium(
|
|
87
|
+
link, selectors
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
processed_results.append(
|
|
91
|
+
{
|
|
92
|
+
"title": item.get("title"),
|
|
93
|
+
"url": link,
|
|
94
|
+
"snippet": item.get("snippet"),
|
|
95
|
+
"content_type": content_type,
|
|
96
|
+
"markdown": markdown,
|
|
97
|
+
}
|
|
98
|
+
)
|
|
99
|
+
finally:
|
|
100
|
+
await self._close_driver()
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
"site": site,
|
|
104
|
+
"search_terms": query,
|
|
105
|
+
"total_results": len(processed_results),
|
|
106
|
+
"results": processed_results,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async def _render_with_selenium(
|
|
110
|
+
self, url: str, selectors: Optional[List[str]]
|
|
111
|
+
) -> tuple[str, str]:
|
|
112
|
+
driver = await self._get_driver()
|
|
113
|
+
loop = asyncio.get_running_loop()
|
|
114
|
+
|
|
115
|
+
await loop.run_in_executor(None, driver.get, url)
|
|
116
|
+
await asyncio.sleep(2)
|
|
117
|
+
page_source = await loop.run_in_executor(None, lambda: driver.page_source)
|
|
118
|
+
|
|
119
|
+
soup = BeautifulSoup(page_source, "html.parser")
|
|
120
|
+
if selectors:
|
|
121
|
+
selected_html = []
|
|
122
|
+
for selector in selectors:
|
|
123
|
+
selected_html.extend([str(elem) for elem in soup.select(selector)])
|
|
124
|
+
html_content = "\n".join(selected_html) if selected_html else str(soup)
|
|
125
|
+
else:
|
|
126
|
+
html_content = str(soup)
|
|
127
|
+
|
|
128
|
+
markdown = await self._convert_html_to_markdown(html_content)
|
|
129
|
+
return markdown, "text/html"
|
|
130
|
+
|
|
131
|
+
async def _convert_pdf(self, url: str) -> tuple[str, str]:
|
|
132
|
+
timeout = aiohttp.ClientTimeout(total=60)
|
|
133
|
+
headers = {
|
|
134
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
|
|
135
|
+
"(KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
|
|
136
|
+
}
|
|
137
|
+
async with aiohttp.ClientSession(timeout=timeout, headers=headers) as session:
|
|
138
|
+
async with session.get(url) as response:
|
|
139
|
+
if response.status != 200:
|
|
140
|
+
return (f"Failed to download PDF (HTTP {response.status})", "application/pdf")
|
|
141
|
+
|
|
142
|
+
pdf_bytes = await response.read()
|
|
143
|
+
loop = asyncio.get_running_loop()
|
|
144
|
+
|
|
145
|
+
def convert() -> str:
|
|
146
|
+
with tempfile.NamedTemporaryFile(
|
|
147
|
+
mode="wb", suffix=".pdf", delete=False
|
|
148
|
+
) as tmp_file:
|
|
149
|
+
tmp_file.write(pdf_bytes)
|
|
150
|
+
tmp_path = Path(tmp_file.name)
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
result = self._markitdown.convert(str(tmp_path))
|
|
154
|
+
markdown_content = getattr(result, "text_content", "") or ""
|
|
155
|
+
finally:
|
|
156
|
+
tmp_path.unlink(missing_ok=True)
|
|
157
|
+
return markdown_content
|
|
158
|
+
|
|
159
|
+
markdown_content = await loop.run_in_executor(None, convert)
|
|
160
|
+
return markdown_content, "application/pdf"
|
|
161
|
+
|
|
162
|
+
async def _convert_html_to_markdown(self, html: str) -> str:
|
|
163
|
+
loop = asyncio.get_running_loop()
|
|
164
|
+
|
|
165
|
+
def convert() -> str:
|
|
166
|
+
with tempfile.NamedTemporaryFile(
|
|
167
|
+
mode="w", suffix=".html", delete=False, encoding="utf-8"
|
|
168
|
+
) as tmp_file:
|
|
169
|
+
tmp_file.write(html)
|
|
170
|
+
tmp_path = Path(tmp_file.name)
|
|
171
|
+
|
|
172
|
+
try:
|
|
173
|
+
result = self._markitdown.convert(str(tmp_path))
|
|
174
|
+
markdown_content = getattr(result, "text_content", "") or ""
|
|
175
|
+
finally:
|
|
176
|
+
tmp_path.unlink(missing_ok=True)
|
|
177
|
+
|
|
178
|
+
return markdown_content
|
|
179
|
+
|
|
180
|
+
return await loop.run_in_executor(None, convert)
|
|
181
|
+
|
|
182
|
+
async def _is_pdf(self, url: str) -> bool:
|
|
183
|
+
if url.lower().endswith(".pdf"):
|
|
184
|
+
return True
|
|
185
|
+
|
|
186
|
+
timeout = aiohttp.ClientTimeout(total=10)
|
|
187
|
+
try:
|
|
188
|
+
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
189
|
+
async with session.head(url, allow_redirects=True) as response:
|
|
190
|
+
content_type = response.headers.get("Content-Type", "").lower()
|
|
191
|
+
return "pdf" in content_type
|
|
192
|
+
except Exception:
|
|
193
|
+
return False
|
|
194
|
+
|
|
195
|
+
async def _get_driver(self):
|
|
196
|
+
if self._driver is None:
|
|
197
|
+
self._selenium_setup = SeleniumSetup()
|
|
198
|
+
self._driver = await self._selenium_setup.get_driver()
|
|
199
|
+
return self._driver
|
|
200
|
+
|
|
201
|
+
async def _close_driver(self):
|
|
202
|
+
if self._driver is not None:
|
|
203
|
+
loop = asyncio.get_running_loop()
|
|
204
|
+
await loop.run_in_executor(None, self._driver.quit)
|
|
205
|
+
self._driver = None
|
|
206
|
+
self._selenium_setup = None
|
|
207
|
+
|
|
208
|
+
@staticmethod
|
|
209
|
+
def _extract_site(url: str) -> str:
|
|
210
|
+
parsed = urllib.parse.urlparse(url)
|
|
211
|
+
return parsed.netloc or parsed.path
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
__all__ = ["SiteSearch", "SiteSearchArgs"]
|