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,508 @@
|
|
|
1
|
+
"""Git/GitHub toolkit inspired by :mod:`parrot.tools.jiratoolkit`.
|
|
2
|
+
|
|
3
|
+
This toolkit focuses on two complementary workflows that frequently appear
|
|
4
|
+
in software review loops:
|
|
5
|
+
|
|
6
|
+
* producing a ``git apply`` compatible patch from pieces of code supplied by
|
|
7
|
+
an agent or user; and
|
|
8
|
+
* turning those code snippets into an actionable GitHub pull request via the
|
|
9
|
+
public REST API.
|
|
10
|
+
|
|
11
|
+
The implementation deliberately mirrors the structure of
|
|
12
|
+
``JiraToolkit``—async public methods automatically become tools thanks to the
|
|
13
|
+
``AbstractToolkit`` base class—so that it can be dropped into existing agent
|
|
14
|
+
configurations with minimal friction.
|
|
15
|
+
|
|
16
|
+
Only standard library modules (plus :mod:`requests` and :mod:`pydantic`, which
|
|
17
|
+
are already dependencies of Parrot) are required.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import asyncio
|
|
23
|
+
import base64
|
|
24
|
+
import datetime as _dt
|
|
25
|
+
import os
|
|
26
|
+
from dataclasses import dataclass
|
|
27
|
+
from typing import Any, Dict, List, Literal, Optional
|
|
28
|
+
|
|
29
|
+
import difflib
|
|
30
|
+
|
|
31
|
+
import requests
|
|
32
|
+
from pydantic import BaseModel, Field, model_validator
|
|
33
|
+
|
|
34
|
+
from .decorators import tool_schema
|
|
35
|
+
from .toolkit import AbstractToolkit
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class GitToolkitError(RuntimeError):
|
|
39
|
+
"""Raised when the toolkit cannot satisfy a request."""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class GitToolkitInput(BaseModel):
|
|
43
|
+
"""Default configuration shared by all tools in the toolkit."""
|
|
44
|
+
|
|
45
|
+
default_repository: Optional[str] = Field(
|
|
46
|
+
default=None,
|
|
47
|
+
description="Default GitHub repository in 'owner/name' format.",
|
|
48
|
+
)
|
|
49
|
+
default_branch: str = Field(
|
|
50
|
+
default="main", description="Fallback branch used for pull requests."
|
|
51
|
+
)
|
|
52
|
+
github_token: Optional[str] = Field(
|
|
53
|
+
default=None,
|
|
54
|
+
description="Personal access token with repo scope for GitHub calls.",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class GitPatchFile(BaseModel):
|
|
59
|
+
"""Represents a single file change for patch generation."""
|
|
60
|
+
|
|
61
|
+
path: str = Field(description="Path to the file inside the repository.")
|
|
62
|
+
change_type: Literal["modify", "add", "delete"] = Field(
|
|
63
|
+
default="modify",
|
|
64
|
+
description="Type of change represented by this patch fragment.",
|
|
65
|
+
)
|
|
66
|
+
original: Optional[str] = Field(
|
|
67
|
+
default=None,
|
|
68
|
+
description="Original file contents relevant to the change.",
|
|
69
|
+
)
|
|
70
|
+
updated: Optional[str] = Field(
|
|
71
|
+
default=None,
|
|
72
|
+
description="Updated file contents to apply.",
|
|
73
|
+
)
|
|
74
|
+
from_path: Optional[str] = Field(
|
|
75
|
+
default=None,
|
|
76
|
+
description="Override the 'from' path in the generated diff.",
|
|
77
|
+
)
|
|
78
|
+
to_path: Optional[str] = Field(
|
|
79
|
+
default=None,
|
|
80
|
+
description="Override the 'to' path in the generated diff.",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
@model_validator(mode="after")
|
|
84
|
+
def _validate_payload(self) -> "GitPatchFile": # pragma: no cover - pydantic hook
|
|
85
|
+
"""Ensure the required content is supplied for the selected change."""
|
|
86
|
+
|
|
87
|
+
if self.change_type == "modify":
|
|
88
|
+
if self.original is None or self.updated is None:
|
|
89
|
+
raise ValueError("modify changes require both original and updated code")
|
|
90
|
+
elif self.change_type == "add":
|
|
91
|
+
if self.updated is None:
|
|
92
|
+
raise ValueError("add changes require the updated code")
|
|
93
|
+
elif self.change_type == "delete":
|
|
94
|
+
if self.original is None:
|
|
95
|
+
raise ValueError("delete changes require the original code")
|
|
96
|
+
return self
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class GeneratePatchInput(BaseModel):
|
|
100
|
+
"""Input payload for ``generate_git_apply_patch``."""
|
|
101
|
+
|
|
102
|
+
files: List[GitPatchFile] = Field(
|
|
103
|
+
description="Collection of file changes that should be turned into a unified diff.",
|
|
104
|
+
)
|
|
105
|
+
context_lines: int = Field(
|
|
106
|
+
default=3,
|
|
107
|
+
ge=0,
|
|
108
|
+
description="How many context lines to include in the diff output.",
|
|
109
|
+
)
|
|
110
|
+
include_apply_snippet: bool = Field(
|
|
111
|
+
default=True,
|
|
112
|
+
description="If true, include a ready-to-run git-apply heredoc snippet.",
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class GitHubFileChange(BaseModel):
|
|
117
|
+
"""Description of a file mutation when creating a pull request."""
|
|
118
|
+
|
|
119
|
+
path: str = Field(description="File path inside the repository.")
|
|
120
|
+
content: Optional[str] = Field(
|
|
121
|
+
default=None,
|
|
122
|
+
description="New file content. Leave ``None`` to delete a file.",
|
|
123
|
+
)
|
|
124
|
+
encoding: Literal["utf-8", "base64"] = Field(
|
|
125
|
+
default="utf-8", description="Encoding used for ``content``."
|
|
126
|
+
)
|
|
127
|
+
message: Optional[str] = Field(
|
|
128
|
+
default=None,
|
|
129
|
+
description="Optional commit message just for this file change.",
|
|
130
|
+
)
|
|
131
|
+
change_type: Literal["modify", "add", "delete"] = Field(
|
|
132
|
+
default="modify", description="Type of change performed on the file."
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
@model_validator(mode="after")
|
|
136
|
+
def _validate_content(self) -> "GitHubFileChange": # pragma: no cover - pydantic hook
|
|
137
|
+
"""Ensure ``content`` is present unless this is a deletion."""
|
|
138
|
+
|
|
139
|
+
if self.change_type == "delete" and self.content is not None:
|
|
140
|
+
raise ValueError("delete operations should not provide new content")
|
|
141
|
+
if self.change_type in {"modify", "add"} and self.content is None:
|
|
142
|
+
raise ValueError("modify/add operations require content")
|
|
143
|
+
return self
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class CreatePullRequestInput(BaseModel):
|
|
147
|
+
"""Input payload for ``create_pull_request``."""
|
|
148
|
+
|
|
149
|
+
repository: Optional[str] = Field(
|
|
150
|
+
default=None, description="Target GitHub repository in 'owner/name' format."
|
|
151
|
+
)
|
|
152
|
+
title: str = Field(description="Pull request title")
|
|
153
|
+
body: Optional[str] = Field(default=None, description="Pull request description")
|
|
154
|
+
base_branch: Optional[str] = Field(
|
|
155
|
+
default=None, description="Branch into which the changes should merge."
|
|
156
|
+
)
|
|
157
|
+
head_branch: Optional[str] = Field(
|
|
158
|
+
default=None, description="Branch name to create and push changes onto."
|
|
159
|
+
)
|
|
160
|
+
commit_message: Optional[str] = Field(
|
|
161
|
+
default=None,
|
|
162
|
+
description="Commit message used for the updates (defaults to title).",
|
|
163
|
+
)
|
|
164
|
+
files: List[GitHubFileChange] = Field(
|
|
165
|
+
description="List of file updates that compose the pull request.",
|
|
166
|
+
)
|
|
167
|
+
draft: bool = Field(
|
|
168
|
+
default=False, description="Create the pull request as a draft if true."
|
|
169
|
+
)
|
|
170
|
+
labels: Optional[List[str]] = Field(
|
|
171
|
+
default=None, description="Optional labels to apply after PR creation."
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@dataclass
|
|
176
|
+
class _GitHubContext:
|
|
177
|
+
"""Simple container with prepared GitHub configuration."""
|
|
178
|
+
|
|
179
|
+
repository: str
|
|
180
|
+
base_branch: str
|
|
181
|
+
token: str
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class GitToolkit(AbstractToolkit):
|
|
185
|
+
"""Toolkit dedicated to Git patch generation and GitHub pull requests."""
|
|
186
|
+
|
|
187
|
+
input_class = GitToolkitInput
|
|
188
|
+
|
|
189
|
+
def __init__(
|
|
190
|
+
self,
|
|
191
|
+
default_repository: Optional[str] = None,
|
|
192
|
+
default_branch: str = "main",
|
|
193
|
+
github_token: Optional[str] = None,
|
|
194
|
+
**kwargs: Any,
|
|
195
|
+
) -> None:
|
|
196
|
+
super().__init__(**kwargs)
|
|
197
|
+
|
|
198
|
+
self.default_repository = (
|
|
199
|
+
default_repository
|
|
200
|
+
or os.getenv("GIT_DEFAULT_REPOSITORY")
|
|
201
|
+
or os.getenv("GITHUB_REPOSITORY")
|
|
202
|
+
)
|
|
203
|
+
self.default_branch = (
|
|
204
|
+
default_branch or os.getenv("GIT_DEFAULT_BRANCH") or "main"
|
|
205
|
+
)
|
|
206
|
+
self.github_token = github_token or os.getenv("GITHUB_TOKEN")
|
|
207
|
+
|
|
208
|
+
# ------------------------------------------------------------------
|
|
209
|
+
# Patch generation helpers
|
|
210
|
+
# ------------------------------------------------------------------
|
|
211
|
+
@staticmethod
|
|
212
|
+
def _ensure_trailing_newline(text: str) -> str:
|
|
213
|
+
"""Return ``text`` ensuring it terminates with a single newline."""
|
|
214
|
+
|
|
215
|
+
if text.endswith("\n"):
|
|
216
|
+
return text
|
|
217
|
+
return f"{text}\n"
|
|
218
|
+
|
|
219
|
+
@staticmethod
|
|
220
|
+
def _make_diff_fragment(
|
|
221
|
+
change: GitPatchFile,
|
|
222
|
+
context_lines: int,
|
|
223
|
+
) -> Optional[str]:
|
|
224
|
+
"""Produce a unified diff fragment for ``change``."""
|
|
225
|
+
|
|
226
|
+
from_path = change.from_path or f"a/{change.path}"
|
|
227
|
+
to_path = change.to_path or f"b/{change.path}"
|
|
228
|
+
|
|
229
|
+
if change.change_type == "add":
|
|
230
|
+
original_lines: List[str] = []
|
|
231
|
+
updated_lines = GitToolkit._ensure_trailing_newline(change.updated or "").splitlines(True)
|
|
232
|
+
from_path = "/dev/null"
|
|
233
|
+
elif change.change_type == "delete":
|
|
234
|
+
original_lines = GitToolkit._ensure_trailing_newline(change.original or "").splitlines(True)
|
|
235
|
+
updated_lines = []
|
|
236
|
+
to_path = "/dev/null"
|
|
237
|
+
else:
|
|
238
|
+
original_lines = GitToolkit._ensure_trailing_newline(change.original or "").splitlines(True)
|
|
239
|
+
updated_lines = GitToolkit._ensure_trailing_newline(change.updated or "").splitlines(True)
|
|
240
|
+
|
|
241
|
+
diff = list(
|
|
242
|
+
difflib.unified_diff(
|
|
243
|
+
original_lines,
|
|
244
|
+
updated_lines,
|
|
245
|
+
fromfile=from_path,
|
|
246
|
+
tofile=to_path,
|
|
247
|
+
n=context_lines,
|
|
248
|
+
)
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
if not diff:
|
|
252
|
+
return None
|
|
253
|
+
|
|
254
|
+
# Ensure diff chunks end with a newline to keep git-apply happy.
|
|
255
|
+
diff_text = "".join(diff)
|
|
256
|
+
if not diff_text.endswith("\n"):
|
|
257
|
+
diff_text += "\n"
|
|
258
|
+
return diff_text
|
|
259
|
+
|
|
260
|
+
def _render_patch(
|
|
261
|
+
self,
|
|
262
|
+
files: List[GitPatchFile],
|
|
263
|
+
context_lines: int,
|
|
264
|
+
include_apply_snippet: bool,
|
|
265
|
+
) -> Dict[str, Any]:
|
|
266
|
+
fragments: List[str] = []
|
|
267
|
+
skipped: List[str] = []
|
|
268
|
+
|
|
269
|
+
for change in files:
|
|
270
|
+
fragment = self._make_diff_fragment(change, context_lines)
|
|
271
|
+
if fragment:
|
|
272
|
+
fragments.append(fragment)
|
|
273
|
+
else:
|
|
274
|
+
skipped.append(change.path)
|
|
275
|
+
|
|
276
|
+
if not fragments:
|
|
277
|
+
raise GitToolkitError("No differences detected across the provided files.")
|
|
278
|
+
|
|
279
|
+
patch = "".join(fragments)
|
|
280
|
+
apply_snippet = None
|
|
281
|
+
if include_apply_snippet:
|
|
282
|
+
apply_snippet = "cat <<'PATCH' | git apply -\n" + patch + "PATCH\n"
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
"patch": patch,
|
|
286
|
+
"git_apply": apply_snippet,
|
|
287
|
+
"files": len(files),
|
|
288
|
+
"skipped": skipped,
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
@tool_schema(GeneratePatchInput)
|
|
292
|
+
async def generate_git_apply_patch(
|
|
293
|
+
self,
|
|
294
|
+
files: List[GitPatchFile],
|
|
295
|
+
context_lines: int = 3,
|
|
296
|
+
include_apply_snippet: bool = True,
|
|
297
|
+
) -> Dict[str, Any]:
|
|
298
|
+
"""Create a unified diff (and optional ``git apply`` snippet) from code blocks."""
|
|
299
|
+
|
|
300
|
+
return await asyncio.to_thread(
|
|
301
|
+
self._render_patch, files, context_lines, include_apply_snippet
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
# ------------------------------------------------------------------
|
|
305
|
+
# GitHub helpers
|
|
306
|
+
# ------------------------------------------------------------------
|
|
307
|
+
def _prepare_github_context(
|
|
308
|
+
self, repository: Optional[str], base_branch: Optional[str]
|
|
309
|
+
) -> _GitHubContext:
|
|
310
|
+
repo = repository or self.default_repository
|
|
311
|
+
if not repo:
|
|
312
|
+
raise GitToolkitError(
|
|
313
|
+
"A target repository is required (pass repository or configure default)."
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
token = self.github_token
|
|
317
|
+
if not token:
|
|
318
|
+
raise GitToolkitError(
|
|
319
|
+
"A GitHub personal access token is required via init argument or GITHUB_TOKEN."
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
branch = base_branch or self.default_branch
|
|
323
|
+
return _GitHubContext(repository=repo, base_branch=branch, token=token)
|
|
324
|
+
|
|
325
|
+
@staticmethod
|
|
326
|
+
def _request(
|
|
327
|
+
method: str,
|
|
328
|
+
url: str,
|
|
329
|
+
token: str,
|
|
330
|
+
*,
|
|
331
|
+
expected: int,
|
|
332
|
+
**kwargs: Any,
|
|
333
|
+
) -> requests.Response:
|
|
334
|
+
headers = kwargs.pop("headers", {})
|
|
335
|
+
headers.setdefault("Authorization", f"Bearer {token}")
|
|
336
|
+
headers.setdefault("Accept", "application/vnd.github+json")
|
|
337
|
+
headers.setdefault("User-Agent", "parrot-gittoolkit")
|
|
338
|
+
response = requests.request(method, url, headers=headers, timeout=30, **kwargs)
|
|
339
|
+
if response.status_code != expected:
|
|
340
|
+
raise GitToolkitError(
|
|
341
|
+
f"GitHub API call to {url} failed with status {response.status_code}: {response.text}"
|
|
342
|
+
)
|
|
343
|
+
return response
|
|
344
|
+
|
|
345
|
+
@staticmethod
|
|
346
|
+
def _encode_content(change: GitHubFileChange) -> Optional[str]:
|
|
347
|
+
if change.change_type == "delete":
|
|
348
|
+
return None
|
|
349
|
+
if change.encoding == "base64":
|
|
350
|
+
return change.content or ""
|
|
351
|
+
assert change.encoding == "utf-8"
|
|
352
|
+
data = (change.content or "").encode("utf-8")
|
|
353
|
+
return base64.b64encode(data).decode("ascii")
|
|
354
|
+
|
|
355
|
+
def _fetch_file_sha(
|
|
356
|
+
self,
|
|
357
|
+
ctx: _GitHubContext,
|
|
358
|
+
path: str,
|
|
359
|
+
ref: str,
|
|
360
|
+
token: str,
|
|
361
|
+
) -> Optional[str]:
|
|
362
|
+
url = f"https://api.github.com/repos/{ctx.repository}/contents/{path}"
|
|
363
|
+
response = requests.get(
|
|
364
|
+
url,
|
|
365
|
+
headers={
|
|
366
|
+
"Authorization": f"Bearer {token}",
|
|
367
|
+
"Accept": "application/vnd.github+json",
|
|
368
|
+
"User-Agent": "parrot-gittoolkit",
|
|
369
|
+
},
|
|
370
|
+
params={"ref": ref},
|
|
371
|
+
timeout=30,
|
|
372
|
+
)
|
|
373
|
+
if response.status_code == 404:
|
|
374
|
+
return None
|
|
375
|
+
if response.status_code != 200:
|
|
376
|
+
raise GitToolkitError(
|
|
377
|
+
f"Unable to fetch metadata for {path}: {response.status_code} {response.text}"
|
|
378
|
+
)
|
|
379
|
+
payload = response.json()
|
|
380
|
+
return payload.get("sha")
|
|
381
|
+
|
|
382
|
+
def _create_pull_request_sync(
|
|
383
|
+
self,
|
|
384
|
+
*,
|
|
385
|
+
repository: Optional[str],
|
|
386
|
+
title: str,
|
|
387
|
+
body: Optional[str],
|
|
388
|
+
base_branch: Optional[str],
|
|
389
|
+
head_branch: Optional[str],
|
|
390
|
+
commit_message: Optional[str],
|
|
391
|
+
files: List[GitHubFileChange],
|
|
392
|
+
draft: bool,
|
|
393
|
+
labels: Optional[List[str]],
|
|
394
|
+
) -> Dict[str, Any]:
|
|
395
|
+
ctx = self._prepare_github_context(repository, base_branch)
|
|
396
|
+
token = ctx.token
|
|
397
|
+
base_branch_name = ctx.base_branch
|
|
398
|
+
|
|
399
|
+
branch_name = head_branch or f"parrot/{_dt.datetime.utcnow().strftime('%Y%m%d%H%M%S')}"
|
|
400
|
+
commit_message = commit_message or title
|
|
401
|
+
|
|
402
|
+
base_ref_url = f"https://api.github.com/repos/{ctx.repository}/git/ref/heads/{base_branch_name}"
|
|
403
|
+
ref_response = self._request("GET", base_ref_url, token, expected=200)
|
|
404
|
+
base_sha = ref_response.json()["object"]["sha"]
|
|
405
|
+
|
|
406
|
+
create_ref_url = f"https://api.github.com/repos/{ctx.repository}/git/refs"
|
|
407
|
+
payload = {"ref": f"refs/heads/{branch_name}", "sha": base_sha}
|
|
408
|
+
try:
|
|
409
|
+
self._request("POST", create_ref_url, token, expected=201, json=payload)
|
|
410
|
+
except GitToolkitError as exc:
|
|
411
|
+
if "Reference already exists" not in str(exc):
|
|
412
|
+
raise
|
|
413
|
+
|
|
414
|
+
for change in files:
|
|
415
|
+
sha = self._fetch_file_sha(ctx, change.path, branch_name, token)
|
|
416
|
+
|
|
417
|
+
if change.change_type == "delete":
|
|
418
|
+
if not sha:
|
|
419
|
+
raise GitToolkitError(
|
|
420
|
+
f"Cannot delete {change.path}: file does not exist in branch {branch_name}."
|
|
421
|
+
)
|
|
422
|
+
url = f"https://api.github.com/repos/{ctx.repository}/contents/{change.path}"
|
|
423
|
+
json_payload = {
|
|
424
|
+
"message": change.message or commit_message,
|
|
425
|
+
"branch": branch_name,
|
|
426
|
+
"sha": sha,
|
|
427
|
+
}
|
|
428
|
+
self._request("DELETE", url, token, expected=200, json=json_payload)
|
|
429
|
+
continue
|
|
430
|
+
|
|
431
|
+
encoded = self._encode_content(change)
|
|
432
|
+
if change.change_type == "modify" and not sha:
|
|
433
|
+
raise GitToolkitError(
|
|
434
|
+
f"Cannot modify {change.path}: file does not exist in branch {branch_name}."
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
url = f"https://api.github.com/repos/{ctx.repository}/contents/{change.path}"
|
|
438
|
+
json_payload = {
|
|
439
|
+
"message": change.message or commit_message,
|
|
440
|
+
"content": encoded,
|
|
441
|
+
"branch": branch_name,
|
|
442
|
+
}
|
|
443
|
+
if sha:
|
|
444
|
+
json_payload["sha"] = sha
|
|
445
|
+
self._request("PUT", url, token, expected=201 if not sha else 200, json=json_payload)
|
|
446
|
+
|
|
447
|
+
pr_url = f"https://api.github.com/repos/{ctx.repository}/pulls"
|
|
448
|
+
pr_payload = {
|
|
449
|
+
"title": title,
|
|
450
|
+
"body": body or "",
|
|
451
|
+
"head": branch_name,
|
|
452
|
+
"base": base_branch_name,
|
|
453
|
+
"draft": draft,
|
|
454
|
+
}
|
|
455
|
+
pr_response = self._request("POST", pr_url, token, expected=201, json=pr_payload)
|
|
456
|
+
pr_data = pr_response.json()
|
|
457
|
+
|
|
458
|
+
if labels:
|
|
459
|
+
labels_url = f"https://api.github.com/repos/{ctx.repository}/issues/{pr_data['number']}/labels"
|
|
460
|
+
self._request("POST", labels_url, token, expected=200, json={"labels": labels})
|
|
461
|
+
|
|
462
|
+
return {
|
|
463
|
+
"html_url": pr_data.get("html_url"),
|
|
464
|
+
"number": pr_data.get("number"),
|
|
465
|
+
"head_branch": branch_name,
|
|
466
|
+
"base_branch": base_branch_name,
|
|
467
|
+
"commits": len(files),
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
@tool_schema(CreatePullRequestInput)
|
|
471
|
+
async def create_pull_request(
|
|
472
|
+
self,
|
|
473
|
+
repository: Optional[str],
|
|
474
|
+
title: str,
|
|
475
|
+
body: Optional[str],
|
|
476
|
+
base_branch: Optional[str],
|
|
477
|
+
head_branch: Optional[str],
|
|
478
|
+
commit_message: Optional[str],
|
|
479
|
+
files: List[GitHubFileChange],
|
|
480
|
+
draft: bool = False,
|
|
481
|
+
labels: Optional[List[str]] = None,
|
|
482
|
+
) -> Dict[str, Any]:
|
|
483
|
+
"""Create a GitHub pull request with the supplied file updates."""
|
|
484
|
+
|
|
485
|
+
return await asyncio.to_thread(
|
|
486
|
+
self._create_pull_request_sync,
|
|
487
|
+
repository=repository,
|
|
488
|
+
title=title,
|
|
489
|
+
body=body,
|
|
490
|
+
base_branch=base_branch,
|
|
491
|
+
head_branch=head_branch,
|
|
492
|
+
commit_message=commit_message,
|
|
493
|
+
files=files,
|
|
494
|
+
draft=draft,
|
|
495
|
+
labels=labels,
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
__all__ = [
|
|
500
|
+
"GitToolkit",
|
|
501
|
+
"GitToolkitInput",
|
|
502
|
+
"GitPatchFile",
|
|
503
|
+
"GitHubFileChange",
|
|
504
|
+
"GeneratePatchInput",
|
|
505
|
+
"CreatePullRequestInput",
|
|
506
|
+
"GitToolkitError",
|
|
507
|
+
]
|
|
508
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from .tools import (
|
|
2
|
+
GoogleSearchTool,
|
|
3
|
+
GoogleSiteSearchTool,
|
|
4
|
+
GoogleLocationTool,
|
|
5
|
+
GoogleRoutesTool,
|
|
6
|
+
GoogleReviewsTool,
|
|
7
|
+
GoogleTrafficTool,
|
|
8
|
+
)
|
|
9
|
+
from .base import GoogleBaseTool
|
|
10
|
+
|
|
11
|
+
__all__ = (
|
|
12
|
+
"GoogleSearchTool",
|
|
13
|
+
"GoogleSiteSearchTool",
|
|
14
|
+
"GoogleLocationTool",
|
|
15
|
+
"GoogleRoutesTool",
|
|
16
|
+
"GoogleReviewsTool",
|
|
17
|
+
"GoogleTrafficTool",
|
|
18
|
+
)
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""Base classes for Google Workspace tools."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Dict, Any, Optional, Type, Union, List, Callable
|
|
6
|
+
from abc import abstractmethod
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
from navconfig.logging import logging
|
|
10
|
+
|
|
11
|
+
from ..abstract import AbstractTool, AbstractToolArgsSchema
|
|
12
|
+
from ...interfaces.google import GoogleClient
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class GoogleAuthMode:
|
|
16
|
+
"""Authentication modes available for Google tools."""
|
|
17
|
+
|
|
18
|
+
SERVICE_ACCOUNT = "service_account"
|
|
19
|
+
USER = "user"
|
|
20
|
+
CACHED = "cached"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class GoogleToolArgsSchema(AbstractToolArgsSchema):
|
|
24
|
+
"""Base schema for Google tool arguments."""
|
|
25
|
+
|
|
26
|
+
auth_mode: Optional[str] = Field(
|
|
27
|
+
default=None,
|
|
28
|
+
description="Authentication mode: 'service_account', 'user', or 'cached'."
|
|
29
|
+
)
|
|
30
|
+
scopes: Optional[Union[str, List[str]]] = Field(
|
|
31
|
+
default=None,
|
|
32
|
+
description="Optional override of the default scopes for this tool execution."
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class GoogleBaseTool(AbstractTool):
|
|
37
|
+
"""Base class for Google Workspace tools leveraging :class:`GoogleClient`."""
|
|
38
|
+
|
|
39
|
+
name: str = "google_base"
|
|
40
|
+
description: str = "Base Google Workspace tool"
|
|
41
|
+
args_schema: Type[BaseModel] = GoogleToolArgsSchema
|
|
42
|
+
return_direct: bool = False
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
credentials: Optional[Union[str, Dict[str, Any], Path]] = None,
|
|
47
|
+
default_auth_mode: str = GoogleAuthMode.SERVICE_ACCOUNT,
|
|
48
|
+
scopes: Optional[Union[str, List[str]]] = None,
|
|
49
|
+
user_creds_cache_file: Optional[Union[str, Path]] = None,
|
|
50
|
+
open_browser: bool = True,
|
|
51
|
+
login_callback: Optional[Callable[[str], Optional[bool]]] = None,
|
|
52
|
+
interactive_login_kwargs: Optional[Dict[str, Any]] = None,
|
|
53
|
+
interactive_timeout: int = 300,
|
|
54
|
+
**kwargs
|
|
55
|
+
):
|
|
56
|
+
super().__init__(**kwargs)
|
|
57
|
+
|
|
58
|
+
self.credentials = credentials
|
|
59
|
+
self.default_auth_mode = default_auth_mode
|
|
60
|
+
self.scopes = scopes if scopes is not None else 'all'
|
|
61
|
+
self.user_creds_cache_file = (
|
|
62
|
+
Path(user_creds_cache_file).expanduser().resolve()
|
|
63
|
+
if isinstance(user_creds_cache_file, (str, Path))
|
|
64
|
+
else None
|
|
65
|
+
)
|
|
66
|
+
self.open_browser = open_browser
|
|
67
|
+
self.login_callback = login_callback
|
|
68
|
+
self.interactive_login_kwargs = interactive_login_kwargs or {}
|
|
69
|
+
self.interactive_timeout = interactive_timeout
|
|
70
|
+
|
|
71
|
+
self._client_cache: Dict[str, GoogleClient] = {}
|
|
72
|
+
self.logger = logging.getLogger(f'Parrot.Tools.{self.__class__.__name__}')
|
|
73
|
+
|
|
74
|
+
async def _execute(self, **kwargs) -> Any: # type: ignore[override]
|
|
75
|
+
auth_mode = kwargs.pop('auth_mode', None)
|
|
76
|
+
scopes_override = kwargs.pop('scopes', None)
|
|
77
|
+
client = await self._get_client(auth_mode=auth_mode, scopes=scopes_override)
|
|
78
|
+
return await self._execute_google_operation(client=client, **kwargs)
|
|
79
|
+
|
|
80
|
+
async def _get_client(
|
|
81
|
+
self,
|
|
82
|
+
auth_mode: Optional[str] = None,
|
|
83
|
+
scopes: Optional[Union[str, List[str]]] = None
|
|
84
|
+
) -> GoogleClient:
|
|
85
|
+
"""Create or reuse a configured :class:`GoogleClient`."""
|
|
86
|
+
|
|
87
|
+
resolved_auth_mode = auth_mode or self.default_auth_mode
|
|
88
|
+
resolved_scopes = scopes if scopes is not None else self.scopes
|
|
89
|
+
cache_key = self._build_cache_key(resolved_auth_mode, resolved_scopes)
|
|
90
|
+
|
|
91
|
+
if cache_key in self._client_cache:
|
|
92
|
+
return self._client_cache[cache_key]
|
|
93
|
+
|
|
94
|
+
client_credentials: Optional[Union[str, Dict[str, Any], Path]]
|
|
95
|
+
if isinstance(self.credentials, dict):
|
|
96
|
+
client_credentials = self.credentials.copy()
|
|
97
|
+
else:
|
|
98
|
+
client_credentials = self.credentials
|
|
99
|
+
|
|
100
|
+
client_kwargs: Dict[str, Any] = {}
|
|
101
|
+
if self.user_creds_cache_file is not None:
|
|
102
|
+
client_kwargs['user_creds_cache_file'] = self.user_creds_cache_file
|
|
103
|
+
|
|
104
|
+
client = GoogleClient(
|
|
105
|
+
credentials=client_credentials,
|
|
106
|
+
scopes=resolved_scopes,
|
|
107
|
+
**client_kwargs
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
if resolved_auth_mode == GoogleAuthMode.SERVICE_ACCOUNT:
|
|
111
|
+
await client.initialize()
|
|
112
|
+
elif resolved_auth_mode == GoogleAuthMode.USER:
|
|
113
|
+
await client.interactive_login(
|
|
114
|
+
scopes=resolved_scopes,
|
|
115
|
+
open_browser=self.open_browser,
|
|
116
|
+
login_callback=self.login_callback,
|
|
117
|
+
timeout=self.interactive_timeout,
|
|
118
|
+
**self.interactive_login_kwargs
|
|
119
|
+
)
|
|
120
|
+
await client.initialize()
|
|
121
|
+
elif resolved_auth_mode == GoogleAuthMode.CACHED:
|
|
122
|
+
try:
|
|
123
|
+
await client.initialize()
|
|
124
|
+
except RuntimeError as auth_error:
|
|
125
|
+
if "User credentials not available" not in str(auth_error):
|
|
126
|
+
raise
|
|
127
|
+
self.logger.info(
|
|
128
|
+
"No cached Google credentials found; launching interactive login"
|
|
129
|
+
)
|
|
130
|
+
await client.interactive_login(
|
|
131
|
+
scopes=resolved_scopes,
|
|
132
|
+
open_browser=self.open_browser,
|
|
133
|
+
login_callback=self.login_callback,
|
|
134
|
+
timeout=self.interactive_timeout,
|
|
135
|
+
**self.interactive_login_kwargs
|
|
136
|
+
)
|
|
137
|
+
await client.initialize()
|
|
138
|
+
else:
|
|
139
|
+
raise ValueError(f"Unsupported Google auth mode: {resolved_auth_mode}")
|
|
140
|
+
|
|
141
|
+
self._client_cache[cache_key] = client
|
|
142
|
+
return client
|
|
143
|
+
|
|
144
|
+
def _build_cache_key(
|
|
145
|
+
self,
|
|
146
|
+
auth_mode: str,
|
|
147
|
+
scopes: Optional[Union[str, List[str]]]
|
|
148
|
+
) -> str:
|
|
149
|
+
normalized_scopes = self._normalize_scope_list(scopes)
|
|
150
|
+
scope_fragment = ','.join(sorted(normalized_scopes)) if normalized_scopes else 'default'
|
|
151
|
+
return f"{auth_mode}:{scope_fragment}"
|
|
152
|
+
|
|
153
|
+
@staticmethod
|
|
154
|
+
def _normalize_scope_list(scopes: Optional[Union[str, List[str]]]) -> List[str]:
|
|
155
|
+
if scopes is None:
|
|
156
|
+
return []
|
|
157
|
+
return [scopes] if isinstance(scopes, str) else list(scopes)
|
|
158
|
+
|
|
159
|
+
def clear_client_cache(self) -> None:
|
|
160
|
+
"""Clear cached client instances."""
|
|
161
|
+
self._client_cache.clear()
|
|
162
|
+
|
|
163
|
+
@abstractmethod
|
|
164
|
+
async def _execute_google_operation(
|
|
165
|
+
self,
|
|
166
|
+
client: GoogleClient,
|
|
167
|
+
**kwargs
|
|
168
|
+
) -> Any:
|
|
169
|
+
"""Execute the tool-specific Google API operation."""
|