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.
Files changed (535) hide show
  1. agentui/.prettierrc +15 -0
  2. agentui/QUICKSTART.md +272 -0
  3. agentui/README.md +59 -0
  4. agentui/env.example +16 -0
  5. agentui/jsconfig.json +14 -0
  6. agentui/package-lock.json +4242 -0
  7. agentui/package.json +34 -0
  8. agentui/scripts/postinstall/apply-patches.mjs +260 -0
  9. agentui/src/app.css +61 -0
  10. agentui/src/app.d.ts +13 -0
  11. agentui/src/app.html +12 -0
  12. agentui/src/components/LoadingSpinner.svelte +64 -0
  13. agentui/src/components/ThemeSwitcher.svelte +159 -0
  14. agentui/src/components/index.js +4 -0
  15. agentui/src/lib/api/bots.ts +60 -0
  16. agentui/src/lib/api/chat.ts +22 -0
  17. agentui/src/lib/api/http.ts +25 -0
  18. agentui/src/lib/components/BotCard.svelte +33 -0
  19. agentui/src/lib/components/ChatBubble.svelte +63 -0
  20. agentui/src/lib/components/Toast.svelte +21 -0
  21. agentui/src/lib/config.ts +20 -0
  22. agentui/src/lib/stores/auth.svelte.ts +73 -0
  23. agentui/src/lib/stores/theme.svelte.js +64 -0
  24. agentui/src/lib/stores/toast.svelte.ts +31 -0
  25. agentui/src/lib/utils/conversation.ts +39 -0
  26. agentui/src/routes/+layout.svelte +20 -0
  27. agentui/src/routes/+page.svelte +232 -0
  28. agentui/src/routes/login/+page.svelte +200 -0
  29. agentui/src/routes/talk/[agentId]/+page.svelte +297 -0
  30. agentui/src/routes/talk/[agentId]/+page.ts +7 -0
  31. agentui/static/README.md +1 -0
  32. agentui/svelte.config.js +11 -0
  33. agentui/tailwind.config.ts +53 -0
  34. agentui/tsconfig.json +3 -0
  35. agentui/vite.config.ts +10 -0
  36. ai_parrot-0.17.2.dist-info/METADATA +472 -0
  37. ai_parrot-0.17.2.dist-info/RECORD +535 -0
  38. ai_parrot-0.17.2.dist-info/WHEEL +6 -0
  39. ai_parrot-0.17.2.dist-info/entry_points.txt +2 -0
  40. ai_parrot-0.17.2.dist-info/licenses/LICENSE +21 -0
  41. ai_parrot-0.17.2.dist-info/top_level.txt +6 -0
  42. crew-builder/.prettierrc +15 -0
  43. crew-builder/QUICKSTART.md +259 -0
  44. crew-builder/README.md +113 -0
  45. crew-builder/env.example +17 -0
  46. crew-builder/jsconfig.json +14 -0
  47. crew-builder/package-lock.json +4182 -0
  48. crew-builder/package.json +37 -0
  49. crew-builder/scripts/postinstall/apply-patches.mjs +260 -0
  50. crew-builder/src/app.css +62 -0
  51. crew-builder/src/app.d.ts +13 -0
  52. crew-builder/src/app.html +12 -0
  53. crew-builder/src/components/LoadingSpinner.svelte +64 -0
  54. crew-builder/src/components/ThemeSwitcher.svelte +149 -0
  55. crew-builder/src/components/index.js +9 -0
  56. crew-builder/src/lib/api/bots.ts +60 -0
  57. crew-builder/src/lib/api/chat.ts +80 -0
  58. crew-builder/src/lib/api/client.ts +56 -0
  59. crew-builder/src/lib/api/crew/crew.ts +136 -0
  60. crew-builder/src/lib/api/index.ts +5 -0
  61. crew-builder/src/lib/api/o365/auth.ts +65 -0
  62. crew-builder/src/lib/auth/auth.ts +54 -0
  63. crew-builder/src/lib/components/AgentNode.svelte +43 -0
  64. crew-builder/src/lib/components/BotCard.svelte +33 -0
  65. crew-builder/src/lib/components/ChatBubble.svelte +67 -0
  66. crew-builder/src/lib/components/ConfigPanel.svelte +278 -0
  67. crew-builder/src/lib/components/JsonTreeNode.svelte +76 -0
  68. crew-builder/src/lib/components/JsonViewer.svelte +24 -0
  69. crew-builder/src/lib/components/MarkdownEditor.svelte +48 -0
  70. crew-builder/src/lib/components/ThemeToggle.svelte +36 -0
  71. crew-builder/src/lib/components/Toast.svelte +67 -0
  72. crew-builder/src/lib/components/Toolbar.svelte +157 -0
  73. crew-builder/src/lib/components/index.ts +10 -0
  74. crew-builder/src/lib/config.ts +8 -0
  75. crew-builder/src/lib/stores/auth.svelte.ts +228 -0
  76. crew-builder/src/lib/stores/crewStore.ts +369 -0
  77. crew-builder/src/lib/stores/theme.svelte.js +145 -0
  78. crew-builder/src/lib/stores/toast.svelte.ts +69 -0
  79. crew-builder/src/lib/utils/conversation.ts +39 -0
  80. crew-builder/src/lib/utils/markdown.ts +122 -0
  81. crew-builder/src/lib/utils/talkHistory.ts +47 -0
  82. crew-builder/src/routes/+layout.svelte +20 -0
  83. crew-builder/src/routes/+page.svelte +539 -0
  84. crew-builder/src/routes/agents/+page.svelte +247 -0
  85. crew-builder/src/routes/agents/[agentId]/+page.svelte +288 -0
  86. crew-builder/src/routes/agents/[agentId]/+page.ts +7 -0
  87. crew-builder/src/routes/builder/+page.svelte +204 -0
  88. crew-builder/src/routes/crew/ask/+page.svelte +1052 -0
  89. crew-builder/src/routes/crew/ask/+page.ts +1 -0
  90. crew-builder/src/routes/integrations/o365/+page.svelte +304 -0
  91. crew-builder/src/routes/login/+page.svelte +197 -0
  92. crew-builder/src/routes/talk/[agentId]/+page.svelte +487 -0
  93. crew-builder/src/routes/talk/[agentId]/+page.ts +7 -0
  94. crew-builder/static/README.md +1 -0
  95. crew-builder/svelte.config.js +11 -0
  96. crew-builder/tailwind.config.ts +53 -0
  97. crew-builder/tsconfig.json +3 -0
  98. crew-builder/vite.config.ts +10 -0
  99. mcp_servers/calculator_server.py +309 -0
  100. parrot/__init__.py +27 -0
  101. parrot/__pycache__/__init__.cpython-310.pyc +0 -0
  102. parrot/__pycache__/version.cpython-310.pyc +0 -0
  103. parrot/_version.py +34 -0
  104. parrot/a2a/__init__.py +48 -0
  105. parrot/a2a/client.py +658 -0
  106. parrot/a2a/discovery.py +89 -0
  107. parrot/a2a/mixin.py +257 -0
  108. parrot/a2a/models.py +376 -0
  109. parrot/a2a/server.py +770 -0
  110. parrot/agents/__init__.py +29 -0
  111. parrot/bots/__init__.py +12 -0
  112. parrot/bots/a2a_agent.py +19 -0
  113. parrot/bots/abstract.py +3139 -0
  114. parrot/bots/agent.py +1129 -0
  115. parrot/bots/basic.py +9 -0
  116. parrot/bots/chatbot.py +669 -0
  117. parrot/bots/data.py +1618 -0
  118. parrot/bots/database/__init__.py +5 -0
  119. parrot/bots/database/abstract.py +3071 -0
  120. parrot/bots/database/cache.py +286 -0
  121. parrot/bots/database/models.py +468 -0
  122. parrot/bots/database/prompts.py +154 -0
  123. parrot/bots/database/retries.py +98 -0
  124. parrot/bots/database/router.py +269 -0
  125. parrot/bots/database/sql.py +41 -0
  126. parrot/bots/db/__init__.py +6 -0
  127. parrot/bots/db/abstract.py +556 -0
  128. parrot/bots/db/bigquery.py +602 -0
  129. parrot/bots/db/cache.py +85 -0
  130. parrot/bots/db/documentdb.py +668 -0
  131. parrot/bots/db/elastic.py +1014 -0
  132. parrot/bots/db/influx.py +898 -0
  133. parrot/bots/db/mock.py +96 -0
  134. parrot/bots/db/multi.py +783 -0
  135. parrot/bots/db/prompts.py +185 -0
  136. parrot/bots/db/sql.py +1255 -0
  137. parrot/bots/db/tools.py +212 -0
  138. parrot/bots/document.py +680 -0
  139. parrot/bots/hrbot.py +15 -0
  140. parrot/bots/kb.py +170 -0
  141. parrot/bots/mcp.py +36 -0
  142. parrot/bots/orchestration/README.md +463 -0
  143. parrot/bots/orchestration/__init__.py +1 -0
  144. parrot/bots/orchestration/agent.py +155 -0
  145. parrot/bots/orchestration/crew.py +3330 -0
  146. parrot/bots/orchestration/fsm.py +1179 -0
  147. parrot/bots/orchestration/hr.py +434 -0
  148. parrot/bots/orchestration/storage/__init__.py +4 -0
  149. parrot/bots/orchestration/storage/memory.py +100 -0
  150. parrot/bots/orchestration/storage/mixin.py +119 -0
  151. parrot/bots/orchestration/verify.py +202 -0
  152. parrot/bots/product.py +204 -0
  153. parrot/bots/prompts/__init__.py +96 -0
  154. parrot/bots/prompts/agents.py +155 -0
  155. parrot/bots/prompts/data.py +216 -0
  156. parrot/bots/prompts/output_generation.py +8 -0
  157. parrot/bots/scraper/__init__.py +3 -0
  158. parrot/bots/scraper/models.py +122 -0
  159. parrot/bots/scraper/scraper.py +1173 -0
  160. parrot/bots/scraper/templates.py +115 -0
  161. parrot/bots/stores/__init__.py +5 -0
  162. parrot/bots/stores/local.py +172 -0
  163. parrot/bots/webdev.py +81 -0
  164. parrot/cli.py +17 -0
  165. parrot/clients/__init__.py +16 -0
  166. parrot/clients/base.py +1491 -0
  167. parrot/clients/claude.py +1191 -0
  168. parrot/clients/factory.py +129 -0
  169. parrot/clients/google.py +4567 -0
  170. parrot/clients/gpt.py +1975 -0
  171. parrot/clients/grok.py +432 -0
  172. parrot/clients/groq.py +986 -0
  173. parrot/clients/hf.py +582 -0
  174. parrot/clients/models.py +18 -0
  175. parrot/conf.py +395 -0
  176. parrot/embeddings/__init__.py +9 -0
  177. parrot/embeddings/base.py +157 -0
  178. parrot/embeddings/google.py +98 -0
  179. parrot/embeddings/huggingface.py +74 -0
  180. parrot/embeddings/openai.py +84 -0
  181. parrot/embeddings/processor.py +88 -0
  182. parrot/exceptions.c +13868 -0
  183. parrot/exceptions.cpython-310-x86_64-linux-gnu.so +0 -0
  184. parrot/exceptions.pxd +22 -0
  185. parrot/exceptions.pxi +15 -0
  186. parrot/exceptions.pyx +44 -0
  187. parrot/generators/__init__.py +29 -0
  188. parrot/generators/base.py +200 -0
  189. parrot/generators/html.py +293 -0
  190. parrot/generators/react.py +205 -0
  191. parrot/generators/streamlit.py +203 -0
  192. parrot/generators/template.py +105 -0
  193. parrot/handlers/__init__.py +4 -0
  194. parrot/handlers/agent.py +861 -0
  195. parrot/handlers/agents/__init__.py +1 -0
  196. parrot/handlers/agents/abstract.py +900 -0
  197. parrot/handlers/bots.py +338 -0
  198. parrot/handlers/chat.py +915 -0
  199. parrot/handlers/creation.sql +192 -0
  200. parrot/handlers/crew/ARCHITECTURE.md +362 -0
  201. parrot/handlers/crew/README_BOTMANAGER_PERSISTENCE.md +303 -0
  202. parrot/handlers/crew/README_REDIS_PERSISTENCE.md +366 -0
  203. parrot/handlers/crew/__init__.py +0 -0
  204. parrot/handlers/crew/handler.py +801 -0
  205. parrot/handlers/crew/models.py +229 -0
  206. parrot/handlers/crew/redis_persistence.py +523 -0
  207. parrot/handlers/jobs/__init__.py +10 -0
  208. parrot/handlers/jobs/job.py +384 -0
  209. parrot/handlers/jobs/mixin.py +627 -0
  210. parrot/handlers/jobs/models.py +115 -0
  211. parrot/handlers/jobs/worker.py +31 -0
  212. parrot/handlers/models.py +596 -0
  213. parrot/handlers/o365_auth.py +105 -0
  214. parrot/handlers/stream.py +337 -0
  215. parrot/interfaces/__init__.py +6 -0
  216. parrot/interfaces/aws.py +143 -0
  217. parrot/interfaces/credentials.py +113 -0
  218. parrot/interfaces/database.py +27 -0
  219. parrot/interfaces/google.py +1123 -0
  220. parrot/interfaces/hierarchy.py +1227 -0
  221. parrot/interfaces/http.py +651 -0
  222. parrot/interfaces/images/__init__.py +0 -0
  223. parrot/interfaces/images/plugins/__init__.py +24 -0
  224. parrot/interfaces/images/plugins/abstract.py +58 -0
  225. parrot/interfaces/images/plugins/analisys.py +148 -0
  226. parrot/interfaces/images/plugins/classify.py +150 -0
  227. parrot/interfaces/images/plugins/classifybase.py +182 -0
  228. parrot/interfaces/images/plugins/detect.py +150 -0
  229. parrot/interfaces/images/plugins/exif.py +1103 -0
  230. parrot/interfaces/images/plugins/hash.py +52 -0
  231. parrot/interfaces/images/plugins/vision.py +104 -0
  232. parrot/interfaces/images/plugins/yolo.py +66 -0
  233. parrot/interfaces/images/plugins/zerodetect.py +197 -0
  234. parrot/interfaces/o365.py +978 -0
  235. parrot/interfaces/onedrive.py +822 -0
  236. parrot/interfaces/sharepoint.py +1435 -0
  237. parrot/interfaces/soap.py +257 -0
  238. parrot/loaders/__init__.py +8 -0
  239. parrot/loaders/abstract.py +1131 -0
  240. parrot/loaders/audio.py +199 -0
  241. parrot/loaders/basepdf.py +53 -0
  242. parrot/loaders/basevideo.py +1568 -0
  243. parrot/loaders/csv.py +409 -0
  244. parrot/loaders/docx.py +116 -0
  245. parrot/loaders/epubloader.py +316 -0
  246. parrot/loaders/excel.py +199 -0
  247. parrot/loaders/factory.py +55 -0
  248. parrot/loaders/files/__init__.py +0 -0
  249. parrot/loaders/files/abstract.py +39 -0
  250. parrot/loaders/files/html.py +26 -0
  251. parrot/loaders/files/text.py +63 -0
  252. parrot/loaders/html.py +152 -0
  253. parrot/loaders/markdown.py +442 -0
  254. parrot/loaders/pdf.py +373 -0
  255. parrot/loaders/pdfmark.py +320 -0
  256. parrot/loaders/pdftables.py +506 -0
  257. parrot/loaders/ppt.py +476 -0
  258. parrot/loaders/qa.py +63 -0
  259. parrot/loaders/splitters/__init__.py +10 -0
  260. parrot/loaders/splitters/base.py +138 -0
  261. parrot/loaders/splitters/md.py +228 -0
  262. parrot/loaders/splitters/token.py +143 -0
  263. parrot/loaders/txt.py +26 -0
  264. parrot/loaders/video.py +89 -0
  265. parrot/loaders/videolocal.py +218 -0
  266. parrot/loaders/videounderstanding.py +377 -0
  267. parrot/loaders/vimeo.py +167 -0
  268. parrot/loaders/web.py +599 -0
  269. parrot/loaders/youtube.py +504 -0
  270. parrot/manager/__init__.py +5 -0
  271. parrot/manager/manager.py +1030 -0
  272. parrot/mcp/__init__.py +28 -0
  273. parrot/mcp/adapter.py +105 -0
  274. parrot/mcp/cli.py +174 -0
  275. parrot/mcp/client.py +119 -0
  276. parrot/mcp/config.py +75 -0
  277. parrot/mcp/integration.py +842 -0
  278. parrot/mcp/oauth.py +933 -0
  279. parrot/mcp/server.py +225 -0
  280. parrot/mcp/transports/__init__.py +3 -0
  281. parrot/mcp/transports/base.py +279 -0
  282. parrot/mcp/transports/grpc_session.py +163 -0
  283. parrot/mcp/transports/http.py +312 -0
  284. parrot/mcp/transports/mcp.proto +108 -0
  285. parrot/mcp/transports/quic.py +1082 -0
  286. parrot/mcp/transports/sse.py +330 -0
  287. parrot/mcp/transports/stdio.py +309 -0
  288. parrot/mcp/transports/unix.py +395 -0
  289. parrot/mcp/transports/websocket.py +547 -0
  290. parrot/memory/__init__.py +16 -0
  291. parrot/memory/abstract.py +209 -0
  292. parrot/memory/agent.py +32 -0
  293. parrot/memory/cache.py +175 -0
  294. parrot/memory/core.py +555 -0
  295. parrot/memory/file.py +153 -0
  296. parrot/memory/mem.py +131 -0
  297. parrot/memory/redis.py +613 -0
  298. parrot/models/__init__.py +46 -0
  299. parrot/models/basic.py +118 -0
  300. parrot/models/compliance.py +208 -0
  301. parrot/models/crew.py +395 -0
  302. parrot/models/detections.py +654 -0
  303. parrot/models/generation.py +85 -0
  304. parrot/models/google.py +223 -0
  305. parrot/models/groq.py +23 -0
  306. parrot/models/openai.py +30 -0
  307. parrot/models/outputs.py +285 -0
  308. parrot/models/responses.py +938 -0
  309. parrot/notifications/__init__.py +743 -0
  310. parrot/openapi/__init__.py +3 -0
  311. parrot/openapi/components.yaml +641 -0
  312. parrot/openapi/config.py +322 -0
  313. parrot/outputs/__init__.py +32 -0
  314. parrot/outputs/formats/__init__.py +108 -0
  315. parrot/outputs/formats/altair.py +359 -0
  316. parrot/outputs/formats/application.py +122 -0
  317. parrot/outputs/formats/base.py +351 -0
  318. parrot/outputs/formats/bokeh.py +356 -0
  319. parrot/outputs/formats/card.py +424 -0
  320. parrot/outputs/formats/chart.py +436 -0
  321. parrot/outputs/formats/d3.py +255 -0
  322. parrot/outputs/formats/echarts.py +310 -0
  323. parrot/outputs/formats/generators/__init__.py +0 -0
  324. parrot/outputs/formats/generators/abstract.py +61 -0
  325. parrot/outputs/formats/generators/panel.py +145 -0
  326. parrot/outputs/formats/generators/streamlit.py +86 -0
  327. parrot/outputs/formats/generators/terminal.py +63 -0
  328. parrot/outputs/formats/holoviews.py +310 -0
  329. parrot/outputs/formats/html.py +147 -0
  330. parrot/outputs/formats/jinja2.py +46 -0
  331. parrot/outputs/formats/json.py +87 -0
  332. parrot/outputs/formats/map.py +933 -0
  333. parrot/outputs/formats/markdown.py +172 -0
  334. parrot/outputs/formats/matplotlib.py +237 -0
  335. parrot/outputs/formats/mixins/__init__.py +0 -0
  336. parrot/outputs/formats/mixins/emaps.py +855 -0
  337. parrot/outputs/formats/plotly.py +341 -0
  338. parrot/outputs/formats/seaborn.py +310 -0
  339. parrot/outputs/formats/table.py +397 -0
  340. parrot/outputs/formats/template_report.py +138 -0
  341. parrot/outputs/formats/yaml.py +125 -0
  342. parrot/outputs/formatter.py +152 -0
  343. parrot/outputs/templates/__init__.py +95 -0
  344. parrot/pipelines/__init__.py +0 -0
  345. parrot/pipelines/abstract.py +210 -0
  346. parrot/pipelines/detector.py +124 -0
  347. parrot/pipelines/models.py +90 -0
  348. parrot/pipelines/planogram.py +3002 -0
  349. parrot/pipelines/table.sql +97 -0
  350. parrot/plugins/__init__.py +106 -0
  351. parrot/plugins/importer.py +80 -0
  352. parrot/py.typed +0 -0
  353. parrot/registry/__init__.py +18 -0
  354. parrot/registry/registry.py +594 -0
  355. parrot/scheduler/__init__.py +1189 -0
  356. parrot/scheduler/models.py +60 -0
  357. parrot/security/__init__.py +16 -0
  358. parrot/security/prompt_injection.py +268 -0
  359. parrot/security/security_events.sql +25 -0
  360. parrot/services/__init__.py +1 -0
  361. parrot/services/mcp/__init__.py +8 -0
  362. parrot/services/mcp/config.py +13 -0
  363. parrot/services/mcp/server.py +295 -0
  364. parrot/services/o365_remote_auth.py +235 -0
  365. parrot/stores/__init__.py +7 -0
  366. parrot/stores/abstract.py +352 -0
  367. parrot/stores/arango.py +1090 -0
  368. parrot/stores/bigquery.py +1377 -0
  369. parrot/stores/cache.py +106 -0
  370. parrot/stores/empty.py +10 -0
  371. parrot/stores/faiss_store.py +1157 -0
  372. parrot/stores/kb/__init__.py +9 -0
  373. parrot/stores/kb/abstract.py +68 -0
  374. parrot/stores/kb/cache.py +165 -0
  375. parrot/stores/kb/doc.py +325 -0
  376. parrot/stores/kb/hierarchy.py +346 -0
  377. parrot/stores/kb/local.py +457 -0
  378. parrot/stores/kb/prompt.py +28 -0
  379. parrot/stores/kb/redis.py +659 -0
  380. parrot/stores/kb/store.py +115 -0
  381. parrot/stores/kb/user.py +374 -0
  382. parrot/stores/models.py +59 -0
  383. parrot/stores/pgvector.py +3 -0
  384. parrot/stores/postgres.py +2853 -0
  385. parrot/stores/utils/__init__.py +0 -0
  386. parrot/stores/utils/chunking.py +197 -0
  387. parrot/telemetry/__init__.py +3 -0
  388. parrot/telemetry/mixin.py +111 -0
  389. parrot/template/__init__.py +3 -0
  390. parrot/template/engine.py +259 -0
  391. parrot/tools/__init__.py +23 -0
  392. parrot/tools/abstract.py +644 -0
  393. parrot/tools/agent.py +363 -0
  394. parrot/tools/arangodbsearch.py +537 -0
  395. parrot/tools/arxiv_tool.py +188 -0
  396. parrot/tools/calculator/__init__.py +3 -0
  397. parrot/tools/calculator/operations/__init__.py +38 -0
  398. parrot/tools/calculator/operations/calculus.py +80 -0
  399. parrot/tools/calculator/operations/statistics.py +76 -0
  400. parrot/tools/calculator/tool.py +150 -0
  401. parrot/tools/cloudwatch.py +988 -0
  402. parrot/tools/codeinterpreter/__init__.py +127 -0
  403. parrot/tools/codeinterpreter/executor.py +371 -0
  404. parrot/tools/codeinterpreter/internals.py +473 -0
  405. parrot/tools/codeinterpreter/models.py +643 -0
  406. parrot/tools/codeinterpreter/prompts.py +224 -0
  407. parrot/tools/codeinterpreter/tool.py +664 -0
  408. parrot/tools/company_info/__init__.py +6 -0
  409. parrot/tools/company_info/tool.py +1138 -0
  410. parrot/tools/correlationanalysis.py +437 -0
  411. parrot/tools/database/abstract.py +286 -0
  412. parrot/tools/database/bq.py +115 -0
  413. parrot/tools/database/cache.py +284 -0
  414. parrot/tools/database/models.py +95 -0
  415. parrot/tools/database/pg.py +343 -0
  416. parrot/tools/databasequery.py +1159 -0
  417. parrot/tools/db.py +1800 -0
  418. parrot/tools/ddgo.py +370 -0
  419. parrot/tools/decorators.py +271 -0
  420. parrot/tools/dftohtml.py +282 -0
  421. parrot/tools/document.py +549 -0
  422. parrot/tools/ecs.py +819 -0
  423. parrot/tools/edareport.py +368 -0
  424. parrot/tools/elasticsearch.py +1049 -0
  425. parrot/tools/employees.py +462 -0
  426. parrot/tools/epson/__init__.py +96 -0
  427. parrot/tools/excel.py +683 -0
  428. parrot/tools/file/__init__.py +13 -0
  429. parrot/tools/file/abstract.py +76 -0
  430. parrot/tools/file/gcs.py +378 -0
  431. parrot/tools/file/local.py +284 -0
  432. parrot/tools/file/s3.py +511 -0
  433. parrot/tools/file/tmp.py +309 -0
  434. parrot/tools/file/tool.py +501 -0
  435. parrot/tools/file_reader.py +129 -0
  436. parrot/tools/flowtask/__init__.py +19 -0
  437. parrot/tools/flowtask/tool.py +761 -0
  438. parrot/tools/gittoolkit.py +508 -0
  439. parrot/tools/google/__init__.py +18 -0
  440. parrot/tools/google/base.py +169 -0
  441. parrot/tools/google/tools.py +1251 -0
  442. parrot/tools/googlelocation.py +5 -0
  443. parrot/tools/googleroutes.py +5 -0
  444. parrot/tools/googlesearch.py +5 -0
  445. parrot/tools/googlesitesearch.py +5 -0
  446. parrot/tools/googlevoice.py +2 -0
  447. parrot/tools/gvoice.py +695 -0
  448. parrot/tools/ibisworld/README.md +225 -0
  449. parrot/tools/ibisworld/__init__.py +11 -0
  450. parrot/tools/ibisworld/tool.py +366 -0
  451. parrot/tools/jiratoolkit.py +1718 -0
  452. parrot/tools/manager.py +1098 -0
  453. parrot/tools/math.py +152 -0
  454. parrot/tools/metadata.py +476 -0
  455. parrot/tools/msteams.py +1621 -0
  456. parrot/tools/msword.py +635 -0
  457. parrot/tools/multidb.py +580 -0
  458. parrot/tools/multistoresearch.py +369 -0
  459. parrot/tools/networkninja.py +167 -0
  460. parrot/tools/nextstop/__init__.py +4 -0
  461. parrot/tools/nextstop/base.py +286 -0
  462. parrot/tools/nextstop/employee.py +733 -0
  463. parrot/tools/nextstop/store.py +462 -0
  464. parrot/tools/notification.py +435 -0
  465. parrot/tools/o365/__init__.py +42 -0
  466. parrot/tools/o365/base.py +295 -0
  467. parrot/tools/o365/bundle.py +522 -0
  468. parrot/tools/o365/events.py +554 -0
  469. parrot/tools/o365/mail.py +992 -0
  470. parrot/tools/o365/onedrive.py +497 -0
  471. parrot/tools/o365/sharepoint.py +641 -0
  472. parrot/tools/openapi_toolkit.py +904 -0
  473. parrot/tools/openweather.py +527 -0
  474. parrot/tools/pdfprint.py +1001 -0
  475. parrot/tools/powerbi.py +518 -0
  476. parrot/tools/powerpoint.py +1113 -0
  477. parrot/tools/pricestool.py +146 -0
  478. parrot/tools/products/__init__.py +246 -0
  479. parrot/tools/prophet_tool.py +171 -0
  480. parrot/tools/pythonpandas.py +630 -0
  481. parrot/tools/pythonrepl.py +910 -0
  482. parrot/tools/qsource.py +436 -0
  483. parrot/tools/querytoolkit.py +395 -0
  484. parrot/tools/quickeda.py +827 -0
  485. parrot/tools/resttool.py +553 -0
  486. parrot/tools/retail/__init__.py +0 -0
  487. parrot/tools/retail/bby.py +528 -0
  488. parrot/tools/sandboxtool.py +703 -0
  489. parrot/tools/sassie/__init__.py +352 -0
  490. parrot/tools/scraping/__init__.py +7 -0
  491. parrot/tools/scraping/docs/select.md +466 -0
  492. parrot/tools/scraping/documentation.md +1278 -0
  493. parrot/tools/scraping/driver.py +436 -0
  494. parrot/tools/scraping/models.py +576 -0
  495. parrot/tools/scraping/options.py +85 -0
  496. parrot/tools/scraping/orchestrator.py +517 -0
  497. parrot/tools/scraping/readme.md +740 -0
  498. parrot/tools/scraping/tool.py +3115 -0
  499. parrot/tools/seasonaldetection.py +642 -0
  500. parrot/tools/shell_tool/__init__.py +5 -0
  501. parrot/tools/shell_tool/actions.py +408 -0
  502. parrot/tools/shell_tool/engine.py +155 -0
  503. parrot/tools/shell_tool/models.py +322 -0
  504. parrot/tools/shell_tool/tool.py +442 -0
  505. parrot/tools/site_search.py +214 -0
  506. parrot/tools/textfile.py +418 -0
  507. parrot/tools/think.py +378 -0
  508. parrot/tools/toolkit.py +298 -0
  509. parrot/tools/webapp_tool.py +187 -0
  510. parrot/tools/whatif.py +1279 -0
  511. parrot/tools/workday/MULTI_WSDL_EXAMPLE.md +249 -0
  512. parrot/tools/workday/__init__.py +6 -0
  513. parrot/tools/workday/models.py +1389 -0
  514. parrot/tools/workday/tool.py +1293 -0
  515. parrot/tools/yfinance_tool.py +306 -0
  516. parrot/tools/zipcode.py +217 -0
  517. parrot/utils/__init__.py +2 -0
  518. parrot/utils/helpers.py +73 -0
  519. parrot/utils/parsers/__init__.py +5 -0
  520. parrot/utils/parsers/toml.c +12078 -0
  521. parrot/utils/parsers/toml.cpython-310-x86_64-linux-gnu.so +0 -0
  522. parrot/utils/parsers/toml.pyx +21 -0
  523. parrot/utils/toml.py +11 -0
  524. parrot/utils/types.cpp +20936 -0
  525. parrot/utils/types.cpython-310-x86_64-linux-gnu.so +0 -0
  526. parrot/utils/types.pyx +213 -0
  527. parrot/utils/uv.py +11 -0
  528. parrot/version.py +10 -0
  529. parrot/yaml-rs/Cargo.lock +350 -0
  530. parrot/yaml-rs/Cargo.toml +19 -0
  531. parrot/yaml-rs/pyproject.toml +19 -0
  532. parrot/yaml-rs/python/yaml_rs/__init__.py +81 -0
  533. parrot/yaml-rs/src/lib.rs +222 -0
  534. requirements/docker-compose.yml +24 -0
  535. 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