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,1293 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Workday Toolkit - A unified toolkit for Workday SOAP operations with multi-service support.
|
|
3
|
+
|
|
4
|
+
This toolkit wraps common Workday operations across multiple services (Human Resources,
|
|
5
|
+
Absence Management, Time Tracking, Staffing, Financial Management, and Recruiting) as
|
|
6
|
+
async tools, extending AbstractToolkit and using SOAPClient for SOAP/WSDL handling.
|
|
7
|
+
|
|
8
|
+
Features:
|
|
9
|
+
- Multi-service WSDL support with automatic client routing
|
|
10
|
+
- OAuth2 authentication with refresh_token grant
|
|
11
|
+
- Redis token caching for performance
|
|
12
|
+
- Automatic tool generation from public async methods
|
|
13
|
+
- Lazy client initialization for optimal resource usage
|
|
14
|
+
|
|
15
|
+
Dependencies:
|
|
16
|
+
- zeep
|
|
17
|
+
- httpx
|
|
18
|
+
- redis
|
|
19
|
+
- pydantic
|
|
20
|
+
|
|
21
|
+
Example usage:
|
|
22
|
+
# Single service (Human Resources only)
|
|
23
|
+
toolkit = WorkdayToolkit(
|
|
24
|
+
credentials={
|
|
25
|
+
"client_id": "your-client-id",
|
|
26
|
+
"client_secret": "your-client-secret",
|
|
27
|
+
"token_url": "https://wd2-impl.workday.com/ccx/oauth2/token",
|
|
28
|
+
"wsdl_path": "https://wd2-impl.workday.com/ccx/service/tenant/Human_Resources/v44.2?wsdl",
|
|
29
|
+
"refresh_token": "your-refresh-token"
|
|
30
|
+
},
|
|
31
|
+
tenant_name="your_tenant"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Multiple services with explicit WSDL paths
|
|
35
|
+
toolkit = WorkdayToolkit(
|
|
36
|
+
credentials={
|
|
37
|
+
"client_id": "your-client-id",
|
|
38
|
+
"client_secret": "your-client-secret",
|
|
39
|
+
"token_url": "https://wd2-impl.workday.com/ccx/oauth2/token",
|
|
40
|
+
"refresh_token": "your-refresh-token"
|
|
41
|
+
},
|
|
42
|
+
tenant_name="your_tenant",
|
|
43
|
+
wsdl_paths={
|
|
44
|
+
"human_resources": "https://wd2-impl.workday.com/ccx/service/tenant/Human_Resources/v44.2?wsdl",
|
|
45
|
+
"absence_management": "https://wd2-impl.workday.com/ccx/service/tenant/Absence_Management/v45?wsdl",
|
|
46
|
+
"time_tracking": "https://wd2-impl.workday.com/ccx/service/tenant/Time_Tracking/v44.2?wsdl",
|
|
47
|
+
"staffing": "https://wd2-impl.workday.com/ccx/service/tenant/Staffing/v44.2?wsdl",
|
|
48
|
+
"financial_management": "https://wd2-impl.workday.com/ccx/service/tenant/Financial_Management/v45?wsdl",
|
|
49
|
+
"recruiting": "https://wd2-impl.workday.com/ccx/service/tenant/Recruiting/v44.2?wsdl"
|
|
50
|
+
}
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Initialize the connection
|
|
54
|
+
await toolkit.wd_start()
|
|
55
|
+
|
|
56
|
+
# Use methods - appropriate client is selected automatically
|
|
57
|
+
worker = await toolkit.wd_get_worker(worker_id="12345")
|
|
58
|
+
time_off = await toolkit.wd_get_time_off_balance(worker_id="12345")
|
|
59
|
+
"""
|
|
60
|
+
from __future__ import annotations
|
|
61
|
+
|
|
62
|
+
import asyncio
|
|
63
|
+
import contextlib
|
|
64
|
+
from enum import Enum
|
|
65
|
+
from typing import Any, Dict, List, Optional, Type, Union
|
|
66
|
+
from datetime import datetime
|
|
67
|
+
from pathlib import PurePath
|
|
68
|
+
from pydantic import BaseModel, Field
|
|
69
|
+
from zeep import helpers
|
|
70
|
+
from ..toolkit import AbstractToolkit
|
|
71
|
+
from ..decorators import tool_schema
|
|
72
|
+
from ...interfaces.soap import SOAPClient
|
|
73
|
+
from .models import (
|
|
74
|
+
WorkdayReference,
|
|
75
|
+
WorkerModel,
|
|
76
|
+
OrganizationModel,
|
|
77
|
+
WorkdayResponseParser
|
|
78
|
+
)
|
|
79
|
+
from ...conf import (
|
|
80
|
+
WORKDAY_DEFAULT_TENANT,
|
|
81
|
+
WORKDAY_CLIENT_ID,
|
|
82
|
+
WORKDAY_CLIENT_SECRET,
|
|
83
|
+
WORKDAY_TOKEN_URL,
|
|
84
|
+
WORKDAY_WSDL_PATH,
|
|
85
|
+
WORKDAY_REFRESH_TOKEN,
|
|
86
|
+
WORKDAY_WSDL_PATHS
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# -----------------------------
|
|
90
|
+
# Workday Service Types
|
|
91
|
+
# -----------------------------
|
|
92
|
+
class WorkdayService(str, Enum):
|
|
93
|
+
"""Enumeration of Workday SOAP service types."""
|
|
94
|
+
HUMAN_RESOURCES = "human_resources"
|
|
95
|
+
ABSENCE_MANAGEMENT = "absence_management"
|
|
96
|
+
TIME_TRACKING = "time_tracking"
|
|
97
|
+
STAFFING = "staffing"
|
|
98
|
+
FINANCIAL_MANAGEMENT = "financial_management"
|
|
99
|
+
RECRUITING = "recruiting"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# Mapping of toolkit methods to required Workday services
|
|
103
|
+
METHOD_TO_SERVICE_MAP = {
|
|
104
|
+
# Human Resources service methods
|
|
105
|
+
"wd_get_worker": WorkdayService.HUMAN_RESOURCES,
|
|
106
|
+
"wd_search_workers": WorkdayService.HUMAN_RESOURCES,
|
|
107
|
+
"wd_get_worker_contact": WorkdayService.HUMAN_RESOURCES,
|
|
108
|
+
"wd_get_worker_job_data": WorkdayService.HUMAN_RESOURCES,
|
|
109
|
+
"wd_get_organization": WorkdayService.HUMAN_RESOURCES,
|
|
110
|
+
"wd_get_workers_by_organization": WorkdayService.HUMAN_RESOURCES,
|
|
111
|
+
"wd_get_workers_by_ids": WorkdayService.HUMAN_RESOURCES,
|
|
112
|
+
"wd_search_workers_by_name": WorkdayService.HUMAN_RESOURCES,
|
|
113
|
+
"wd_get_workers_by_manager": WorkdayService.HUMAN_RESOURCES,
|
|
114
|
+
"wd_get_inactive_workers": WorkdayService.HUMAN_RESOURCES,
|
|
115
|
+
"wd_get_worker_time_off_balance": WorkdayService.HUMAN_RESOURCES,
|
|
116
|
+
# Absence Management service methods
|
|
117
|
+
"wd_get_time_off_balance": WorkdayService.ABSENCE_MANAGEMENT,
|
|
118
|
+
# Time Tracking service methods (placeholder for future implementation)
|
|
119
|
+
# "wd_get_time_entry": WorkdayService.TIME_TRACKING,
|
|
120
|
+
# "wd_submit_timesheet": WorkdayService.TIME_TRACKING,
|
|
121
|
+
|
|
122
|
+
# Staffing service methods (placeholder for future implementation)
|
|
123
|
+
# "wd_get_position": WorkdayService.STAFFING,
|
|
124
|
+
# "wd_create_position": WorkdayService.STAFFING,
|
|
125
|
+
|
|
126
|
+
# Financial Management service methods (placeholder for future implementation)
|
|
127
|
+
# "wd_get_spend_category": WorkdayService.FINANCIAL_MANAGEMENT,
|
|
128
|
+
# "wd_get_worktags": WorkdayService.FINANCIAL_MANAGEMENT,
|
|
129
|
+
|
|
130
|
+
# Recruiting service methods (placeholder for future implementation)
|
|
131
|
+
# "wd_get_job_requisition": WorkdayService.RECRUITING,
|
|
132
|
+
# "wd_get_candidates": WorkdayService.RECRUITING,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# -----------------------------
|
|
137
|
+
# Input models (schemas)
|
|
138
|
+
# -----------------------------
|
|
139
|
+
class WorkdayToolkitInput(BaseModel):
|
|
140
|
+
"""Default configuration for Workday toolkit operations."""
|
|
141
|
+
|
|
142
|
+
tenant_name: str = Field(
|
|
143
|
+
description="Workday tenant name (e.g., 'acme_impl', 'company_prod')"
|
|
144
|
+
)
|
|
145
|
+
include_reference: bool = Field(
|
|
146
|
+
default=True,
|
|
147
|
+
description="Include reference data in responses"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class GetWorkerInput(BaseModel):
|
|
152
|
+
"""Input for retrieving a single worker by ID."""
|
|
153
|
+
|
|
154
|
+
worker_id: str = Field(
|
|
155
|
+
description="Worker ID (Employee ID, Contingent Worker ID, or WID)"
|
|
156
|
+
)
|
|
157
|
+
output_format: Optional[Type[BaseModel]] = Field(
|
|
158
|
+
default=None,
|
|
159
|
+
description="Optional Pydantic model to format the output"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class SearchWorkersInput(BaseModel):
|
|
164
|
+
"""Input for searching workers with filters."""
|
|
165
|
+
|
|
166
|
+
search_text: Optional[str] = Field(
|
|
167
|
+
default=None,
|
|
168
|
+
description="Text to search in worker names, emails, or IDs"
|
|
169
|
+
)
|
|
170
|
+
manager_id: Optional[str] = Field(
|
|
171
|
+
default=None,
|
|
172
|
+
description="Filter by manager's worker ID"
|
|
173
|
+
)
|
|
174
|
+
location_id: Optional[str] = Field(
|
|
175
|
+
default=None,
|
|
176
|
+
description="Filter by location ID"
|
|
177
|
+
)
|
|
178
|
+
job_profile_id: Optional[str] = Field(
|
|
179
|
+
default=None,
|
|
180
|
+
description="Filter by job profile ID"
|
|
181
|
+
)
|
|
182
|
+
hire_date_from: Optional[str] = Field(
|
|
183
|
+
default=None,
|
|
184
|
+
description="Filter by hire date (YYYY-MM-DD format) - from"
|
|
185
|
+
)
|
|
186
|
+
hire_date_to: Optional[str] = Field(
|
|
187
|
+
default=None,
|
|
188
|
+
description="Filter by hire date (YYYY-MM-DD format) - to"
|
|
189
|
+
)
|
|
190
|
+
max_results: int = Field(
|
|
191
|
+
default=100,
|
|
192
|
+
description="Maximum number of results to return"
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class GetWorkerContactInput(BaseModel):
|
|
197
|
+
"""Input for retrieving worker contact information."""
|
|
198
|
+
|
|
199
|
+
worker_id: str = Field(
|
|
200
|
+
description="Worker ID to get contact info for"
|
|
201
|
+
)
|
|
202
|
+
include_personal: bool = Field(
|
|
203
|
+
default=True,
|
|
204
|
+
description="Include personal contact information"
|
|
205
|
+
)
|
|
206
|
+
include_work: bool = Field(
|
|
207
|
+
default=True,
|
|
208
|
+
description="Include work contact information"
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class GetOrganizationInput(BaseModel):
|
|
213
|
+
"""Input for retrieving organization information."""
|
|
214
|
+
|
|
215
|
+
org_id: str = Field(
|
|
216
|
+
description="Organization ID or reference ID"
|
|
217
|
+
)
|
|
218
|
+
include_hierarchy: bool = Field(
|
|
219
|
+
default=False,
|
|
220
|
+
description="Include organizational hierarchy"
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class GetWorkerJobDataInput(BaseModel):
|
|
225
|
+
"""Input for retrieving worker's job-related data."""
|
|
226
|
+
|
|
227
|
+
worker_id: str = Field(
|
|
228
|
+
description="Worker ID to get job data for"
|
|
229
|
+
)
|
|
230
|
+
effective_date: Optional[str] = Field(
|
|
231
|
+
default=None,
|
|
232
|
+
description="Effective date for job data (YYYY-MM-DD). Defaults to today."
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class GetTimeOffBalanceInput(BaseModel):
|
|
237
|
+
"""Input for retrieving time off balance information."""
|
|
238
|
+
|
|
239
|
+
worker_id: str = Field(
|
|
240
|
+
description="Worker ID to get time off balance for"
|
|
241
|
+
)
|
|
242
|
+
time_off_plan_id: Optional[str] = Field(
|
|
243
|
+
default=None,
|
|
244
|
+
description="Optional specific time off plan ID to filter by"
|
|
245
|
+
)
|
|
246
|
+
output_format: Optional[Type[BaseModel]] = Field(
|
|
247
|
+
default=None,
|
|
248
|
+
description="Optional Pydantic model to format the output"
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
# -----------------------------
|
|
253
|
+
# Workday SOAP Client
|
|
254
|
+
# -----------------------------
|
|
255
|
+
class WorkdaySOAPClient(SOAPClient):
|
|
256
|
+
"""
|
|
257
|
+
Specialized SOAPClient for Workday operations.
|
|
258
|
+
|
|
259
|
+
Handles Workday-specific SOAP envelope construction and response parsing.
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
def __init__(self, tenant_name: str, **kwargs):
|
|
263
|
+
"""
|
|
264
|
+
Initialize Workday SOAP client.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
tenant_name: Workday tenant identifier
|
|
268
|
+
**kwargs: Additional arguments passed to SOAPClient
|
|
269
|
+
"""
|
|
270
|
+
super().__init__(**kwargs)
|
|
271
|
+
self.tenant_name = tenant_name
|
|
272
|
+
|
|
273
|
+
def _build_worker_reference(self, worker_id: str, id_type: str = "Employee_ID") -> Dict[str, Any]:
|
|
274
|
+
"""
|
|
275
|
+
Build a Workday worker reference object.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
worker_id: Worker identifier
|
|
279
|
+
id_type: Type of ID (Employee_ID, Contingent_Worker_ID, WID, etc.)
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
Worker reference dictionary for SOAP request
|
|
283
|
+
"""
|
|
284
|
+
return {
|
|
285
|
+
"ID": [
|
|
286
|
+
{
|
|
287
|
+
"type": id_type,
|
|
288
|
+
"_value_1": worker_id
|
|
289
|
+
}
|
|
290
|
+
]
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
def _build_request_criteria(
|
|
294
|
+
self,
|
|
295
|
+
**filters: Any
|
|
296
|
+
) -> Dict[str, Any]:
|
|
297
|
+
"""
|
|
298
|
+
Build request criteria for Workday queries.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
**filters: Filter parameters
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
Request criteria dictionary
|
|
305
|
+
"""
|
|
306
|
+
criteria = {}
|
|
307
|
+
|
|
308
|
+
if "search_text" in filters and filters["search_text"]:
|
|
309
|
+
criteria["Search_Text"] = filters["search_text"]
|
|
310
|
+
|
|
311
|
+
if "manager_id" in filters and filters["manager_id"]:
|
|
312
|
+
criteria["Manager_Reference"] = self._build_worker_reference(
|
|
313
|
+
filters["manager_id"]
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
return criteria
|
|
317
|
+
|
|
318
|
+
def _parse_worker_response(self, response: Any) -> Dict[str, Any]:
|
|
319
|
+
"""
|
|
320
|
+
Parse Workday worker response into a clean dictionary.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
response: Raw SOAP response
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
Parsed worker data
|
|
327
|
+
"""
|
|
328
|
+
# This is a simplified parser - adjust based on actual Workday response structure
|
|
329
|
+
return helpers.serialize_object(response) if response else {}
|
|
330
|
+
|
|
331
|
+
def _build_organization_reference(
|
|
332
|
+
self,
|
|
333
|
+
org_id: str,
|
|
334
|
+
id_type: str = "Organization_Reference_ID"
|
|
335
|
+
) -> Dict[str, Any]:
|
|
336
|
+
"""Build organization reference."""
|
|
337
|
+
return {
|
|
338
|
+
"ID": [
|
|
339
|
+
{
|
|
340
|
+
"type": id_type,
|
|
341
|
+
"_value_1": org_id
|
|
342
|
+
}
|
|
343
|
+
]
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
def _build_field_criteria(
|
|
347
|
+
self,
|
|
348
|
+
field_name: str,
|
|
349
|
+
field_value: str,
|
|
350
|
+
operator: str = "Equals"
|
|
351
|
+
) -> Dict[str, Any]:
|
|
352
|
+
"""
|
|
353
|
+
Build field and parameter criteria for advanced searches.
|
|
354
|
+
|
|
355
|
+
Args:
|
|
356
|
+
field_name: Workday field name (e.g., "Legal_Name", "Email")
|
|
357
|
+
field_value: Value to search for
|
|
358
|
+
operator: Comparison operator (Equals, Contains, Starts_With, etc.)
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
Field criteria dictionary
|
|
362
|
+
"""
|
|
363
|
+
return {
|
|
364
|
+
"Field_And_Parameter_Criteria_Data": [
|
|
365
|
+
{
|
|
366
|
+
"Field_Name": field_name,
|
|
367
|
+
"Operator": operator,
|
|
368
|
+
"Value": field_value
|
|
369
|
+
}
|
|
370
|
+
]
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
# -----------------------------
|
|
375
|
+
# Toolkit implementation
|
|
376
|
+
# -----------------------------
|
|
377
|
+
class WorkdayToolkit(AbstractToolkit):
|
|
378
|
+
"""
|
|
379
|
+
Toolkit for interacting with Workday via SOAP/WSDL with multi-service support.
|
|
380
|
+
|
|
381
|
+
This toolkit provides async tools for Workday operations across multiple services:
|
|
382
|
+
- Human Resources: Worker management, organization queries, employment data
|
|
383
|
+
- Absence Management: Time off balances, leave requests
|
|
384
|
+
- Time Tracking: Timesheet operations (placeholder for future implementation)
|
|
385
|
+
- Staffing: Position management (placeholder for future implementation)
|
|
386
|
+
- Financial Management: Spend categories, worktags (placeholder for future implementation)
|
|
387
|
+
- Recruiting: Job requisitions, candidates (placeholder for future implementation)
|
|
388
|
+
|
|
389
|
+
The toolkit automatically routes method calls to the appropriate WSDL service
|
|
390
|
+
based on the METHOD_TO_SERVICE_MAP configuration. Clients are lazily initialized
|
|
391
|
+
on first use and cached for performance.
|
|
392
|
+
|
|
393
|
+
All public async methods automatically become tools via AbstractToolkit.
|
|
394
|
+
"""
|
|
395
|
+
|
|
396
|
+
def __init__(
|
|
397
|
+
self,
|
|
398
|
+
tenant_name: str = None,
|
|
399
|
+
credentials: Dict[str, str] = None,
|
|
400
|
+
wsdl_paths: Optional[Dict[str, str]] = None,
|
|
401
|
+
redis_url: Optional[str] = None,
|
|
402
|
+
redis_key: str = "workday:access_token",
|
|
403
|
+
timeout: int = 30,
|
|
404
|
+
**kwargs
|
|
405
|
+
):
|
|
406
|
+
"""
|
|
407
|
+
Initialize Workday toolkit with support for multiple service WSDLs.
|
|
408
|
+
|
|
409
|
+
Args:
|
|
410
|
+
credentials: Dict with OAuth2 credentials (client_id, client_secret, token_url, refresh_token)
|
|
411
|
+
and default wsdl_path (typically Human Resources)
|
|
412
|
+
tenant_name: Workday tenant name
|
|
413
|
+
wsdl_paths: Optional dict mapping service names to WSDL URLs, e.g.:
|
|
414
|
+
{
|
|
415
|
+
"human_resources": "https://.../Human_Resources/v44.2?wsdl",
|
|
416
|
+
"absence_management": "https://.../Absence_Management/v45?wsdl",
|
|
417
|
+
"time_tracking": "https://.../Time_Tracking/v44.2?wsdl",
|
|
418
|
+
"staffing": "https://.../Staffing/v44.2?wsdl",
|
|
419
|
+
"financial_management": "https://.../Financial_Management/v45?wsdl",
|
|
420
|
+
"recruiting": "https://.../Recruiting/v44.2?wsdl"
|
|
421
|
+
}
|
|
422
|
+
redis_url: Redis connection URL for token caching
|
|
423
|
+
redis_key: Redis key for storing access token
|
|
424
|
+
timeout: HTTP timeout in seconds
|
|
425
|
+
**kwargs: Additional toolkit configuration
|
|
426
|
+
"""
|
|
427
|
+
super().__init__(**kwargs)
|
|
428
|
+
|
|
429
|
+
# Store credentials and settings for creating clients
|
|
430
|
+
self.credentials = credentials or self._default_credentials()
|
|
431
|
+
self.redis_url = redis_url
|
|
432
|
+
self.redis_key = redis_key
|
|
433
|
+
self.timeout = timeout
|
|
434
|
+
self.tenant_name = tenant_name or WORKDAY_DEFAULT_TENANT
|
|
435
|
+
|
|
436
|
+
# Initialize WSDL paths mapping
|
|
437
|
+
self.wsdl_paths: Dict[WorkdayService, str] = {}
|
|
438
|
+
|
|
439
|
+
# Process wsdl_paths parameter or use legacy approach
|
|
440
|
+
if wsdl_paths:
|
|
441
|
+
# Map service names to enum values
|
|
442
|
+
for service_name, wsdl_url in wsdl_paths.items():
|
|
443
|
+
try:
|
|
444
|
+
service_enum = WorkdayService(service_name)
|
|
445
|
+
self.wsdl_paths[service_enum] = wsdl_url
|
|
446
|
+
except ValueError:
|
|
447
|
+
# Skip unknown service names
|
|
448
|
+
continue
|
|
449
|
+
else:
|
|
450
|
+
for service_name, wsdl_url in WORKDAY_WSDL_PATHS.items():
|
|
451
|
+
if isinstance(wsdl_url, PurePath) and not wsdl_url.is_file():
|
|
452
|
+
print('Warning: WSDL path does not exist:', wsdl_url)
|
|
453
|
+
try:
|
|
454
|
+
service_enum = WorkdayService(service_name)
|
|
455
|
+
self.wsdl_paths[service_enum] = wsdl_url
|
|
456
|
+
except ValueError:
|
|
457
|
+
# Skip unknown service names
|
|
458
|
+
continue
|
|
459
|
+
|
|
460
|
+
# Fallback: Use default wsdl_path from credentials for Human Resources
|
|
461
|
+
if WorkdayService.HUMAN_RESOURCES not in self.wsdl_paths:
|
|
462
|
+
if default_wsdl := credentials.get("wsdl_path"):
|
|
463
|
+
self.wsdl_paths[WorkdayService.HUMAN_RESOURCES] = default_wsdl
|
|
464
|
+
|
|
465
|
+
# Dictionary to store initialized clients per service
|
|
466
|
+
self._clients: Dict[WorkdayService, WorkdaySOAPClient] = {}
|
|
467
|
+
|
|
468
|
+
# For backward compatibility, keep soap_client as primary client
|
|
469
|
+
self.soap_client: Optional[WorkdaySOAPClient] = None
|
|
470
|
+
|
|
471
|
+
self._initialized = False
|
|
472
|
+
|
|
473
|
+
def _default_credentials(self) -> Dict[str, str]:
|
|
474
|
+
"""Generate default credentials from configuration."""
|
|
475
|
+
return {
|
|
476
|
+
"client_id": WORKDAY_CLIENT_ID,
|
|
477
|
+
"client_secret": WORKDAY_CLIENT_SECRET,
|
|
478
|
+
"token_url": WORKDAY_TOKEN_URL,
|
|
479
|
+
"wsdl_path": WORKDAY_WSDL_PATH,
|
|
480
|
+
"refresh_token": WORKDAY_REFRESH_TOKEN
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
async def wd_start(self) -> str:
|
|
484
|
+
"""
|
|
485
|
+
Initialize the primary SOAP client connection.
|
|
486
|
+
Must be called before using any tools.
|
|
487
|
+
|
|
488
|
+
Returns:
|
|
489
|
+
Success message
|
|
490
|
+
"""
|
|
491
|
+
if not self._initialized:
|
|
492
|
+
# Initialize primary client (Human Resources by default)
|
|
493
|
+
primary_service = WorkdayService.HUMAN_RESOURCES
|
|
494
|
+
self.soap_client = await self._get_client_for_service(primary_service)
|
|
495
|
+
self._initialized = True
|
|
496
|
+
return "Workday toolkit initialized successfully. Ready to process requests."
|
|
497
|
+
return "Workday toolkit already initialized."
|
|
498
|
+
|
|
499
|
+
async def start(self) -> str:
|
|
500
|
+
"""Compatibility wrapper for toolkit lifecycle start."""
|
|
501
|
+
return await self.wd_start()
|
|
502
|
+
|
|
503
|
+
async def _get_client_for_service(
|
|
504
|
+
self,
|
|
505
|
+
service: WorkdayService
|
|
506
|
+
) -> WorkdaySOAPClient:
|
|
507
|
+
"""
|
|
508
|
+
Get or create a SOAP client for the specified service.
|
|
509
|
+
|
|
510
|
+
This method implements lazy initialization - clients are only created
|
|
511
|
+
when first needed and then cached for reuse.
|
|
512
|
+
|
|
513
|
+
Args:
|
|
514
|
+
service: The Workday service enum (e.g., HUMAN_RESOURCES, ABSENCE_MANAGEMENT)
|
|
515
|
+
|
|
516
|
+
Returns:
|
|
517
|
+
Initialized WorkdaySOAPClient for the requested service
|
|
518
|
+
|
|
519
|
+
Raises:
|
|
520
|
+
RuntimeError: If WSDL path for the service is not configured
|
|
521
|
+
"""
|
|
522
|
+
# Return cached client if already initialized
|
|
523
|
+
if service in self._clients:
|
|
524
|
+
return self._clients[service]
|
|
525
|
+
|
|
526
|
+
# Check if WSDL path is configured for this service
|
|
527
|
+
if service not in self.wsdl_paths:
|
|
528
|
+
raise RuntimeError(
|
|
529
|
+
f"WSDL path for service '{service.value}' is not configured. "
|
|
530
|
+
f"Pass it in 'wsdl_paths' parameter when initializing WorkdayToolkit. "
|
|
531
|
+
f"Example: wsdl_paths={{'{service.value}': 'https://...?wsdl'}}"
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
# Create credentials with service-specific WSDL
|
|
535
|
+
service_credentials = self.credentials.copy()
|
|
536
|
+
service_credentials["wsdl_path"] = self.wsdl_paths[service]
|
|
537
|
+
|
|
538
|
+
# Create and initialize the client
|
|
539
|
+
client = WorkdaySOAPClient(
|
|
540
|
+
tenant_name=self.tenant_name,
|
|
541
|
+
credentials=service_credentials,
|
|
542
|
+
redis_url=self.redis_url,
|
|
543
|
+
redis_key=self.redis_key,
|
|
544
|
+
timeout=self.timeout
|
|
545
|
+
)
|
|
546
|
+
await client.start()
|
|
547
|
+
|
|
548
|
+
# Cache the client
|
|
549
|
+
self._clients[service] = client
|
|
550
|
+
|
|
551
|
+
return client
|
|
552
|
+
|
|
553
|
+
async def _get_client_for_method(self, method_name: str) -> WorkdaySOAPClient:
|
|
554
|
+
"""
|
|
555
|
+
Get the appropriate SOAP client for a given toolkit method.
|
|
556
|
+
|
|
557
|
+
Args:
|
|
558
|
+
method_name: Name of the toolkit method
|
|
559
|
+
|
|
560
|
+
Returns:
|
|
561
|
+
Initialized WorkdaySOAPClient for the method's required service
|
|
562
|
+
|
|
563
|
+
Raises:
|
|
564
|
+
RuntimeError: If method is not mapped to a service or WSDL not configured
|
|
565
|
+
"""
|
|
566
|
+
if method_name not in METHOD_TO_SERVICE_MAP:
|
|
567
|
+
# Default to Human Resources for unmapped methods
|
|
568
|
+
service = WorkdayService.HUMAN_RESOURCES
|
|
569
|
+
else:
|
|
570
|
+
service = METHOD_TO_SERVICE_MAP[method_name]
|
|
571
|
+
|
|
572
|
+
return await self._get_client_for_service(service)
|
|
573
|
+
|
|
574
|
+
async def wd_close(self) -> None:
|
|
575
|
+
"""
|
|
576
|
+
Close all SOAP client connections.
|
|
577
|
+
"""
|
|
578
|
+
# Close all cached clients
|
|
579
|
+
for client in self._clients.values():
|
|
580
|
+
await client.close()
|
|
581
|
+
|
|
582
|
+
self._clients.clear()
|
|
583
|
+
self.soap_client = None
|
|
584
|
+
self._initialized = False
|
|
585
|
+
|
|
586
|
+
# -----------------------------
|
|
587
|
+
# Tool methods (automatically become tools)
|
|
588
|
+
# -----------------------------
|
|
589
|
+
|
|
590
|
+
@tool_schema(GetWorkerInput)
|
|
591
|
+
async def wd_get_worker(
|
|
592
|
+
self,
|
|
593
|
+
worker_id: str,
|
|
594
|
+
output_format: Optional[Type[BaseModel]] = None,
|
|
595
|
+
) -> Union[WorkerModel, BaseModel]:
|
|
596
|
+
"""
|
|
597
|
+
Get detailed information about a specific worker by ID.
|
|
598
|
+
|
|
599
|
+
Retrieves comprehensive worker data including personal information,
|
|
600
|
+
job details, compensation, and organizational relationships.
|
|
601
|
+
|
|
602
|
+
Args:
|
|
603
|
+
worker_id: Worker identifier (Employee ID, Contingent Worker ID, or WID)
|
|
604
|
+
|
|
605
|
+
Returns:
|
|
606
|
+
Worker data dictionary with all available fields
|
|
607
|
+
"""
|
|
608
|
+
if not self._initialized:
|
|
609
|
+
await self.wd_start()
|
|
610
|
+
|
|
611
|
+
# Get the appropriate client for this method
|
|
612
|
+
client = await self._get_client_for_method("wd_get_worker")
|
|
613
|
+
|
|
614
|
+
# Build the Get_Workers request
|
|
615
|
+
request = {
|
|
616
|
+
"Request_References": {
|
|
617
|
+
"Worker_Reference": client._build_worker_reference(worker_id)
|
|
618
|
+
},
|
|
619
|
+
"Response_Filter": {
|
|
620
|
+
"As_Of_Effective_Date": datetime.now().strftime("%Y-%m-%d"),
|
|
621
|
+
"As_Of_Entry_DateTime": datetime.now().isoformat()
|
|
622
|
+
},
|
|
623
|
+
"Response_Group": {
|
|
624
|
+
"Include_Reference": True,
|
|
625
|
+
"Include_Personal_Information": True,
|
|
626
|
+
"Include_Employment_Information": True,
|
|
627
|
+
"Include_Compensation": True,
|
|
628
|
+
"Include_Organizations": True,
|
|
629
|
+
"Include_Roles": True,
|
|
630
|
+
"Include_Management_Chain_Data": True
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
result = await client.run("Get_Workers", **request)
|
|
635
|
+
# Use parser for structured output
|
|
636
|
+
return WorkdayResponseParser.parse_worker_response(
|
|
637
|
+
result,
|
|
638
|
+
output_format=output_format
|
|
639
|
+
)
|
|
640
|
+
# return client._parse_worker_response(result)
|
|
641
|
+
|
|
642
|
+
@tool_schema(SearchWorkersInput)
|
|
643
|
+
async def wd_search_workers(
|
|
644
|
+
self,
|
|
645
|
+
search_text: Optional[str] = None,
|
|
646
|
+
manager_id: Optional[str] = None,
|
|
647
|
+
location_id: Optional[str] = None,
|
|
648
|
+
job_profile_id: Optional[str] = None,
|
|
649
|
+
hire_date_from: Optional[str] = None,
|
|
650
|
+
hire_date_to: Optional[str] = None,
|
|
651
|
+
max_results: int = 100
|
|
652
|
+
) -> List[Dict[str, Any]]:
|
|
653
|
+
"""
|
|
654
|
+
Search for workers based on various criteria.
|
|
655
|
+
|
|
656
|
+
Supports searching by text, manager, location, job profile, and hire date range.
|
|
657
|
+
Returns a list of workers matching the specified criteria.
|
|
658
|
+
|
|
659
|
+
Args:
|
|
660
|
+
search_text: Text to search in names, emails, or IDs
|
|
661
|
+
manager_id: Filter by manager's worker ID
|
|
662
|
+
location_id: Filter by location ID
|
|
663
|
+
job_profile_id: Filter by job profile ID
|
|
664
|
+
hire_date_from: Start of hire date range (YYYY-MM-DD)
|
|
665
|
+
hire_date_to: End of hire date range (YYYY-MM-DD)
|
|
666
|
+
max_results: Maximum number of results (default 100)
|
|
667
|
+
|
|
668
|
+
Returns:
|
|
669
|
+
List of worker dictionaries matching the search criteria
|
|
670
|
+
"""
|
|
671
|
+
if not self._initialized:
|
|
672
|
+
await self.wd_start()
|
|
673
|
+
|
|
674
|
+
# Get the appropriate client for this method
|
|
675
|
+
client = await self._get_client_for_method("wd_search_workers")
|
|
676
|
+
|
|
677
|
+
# Build request criteria
|
|
678
|
+
request = {
|
|
679
|
+
"Request_Criteria": client._build_request_criteria(
|
|
680
|
+
search_text=search_text,
|
|
681
|
+
manager_id=manager_id,
|
|
682
|
+
location_id=location_id,
|
|
683
|
+
job_profile_id=job_profile_id
|
|
684
|
+
),
|
|
685
|
+
"Response_Filter": {
|
|
686
|
+
"Page": 1,
|
|
687
|
+
"Count": max_results
|
|
688
|
+
},
|
|
689
|
+
"Response_Group": {
|
|
690
|
+
"Include_Reference": True,
|
|
691
|
+
"Include_Personal_Information": True,
|
|
692
|
+
"Include_Employment_Information": True
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
# Add hire date filters if provided
|
|
697
|
+
if hire_date_from or hire_date_to:
|
|
698
|
+
request["Request_Criteria"]["Hire_Date_Range"] = {}
|
|
699
|
+
if hire_date_from:
|
|
700
|
+
request["Request_Criteria"]["Hire_Date_Range"]["From"] = hire_date_from
|
|
701
|
+
if hire_date_to:
|
|
702
|
+
request["Request_Criteria"]["Hire_Date_Range"]["To"] = hire_date_to
|
|
703
|
+
|
|
704
|
+
result = await client.run("Get_Workers", **request)
|
|
705
|
+
|
|
706
|
+
# Parse multiple workers from response
|
|
707
|
+
workers = []
|
|
708
|
+
if result and hasattr(result, "Worker"):
|
|
709
|
+
workers.extend(
|
|
710
|
+
client._parse_worker_response(worker)
|
|
711
|
+
for worker in result.Worker
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
return workers
|
|
715
|
+
|
|
716
|
+
@tool_schema(GetWorkerContactInput)
|
|
717
|
+
async def wd_get_worker_contact(
|
|
718
|
+
self,
|
|
719
|
+
worker_id: str,
|
|
720
|
+
include_personal: bool = True,
|
|
721
|
+
include_work: bool = True,
|
|
722
|
+
output_format: Optional[Type[BaseModel]] = None,
|
|
723
|
+
) -> Dict[str, Any]:
|
|
724
|
+
"""
|
|
725
|
+
Get contact information for a specific worker.
|
|
726
|
+
|
|
727
|
+
Retrieves email addresses, phone numbers, addresses, and other
|
|
728
|
+
contact details for the specified worker.
|
|
729
|
+
|
|
730
|
+
Args:
|
|
731
|
+
worker_id: Worker identifier
|
|
732
|
+
include_personal: Include personal contact information
|
|
733
|
+
include_work: Include work contact information
|
|
734
|
+
|
|
735
|
+
Returns:
|
|
736
|
+
Dictionary containing all contact information
|
|
737
|
+
"""
|
|
738
|
+
if not self._initialized:
|
|
739
|
+
await self.wd_start()
|
|
740
|
+
|
|
741
|
+
# Get the appropriate client for this method
|
|
742
|
+
client = await self._get_client_for_method("wd_get_worker_contact")
|
|
743
|
+
|
|
744
|
+
request = {
|
|
745
|
+
"Request_References": {
|
|
746
|
+
"Worker_Reference": client._build_worker_reference(worker_id)
|
|
747
|
+
},
|
|
748
|
+
"Response_Group": {
|
|
749
|
+
"Include_Personal_Information": include_personal,
|
|
750
|
+
"Include_Employment_Information": include_work,
|
|
751
|
+
# "Include_Contact_Information": True
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
result = await client.run("Get_Workers", **request)
|
|
756
|
+
# parsed = client._parse_worker_response(result)
|
|
757
|
+
return WorkdayResponseParser.parse_contact_response(
|
|
758
|
+
result,
|
|
759
|
+
worker_id=worker_id,
|
|
760
|
+
output_format=output_format
|
|
761
|
+
)
|
|
762
|
+
|
|
763
|
+
@tool_schema(GetWorkerJobDataInput)
|
|
764
|
+
async def wd_get_worker_job_data(
|
|
765
|
+
self,
|
|
766
|
+
worker_id: str,
|
|
767
|
+
effective_date: Optional[str] = None
|
|
768
|
+
) -> Dict[str, Any]:
|
|
769
|
+
"""
|
|
770
|
+
Get job-related data for a worker.
|
|
771
|
+
|
|
772
|
+
Retrieves position, job profile, location, manager, compensation,
|
|
773
|
+
and other employment details for the specified worker.
|
|
774
|
+
|
|
775
|
+
Args:
|
|
776
|
+
worker_id: Worker identifier
|
|
777
|
+
effective_date: Date for which to retrieve data (YYYY-MM-DD). Defaults to today.
|
|
778
|
+
|
|
779
|
+
Returns:
|
|
780
|
+
Dictionary containing job data
|
|
781
|
+
"""
|
|
782
|
+
if not self._initialized:
|
|
783
|
+
await self.wd_start()
|
|
784
|
+
|
|
785
|
+
# Get the appropriate client for this method
|
|
786
|
+
client = await self._get_client_for_method("wd_get_worker_job_data")
|
|
787
|
+
|
|
788
|
+
if not effective_date:
|
|
789
|
+
effective_date = datetime.now().strftime("%Y-%m-%d")
|
|
790
|
+
|
|
791
|
+
request = {
|
|
792
|
+
"Request_References": {
|
|
793
|
+
"Worker_Reference": client._build_worker_reference(worker_id)
|
|
794
|
+
},
|
|
795
|
+
"Response_Filter": {
|
|
796
|
+
"As_Of_Effective_Date": effective_date
|
|
797
|
+
},
|
|
798
|
+
"Response_Group": {
|
|
799
|
+
"Include_Employment_Information": True,
|
|
800
|
+
"Include_Compensation": True,
|
|
801
|
+
"Include_Organizations": True,
|
|
802
|
+
"Include_Management_Chain_Data": True
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
result = await client.run("Get_Workers", **request)
|
|
807
|
+
parsed = client._parse_worker_response(result)
|
|
808
|
+
|
|
809
|
+
# Extract job-specific data
|
|
810
|
+
if parsed and "Worker_Data" in parsed:
|
|
811
|
+
worker_data = parsed["Worker_Data"]
|
|
812
|
+
employment_data = worker_data.get("Employment_Data", {})
|
|
813
|
+
|
|
814
|
+
return {
|
|
815
|
+
"worker_id": worker_id,
|
|
816
|
+
"effective_date": effective_date,
|
|
817
|
+
"position": employment_data.get("Position_Data", {}),
|
|
818
|
+
"job_profile": employment_data.get("Position_Data", {}).get("Job_Profile_Summary_Data", {}),
|
|
819
|
+
"business_title": employment_data.get("Position_Data", {}).get("Business_Title", ""),
|
|
820
|
+
"manager": employment_data.get("Worker_Job_Data", {}).get("Manager_Reference", {}),
|
|
821
|
+
"location": employment_data.get("Position_Data", {}).get("Business_Site_Summary_Data", {}),
|
|
822
|
+
"organizations": worker_data.get("Organization_Data", []),
|
|
823
|
+
"compensation": worker_data.get("Compensation_Data", {})
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
return {"worker_id": worker_id, "job_data": parsed}
|
|
827
|
+
|
|
828
|
+
@tool_schema(GetOrganizationInput)
|
|
829
|
+
async def wd_get_organization(
|
|
830
|
+
self,
|
|
831
|
+
org_id: str,
|
|
832
|
+
include_hierarchy: bool = False
|
|
833
|
+
) -> Dict[str, Any]:
|
|
834
|
+
"""
|
|
835
|
+
Get organization information by ID.
|
|
836
|
+
|
|
837
|
+
Retrieves details about an organizational unit including its
|
|
838
|
+
name, type, manager, and optionally its hierarchical structure.
|
|
839
|
+
|
|
840
|
+
Args:
|
|
841
|
+
org_id: Organization ID or reference
|
|
842
|
+
include_hierarchy: Include organizational hierarchy
|
|
843
|
+
|
|
844
|
+
Returns:
|
|
845
|
+
Dictionary containing organization data
|
|
846
|
+
"""
|
|
847
|
+
if not self._initialized:
|
|
848
|
+
await self.wd_start()
|
|
849
|
+
|
|
850
|
+
# Get the appropriate client for this method
|
|
851
|
+
client = await self._get_client_for_method("wd_get_organization")
|
|
852
|
+
|
|
853
|
+
request = {
|
|
854
|
+
"Request_References": {
|
|
855
|
+
"Organization_Reference": {
|
|
856
|
+
"ID": [{"type": "Organization_Reference_ID", "_value_1": org_id}]
|
|
857
|
+
}
|
|
858
|
+
},
|
|
859
|
+
"Response_Group": {
|
|
860
|
+
"Include_Reference": True,
|
|
861
|
+
"Include_Organization_Support_Role_Data": True,
|
|
862
|
+
"Include_Hierarchy_Data": include_hierarchy
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
result = await client.run("Get_Organizations", **request)
|
|
867
|
+
# Parse organization response
|
|
868
|
+
return helpers.serialize_object(result) if result else {}
|
|
869
|
+
|
|
870
|
+
async def wd_get_worker_time_off_balance(
|
|
871
|
+
self,
|
|
872
|
+
worker_id: str,
|
|
873
|
+
output_format: Optional[Type[BaseModel]] = None
|
|
874
|
+
) -> Dict[str, Any]:
|
|
875
|
+
"""
|
|
876
|
+
Get time off balance for a worker.
|
|
877
|
+
|
|
878
|
+
Retrieves available time off balances for all time off types
|
|
879
|
+
assigned to the worker.
|
|
880
|
+
|
|
881
|
+
Args:
|
|
882
|
+
worker_id: Worker identifier
|
|
883
|
+
|
|
884
|
+
Returns:
|
|
885
|
+
Dictionary containing time off balances by type
|
|
886
|
+
"""
|
|
887
|
+
if not self._initialized:
|
|
888
|
+
await self.wd_start()
|
|
889
|
+
|
|
890
|
+
# Get the appropriate client for this method
|
|
891
|
+
client = await self._get_client_for_method("wd_get_worker_time_off_balance")
|
|
892
|
+
|
|
893
|
+
request = {
|
|
894
|
+
"Request_References": {
|
|
895
|
+
"Worker_Reference": client._build_worker_reference(worker_id)
|
|
896
|
+
},
|
|
897
|
+
"Response_Group": {
|
|
898
|
+
# Some Workday tenants/WSDL versions do not expose
|
|
899
|
+
# Include_Time_Off_Balance on the Worker response group. Keep
|
|
900
|
+
# the response group minimal to avoid zeep TypeError for
|
|
901
|
+
# unsupported fields while still returning the worker payload
|
|
902
|
+
# (which may contain Time_Off_Balance_Data when the tenant is
|
|
903
|
+
# licensed for Absence Management).
|
|
904
|
+
"Include_Reference": True
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
result = await client.run("Get_Workers", **request)
|
|
909
|
+
print('RESULT > ', result)
|
|
910
|
+
return WorkdayResponseParser.parse_time_off_balance_response(
|
|
911
|
+
result,
|
|
912
|
+
worker_id=worker_id,
|
|
913
|
+
output_format=output_format
|
|
914
|
+
)
|
|
915
|
+
|
|
916
|
+
@tool_schema(GetTimeOffBalanceInput)
|
|
917
|
+
async def wd_get_time_off_balance(
|
|
918
|
+
self,
|
|
919
|
+
worker_id: str,
|
|
920
|
+
time_off_plan_id: Optional[str] = None,
|
|
921
|
+
output_format: Optional[Type[BaseModel]] = None
|
|
922
|
+
) -> Union[Dict[str, Any], BaseModel]:
|
|
923
|
+
"""
|
|
924
|
+
Get time off plan balances for a worker using Absence Management API.
|
|
925
|
+
|
|
926
|
+
This method uses the Get_Time_Off_Plan_Balances operation from the
|
|
927
|
+
Workday Absence Management WSDL, which provides more detailed balance
|
|
928
|
+
information than the Get_Workers operation.
|
|
929
|
+
|
|
930
|
+
Args:
|
|
931
|
+
worker_id: Worker identifier (Employee_ID)
|
|
932
|
+
time_off_plan_id: Optional specific time off plan ID to filter
|
|
933
|
+
output_format: Optional Pydantic model to format the output
|
|
934
|
+
|
|
935
|
+
Returns:
|
|
936
|
+
Time off balance information formatted according to output_format
|
|
937
|
+
or default TimeOffBalanceModel
|
|
938
|
+
"""
|
|
939
|
+
if not self._initialized:
|
|
940
|
+
await self.wd_start()
|
|
941
|
+
|
|
942
|
+
# Get the Absence Management client
|
|
943
|
+
absence_client = await self._get_client_for_method("wd_get_time_off_balance")
|
|
944
|
+
|
|
945
|
+
# Build the request payload
|
|
946
|
+
payload = {
|
|
947
|
+
"Response_Filter": {
|
|
948
|
+
"As_Of_Entry_DateTime": datetime.now().replace(microsecond=0).isoformat() + "Z"
|
|
949
|
+
},
|
|
950
|
+
"Response_Group": {
|
|
951
|
+
"Include_Reference": True,
|
|
952
|
+
"Include_Time_Off_Plan_Balance_Data": True,
|
|
953
|
+
},
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
# Build Request_Criteria
|
|
957
|
+
request_criteria = {
|
|
958
|
+
"Employee_Reference": {
|
|
959
|
+
"ID": [
|
|
960
|
+
{
|
|
961
|
+
"_value_1": worker_id,
|
|
962
|
+
"type": "Employee_ID"
|
|
963
|
+
}
|
|
964
|
+
]
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
# Add time off plan filter if provided
|
|
969
|
+
if time_off_plan_id:
|
|
970
|
+
request_criteria["Time_Off_Plan_Reference"] = {
|
|
971
|
+
"ID": [
|
|
972
|
+
{
|
|
973
|
+
"_value_1": time_off_plan_id,
|
|
974
|
+
"type": "Time_Off_Plan_ID"
|
|
975
|
+
}
|
|
976
|
+
]
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
payload["Request_Criteria"] = request_criteria
|
|
980
|
+
|
|
981
|
+
# Execute the SOAP operation
|
|
982
|
+
result = await absence_client._service.Get_Time_Off_Plan_Balances(**payload)
|
|
983
|
+
|
|
984
|
+
# Parse the response using the dedicated parser
|
|
985
|
+
return WorkdayResponseParser.parse_time_off_plan_balances_response(
|
|
986
|
+
result,
|
|
987
|
+
worker_id=worker_id,
|
|
988
|
+
output_format=output_format
|
|
989
|
+
)
|
|
990
|
+
|
|
991
|
+
async def wd_get_workers_by_organization(
|
|
992
|
+
self,
|
|
993
|
+
org_id: str,
|
|
994
|
+
output_format: Optional[Type[BaseModel]] = None,
|
|
995
|
+
include_subordinate: bool = True,
|
|
996
|
+
exclude_inactive: bool = True,
|
|
997
|
+
max_results: int = 100
|
|
998
|
+
) -> List[Dict[str, Any]]:
|
|
999
|
+
"""
|
|
1000
|
+
Get all workers in an organization.
|
|
1001
|
+
|
|
1002
|
+
This is the most common way to "search" workers in Workday -
|
|
1003
|
+
by filtering on organizational membership.
|
|
1004
|
+
|
|
1005
|
+
Args:
|
|
1006
|
+
org_id: Organization ID or reference
|
|
1007
|
+
include_subordinate: Include workers from sub-organizations
|
|
1008
|
+
exclude_inactive: Exclude terminated/inactive workers
|
|
1009
|
+
max_results: Maximum results to return
|
|
1010
|
+
|
|
1011
|
+
Returns:
|
|
1012
|
+
List of worker dictionaries
|
|
1013
|
+
"""
|
|
1014
|
+
if not self._initialized:
|
|
1015
|
+
await self.wd_start()
|
|
1016
|
+
|
|
1017
|
+
# Get the appropriate client for this method
|
|
1018
|
+
client = await self._get_client_for_method("wd_get_workers_by_organization")
|
|
1019
|
+
|
|
1020
|
+
request = {
|
|
1021
|
+
"Request_Criteria": {
|
|
1022
|
+
"Organization_Reference": [
|
|
1023
|
+
client._build_organization_reference(org_id)
|
|
1024
|
+
],
|
|
1025
|
+
"Include_Subordinate_Organizations": include_subordinate,
|
|
1026
|
+
"Exclude_Inactive_Workers": exclude_inactive
|
|
1027
|
+
},
|
|
1028
|
+
"Response_Filter": {
|
|
1029
|
+
"Page": 1,
|
|
1030
|
+
"Count": max_results,
|
|
1031
|
+
"As_Of_Effective_Date": datetime.now().strftime("%Y-%m-%d")
|
|
1032
|
+
},
|
|
1033
|
+
"Response_Group": {
|
|
1034
|
+
"Include_Reference": True,
|
|
1035
|
+
"Include_Personal_Information": True,
|
|
1036
|
+
"Include_Employment_Information": True,
|
|
1037
|
+
"Include_Organizations": True
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
result = await client.run("Get_Workers", **request)
|
|
1042
|
+
# Use parser for structured output
|
|
1043
|
+
return WorkdayResponseParser.parse_workers_response(
|
|
1044
|
+
result,
|
|
1045
|
+
output_format=output_format
|
|
1046
|
+
)
|
|
1047
|
+
|
|
1048
|
+
async def wd_get_workers_by_ids(
|
|
1049
|
+
self,
|
|
1050
|
+
worker_ids: List[str],
|
|
1051
|
+
id_type: str = "Employee_ID"
|
|
1052
|
+
) -> List[Dict[str, Any]]:
|
|
1053
|
+
"""
|
|
1054
|
+
Get multiple workers by their IDs.
|
|
1055
|
+
|
|
1056
|
+
This is the most efficient way to retrieve specific workers.
|
|
1057
|
+
|
|
1058
|
+
Args:
|
|
1059
|
+
worker_ids: List of worker identifiers
|
|
1060
|
+
id_type: Type of ID (Employee_ID, WID, etc.)
|
|
1061
|
+
|
|
1062
|
+
Returns:
|
|
1063
|
+
List of worker dictionaries
|
|
1064
|
+
"""
|
|
1065
|
+
if not self._initialized:
|
|
1066
|
+
await self.wd_start()
|
|
1067
|
+
|
|
1068
|
+
# Get the appropriate client for this method
|
|
1069
|
+
client = await self._get_client_for_method("wd_get_workers_by_ids")
|
|
1070
|
+
|
|
1071
|
+
request = {
|
|
1072
|
+
"Request_References": {
|
|
1073
|
+
"Worker_Reference": [
|
|
1074
|
+
client._build_worker_reference(wid, id_type)
|
|
1075
|
+
for wid in worker_ids
|
|
1076
|
+
]
|
|
1077
|
+
},
|
|
1078
|
+
"Response_Filter": {
|
|
1079
|
+
"As_Of_Effective_Date": datetime.now().strftime("%Y-%m-%d")
|
|
1080
|
+
},
|
|
1081
|
+
"Response_Group": {
|
|
1082
|
+
"Include_Reference": True,
|
|
1083
|
+
"Include_Personal_Information": True,
|
|
1084
|
+
"Include_Employment_Information": True
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
result = await client.run("Get_Workers", **request)
|
|
1089
|
+
return self._parse_workers_response(result)
|
|
1090
|
+
|
|
1091
|
+
async def wd_search_workers_by_name(
|
|
1092
|
+
self,
|
|
1093
|
+
name: str,
|
|
1094
|
+
max_results: int = 100,
|
|
1095
|
+
search_type: str = "Contains" # Contains, Equals, Starts_With
|
|
1096
|
+
) -> List[Dict[str, Any]]:
|
|
1097
|
+
"""
|
|
1098
|
+
Search workers by name using Field_And_Parameter_Criteria.
|
|
1099
|
+
|
|
1100
|
+
Note: This is less efficient than organizational searches.
|
|
1101
|
+
Consider combining with organizational filters for better performance.
|
|
1102
|
+
|
|
1103
|
+
Args:
|
|
1104
|
+
name: Name to search for
|
|
1105
|
+
max_results: Maximum results
|
|
1106
|
+
search_type: Type of search (Contains, Equals, Starts_With)
|
|
1107
|
+
|
|
1108
|
+
Returns:
|
|
1109
|
+
List of matching workers
|
|
1110
|
+
"""
|
|
1111
|
+
if not self._initialized:
|
|
1112
|
+
await self.wd_start()
|
|
1113
|
+
|
|
1114
|
+
# Get the appropriate client for this method
|
|
1115
|
+
client = await self._get_client_for_method("wd_search_workers_by_name")
|
|
1116
|
+
|
|
1117
|
+
request = {
|
|
1118
|
+
"Request_Criteria": {
|
|
1119
|
+
"Field_And_Parameter_Criteria_Data": {
|
|
1120
|
+
"Field_Name": "Legal_Name", # Or "Preferred_Name"
|
|
1121
|
+
"Operator": search_type,
|
|
1122
|
+
"Value": name
|
|
1123
|
+
},
|
|
1124
|
+
"Exclude_Inactive_Workers": True
|
|
1125
|
+
},
|
|
1126
|
+
"Response_Filter": {
|
|
1127
|
+
"Page": 1,
|
|
1128
|
+
"Count": max_results
|
|
1129
|
+
},
|
|
1130
|
+
"Response_Group": {
|
|
1131
|
+
"Include_Reference": True,
|
|
1132
|
+
"Include_Personal_Information": True,
|
|
1133
|
+
"Include_Employment_Information": True
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
result = await client.run("Get_Workers", **request)
|
|
1138
|
+
return self._parse_workers_response(result)
|
|
1139
|
+
|
|
1140
|
+
async def wd_get_workers_by_manager(
|
|
1141
|
+
self,
|
|
1142
|
+
manager_id: str,
|
|
1143
|
+
include_indirect_reports: bool = False,
|
|
1144
|
+
max_results: int = 100
|
|
1145
|
+
) -> List[Dict[str, Any]]:
|
|
1146
|
+
"""
|
|
1147
|
+
Get all workers reporting to a manager.
|
|
1148
|
+
|
|
1149
|
+
Note: Workday doesn't have a direct "manager filter" in Get_Workers.
|
|
1150
|
+
This implementation gets the manager's position and then finds
|
|
1151
|
+
all workers in that supervisory organization.
|
|
1152
|
+
|
|
1153
|
+
For true hierarchical reporting, you may need to:
|
|
1154
|
+
1. Get the manager's position
|
|
1155
|
+
2. Get the supervisory organization
|
|
1156
|
+
3. Query workers in that organization
|
|
1157
|
+
|
|
1158
|
+
Args:
|
|
1159
|
+
manager_id: Manager's worker ID
|
|
1160
|
+
include_indirect_reports: Include indirect reports
|
|
1161
|
+
max_results: Maximum results
|
|
1162
|
+
|
|
1163
|
+
Returns:
|
|
1164
|
+
List of direct/indirect reports
|
|
1165
|
+
"""
|
|
1166
|
+
if not self._initialized:
|
|
1167
|
+
await self.wd_start()
|
|
1168
|
+
|
|
1169
|
+
# First, get the manager's data to find their supervisory org
|
|
1170
|
+
manager_data = await self.wd_get_worker(manager_id)
|
|
1171
|
+
|
|
1172
|
+
# Extract supervisory organization from manager's position
|
|
1173
|
+
# This structure varies by Workday configuration
|
|
1174
|
+
supervisory_org_id = None
|
|
1175
|
+
if "Worker_Data" in manager_data:
|
|
1176
|
+
employment = manager_data["Worker_Data"].get("Employment_Data", {})
|
|
1177
|
+
position = employment.get("Position_Data", {})
|
|
1178
|
+
|
|
1179
|
+
# Look for supervisory organization
|
|
1180
|
+
for org in position.get("Organization_Data", []):
|
|
1181
|
+
if org.get("Organization_Type_Reference", {}).get("ID", [{}])[0].get("_value_1") == "SUPERVISORY":
|
|
1182
|
+
supervisory_org_id = org.get("Organization_Reference", {}).get("ID", [{}])[0].get("_value_1")
|
|
1183
|
+
break
|
|
1184
|
+
|
|
1185
|
+
if not supervisory_org_id:
|
|
1186
|
+
return []
|
|
1187
|
+
|
|
1188
|
+
# Now get all workers in that supervisory org
|
|
1189
|
+
return await self.wd_get_workers_by_organization(
|
|
1190
|
+
org_id=supervisory_org_id,
|
|
1191
|
+
include_subordinate=include_indirect_reports,
|
|
1192
|
+
max_results=max_results
|
|
1193
|
+
)
|
|
1194
|
+
|
|
1195
|
+
async def wd_get_inactive_workers(
|
|
1196
|
+
self,
|
|
1197
|
+
org_id: Optional[str] = None,
|
|
1198
|
+
termination_date_from: Optional[str] = None,
|
|
1199
|
+
termination_date_to: Optional[str] = None,
|
|
1200
|
+
max_results: int = 100
|
|
1201
|
+
) -> List[Dict[str, Any]]:
|
|
1202
|
+
"""
|
|
1203
|
+
Get terminated/inactive workers.
|
|
1204
|
+
|
|
1205
|
+
Args:
|
|
1206
|
+
org_id: Optional organization filter
|
|
1207
|
+
termination_date_from: Start of termination date range (YYYY-MM-DD)
|
|
1208
|
+
termination_date_to: End of termination date range (YYYY-MM-DD)
|
|
1209
|
+
max_results: Maximum results
|
|
1210
|
+
|
|
1211
|
+
Returns:
|
|
1212
|
+
List of inactive workers
|
|
1213
|
+
"""
|
|
1214
|
+
if not self._initialized:
|
|
1215
|
+
await self.wd_start()
|
|
1216
|
+
|
|
1217
|
+
# Get the appropriate client for this method
|
|
1218
|
+
client = await self._get_client_for_method("wd_get_inactive_workers")
|
|
1219
|
+
|
|
1220
|
+
request = {
|
|
1221
|
+
"Request_Criteria": {
|
|
1222
|
+
"Exclude_Inactive_Workers": False, # We want inactive workers!
|
|
1223
|
+
"Exclude_Employees": False,
|
|
1224
|
+
"Exclude_Contingent_Workers": False
|
|
1225
|
+
},
|
|
1226
|
+
"Response_Filter": {
|
|
1227
|
+
"Page": 1,
|
|
1228
|
+
"Count": max_results
|
|
1229
|
+
},
|
|
1230
|
+
"Response_Group": {
|
|
1231
|
+
"Include_Reference": True,
|
|
1232
|
+
"Include_Personal_Information": True,
|
|
1233
|
+
"Include_Employment_Information": True
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
# Add org filter if provided
|
|
1238
|
+
if org_id:
|
|
1239
|
+
request["Request_Criteria"]["Organization_Reference"] = [
|
|
1240
|
+
client._build_organization_reference(org_id)
|
|
1241
|
+
]
|
|
1242
|
+
|
|
1243
|
+
# Note: Termination date filtering might require Transaction_Log_Criteria_Data
|
|
1244
|
+
# depending on your Workday configuration
|
|
1245
|
+
|
|
1246
|
+
result = await client.run("Get_Workers", **request)
|
|
1247
|
+
|
|
1248
|
+
# Post-process to filter by termination date if needed
|
|
1249
|
+
workers = self._parse_workers_response(result)
|
|
1250
|
+
|
|
1251
|
+
if termination_date_from or termination_date_to:
|
|
1252
|
+
filtered = []
|
|
1253
|
+
for worker in workers:
|
|
1254
|
+
if (term_date := self._extract_termination_date(worker)):
|
|
1255
|
+
if termination_date_from and term_date < termination_date_from:
|
|
1256
|
+
continue
|
|
1257
|
+
if termination_date_to and term_date > termination_date_to:
|
|
1258
|
+
continue
|
|
1259
|
+
filtered.append(worker)
|
|
1260
|
+
return filtered
|
|
1261
|
+
|
|
1262
|
+
return workers
|
|
1263
|
+
|
|
1264
|
+
def _parse_workers_response(self, response: Any) -> List[Dict[str, Any]]:
|
|
1265
|
+
"""
|
|
1266
|
+
Parse Get_Workers response into list of worker dictionaries.
|
|
1267
|
+
"""
|
|
1268
|
+
workers = []
|
|
1269
|
+
|
|
1270
|
+
if not response:
|
|
1271
|
+
return workers
|
|
1272
|
+
|
|
1273
|
+
# Response structure: Get_Workers_Response -> Response_Data -> Worker[]
|
|
1274
|
+
serialized = helpers.serialize_object(response)
|
|
1275
|
+
|
|
1276
|
+
# Navigate the response structure
|
|
1277
|
+
response_data = serialized.get("Response_Data", {})
|
|
1278
|
+
worker_data = response_data.get("Worker", [])
|
|
1279
|
+
|
|
1280
|
+
# Handle single worker vs array
|
|
1281
|
+
if not isinstance(worker_data, list):
|
|
1282
|
+
worker_data = [worker_data] if worker_data else []
|
|
1283
|
+
workers.extend(iter(worker_data))
|
|
1284
|
+
return workers
|
|
1285
|
+
|
|
1286
|
+
def _extract_termination_date(self, worker_data: Dict[str, Any]) -> Optional[str]:
|
|
1287
|
+
"""Extract termination date from worker data."""
|
|
1288
|
+
with contextlib.suppress(Exception):
|
|
1289
|
+
employment = worker_data.get("Worker_Data", {}).get("Employment_Data", {})
|
|
1290
|
+
status_data = employment.get("Worker_Status_Data", {})
|
|
1291
|
+
if status_data.get("Terminated"):
|
|
1292
|
+
return status_data.get("Termination_Date")
|
|
1293
|
+
return None
|