agentengine-sdk-python 0.8.2__py3-none-any.whl → 0.8.3__py3-none-any.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 (250) hide show
  1. {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/METADATA +18 -25
  2. {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/RECORD +224 -169
  3. ksadk/_build_provenance.json +1 -0
  4. ksadk/agui/_agent_helpers.py +4 -0
  5. ksadk/api/client.py +3 -0
  6. ksadk/builders/managed_runtime_builder.py +3 -0
  7. ksadk/cli/__init__.py +6 -0
  8. ksadk/cli/cmd_plugin.py +657 -0
  9. ksadk/codex/runtime.py +50 -8
  10. ksadk/configs/env_registry.py +31 -3
  11. ksadk/configs/settings.py +9 -0
  12. ksadk/context_engine/contributors.py +73 -8
  13. ksadk/conversations/__init__.py +19 -0
  14. ksadk/conversations/context.py +104 -24
  15. ksadk/conversations/contracts.py +266 -0
  16. ksadk/conversations/message_projection.py +19 -1
  17. ksadk/conversations/projector.py +616 -0
  18. ksadk/conversations/reducer.py +98 -0
  19. ksadk/events/_v1_compat/projection.py +2 -0
  20. ksadk/events/adapters/codex.py +190 -0
  21. ksadk/harness/__init__.py +45 -35
  22. ksadk/harness/runtime.py +60 -1
  23. ksadk/kernel/contract_fingerprints.py +5 -4
  24. ksadk/kernel/contracts.py +8 -0
  25. ksadk/kernel/control.py +1 -0
  26. ksadk/kernel/ingress.py +21 -0
  27. ksadk/kernel/memory_store.py +1 -0
  28. ksadk/kernel/postgres_store.py +1 -0
  29. ksadk/kernel/sqlite_store.py +1 -0
  30. ksadk/kernel/worker.py +11 -0
  31. ksadk/memory/providers/local_sqlite.py +7 -0
  32. ksadk/plugins/__init__.py +167 -0
  33. ksadk/plugins/bridges/__init__.py +45 -0
  34. ksadk/plugins/bridges/codex.py +552 -0
  35. ksadk/plugins/bridges/dsh.py +1116 -0
  36. ksadk/plugins/builtins.py +747 -0
  37. ksadk/plugins/bundle.py +246 -0
  38. ksadk/plugins/bundle_security.py +167 -0
  39. ksadk/plugins/composition.py +529 -0
  40. ksadk/plugins/context_contributor.py +437 -0
  41. ksadk/plugins/contracts.py +451 -0
  42. ksadk/plugins/dsh_toolchain.py +935 -0
  43. ksadk/plugins/ecosystem_bridge.py +1105 -0
  44. ksadk/plugins/ecosystem_probe.py +191 -0
  45. ksadk/plugins/host.py +724 -0
  46. ksadk/plugins/providers/__init__.py +85 -0
  47. ksadk/plugins/providers/bundles/ksadk-codex/cordis.patch.yml +3 -0
  48. ksadk/plugins/providers/bundles/ksadk-codex/index.mjs +18 -0
  49. ksadk/plugins/providers/bundles/ksadk-codex/package.json +19 -0
  50. ksadk/plugins/providers/bundles/ksadk-harness/cordis.patch.yml +3 -0
  51. ksadk/plugins/providers/bundles/ksadk-harness/index.mjs +19 -0
  52. ksadk/plugins/providers/bundles/ksadk-harness/package.json +19 -0
  53. ksadk/plugins/providers/codex.py +722 -0
  54. ksadk/plugins/providers/codex_dsh.py +83 -0
  55. ksadk/plugins/providers/dsh.py +951 -0
  56. ksadk/plugins/providers/dsh_descriptor_host.py +226 -0
  57. ksadk/plugins/providers/harness.py +627 -0
  58. ksadk/plugins/providers/harness_dsh.py +91 -0
  59. ksadk/plugins/providers/legacy.py +215 -0
  60. ksadk/plugins/providers/legacy_catalog.py +100 -0
  61. ksadk/plugins/providers/shipped_dsh.py +370 -0
  62. ksadk/plugins/resolver.py +331 -0
  63. ksadk/plugins/subagent_providers/__init__.py +12 -0
  64. ksadk/plugins/subagent_providers/codex.py +443 -0
  65. ksadk/plugins/subagents.py +333 -0
  66. ksadk/runtime/_runner_adapter/stream_mapping.py +51 -42
  67. ksadk/runtime/adapter.py +1 -0
  68. ksadk/runtime/factory.py +76 -2
  69. ksadk/runtime/framework_adapters.py +18 -0
  70. ksadk/runtime/runner_adapter.py +5 -0
  71. ksadk/sandbox/__init__.py +2 -1
  72. ksadk/sandbox/factory.py +72 -0
  73. ksadk/scheduler/__init__.py +18 -0
  74. ksadk/scheduler/calendar.py +102 -0
  75. ksadk/scheduler/contracts.py +254 -0
  76. ksadk/scheduler/dispatcher.py +129 -0
  77. ksadk/scheduler/engine.py +416 -0
  78. ksadk/scheduler/sqlite_store.py +563 -0
  79. ksadk/server/static/assets/{ArtifactsPanel-DSU1sWD_.js → ArtifactsPanel-10GPXIJ6.js} +1 -1
  80. ksadk/server/static/assets/{CodeBlock-DOH6MVcn.js → CodeBlock-BXk1l-G1.js} +1 -1
  81. ksadk/server/static/assets/{MathMessageMarkdown-BL8my1R2.js → MathMessageMarkdown-D9uB55Oh.js} +1 -1
  82. ksadk/server/static/assets/{MermaidBlock-Dz4IP-Tx.js → MermaidBlock--OEYoXIJ.js} +6 -6
  83. ksadk/server/static/assets/{NativeTerminalPanel-DUrK0JpZ.js → NativeTerminalPanel-DrEe1syq.js} +1 -1
  84. ksadk/server/static/assets/{abnfDiagram-VCTEODGH-g20pFzNV.js → abnfDiagram-VCTEODGH-eeTFJLvo.js} +1 -1
  85. ksadk/server/static/assets/{arc-C4FzinUA.js → arc-S3ZNbIAd.js} +1 -1
  86. ksadk/server/static/assets/architecture-7GRP2DOG-Dk9nRQ6-.js +1 -0
  87. ksadk/server/static/assets/{architectureDiagram-5GKGNRK7-DpIqL5h4.js → architectureDiagram-5GKGNRK7-CRjgEJDX.js} +1 -1
  88. ksadk/server/static/assets/{blockDiagram-NRAW4CY4-BjkljTNz.js → blockDiagram-NRAW4CY4-C7bsBoNH.js} +1 -1
  89. ksadk/server/static/assets/{c4Diagram-UCG6FXSJ-Dk_ieq2X.js → c4Diagram-UCG6FXSJ-BX3c33Ob.js} +1 -1
  90. ksadk/server/static/assets/channel-s354Yo6o.js +1 -0
  91. ksadk/server/static/assets/chunk-2Q5K7J3B-CDpPpKR5.js +1 -0
  92. ksadk/server/static/assets/{chunk-5VM5RSS4-BVHAchFb.js → chunk-5VM5RSS4-bZ7ws-8m.js} +1 -1
  93. ksadk/server/static/assets/{chunk-F27PBJKO-C-ipQzuS.js → chunk-F27PBJKO-D2r0pvhY.js} +1 -1
  94. ksadk/server/static/assets/{chunk-G27WJ6UU-C2EyJbIK.js → chunk-G27WJ6UU-CZwF3oUP.js} +1 -1
  95. ksadk/server/static/assets/{chunk-JWPE2WC7-vYvVJb_M.js → chunk-JWPE2WC7-DigFYCML.js} +1 -1
  96. ksadk/server/static/assets/{chunk-LCL6LL3I-BcI6ysPT.js → chunk-LCL6LL3I-CnorGV5q.js} +1 -1
  97. ksadk/server/static/assets/{chunk-POPQ4Y6H-C030x_Z1.js → chunk-POPQ4Y6H-CexntQA-.js} +1 -1
  98. ksadk/server/static/assets/{chunk-RHFEMEQ7-yyGpsz4n.js → chunk-RHFEMEQ7-CHTC7UxA.js} +1 -1
  99. ksadk/server/static/assets/{chunk-SVP7TREG-BLTlmMU7.js → chunk-SVP7TREG-BC1EWt_-.js} +1 -1
  100. ksadk/server/static/assets/{chunk-XXDRQBXY-C_32ArgP.js → chunk-XXDRQBXY-BTG24xN0.js} +1 -1
  101. ksadk/server/static/assets/classDiagram-DTDB5LWJ-Dd0nucl9.js +1 -0
  102. ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-Dd0nucl9.js +1 -0
  103. ksadk/server/static/assets/{cose-bilkent-JH36ORCC-9YQYwdl0.js → cose-bilkent-JH36ORCC-CkonRdAS.js} +1 -1
  104. ksadk/server/static/assets/cynefin-OW5HDTMX-BBlT4uMf.js +1 -0
  105. ksadk/server/static/assets/{cynefinDiagram-5FMLGOSQ-ErLF5N13.js → cynefinDiagram-5FMLGOSQ-DEOCBe15.js} +1 -1
  106. ksadk/server/static/assets/{dagre-3AP2YEHR-DFiQF-6f.js → dagre-3AP2YEHR-DBThPuLa.js} +1 -1
  107. ksadk/server/static/assets/{dagre-BuhGnRI1.js → dagre-ClTPrTht.js} +1 -1
  108. ksadk/server/static/assets/{diagram-S7CK7UJ4-Bt1v8-GC.js → diagram-S7CK7UJ4-DsZHrnpJ.js} +1 -1
  109. ksadk/server/static/assets/{diagram-UQ7AKVKN-DSCxJdBK.js → diagram-UQ7AKVKN-BJR4nhVr.js} +1 -1
  110. ksadk/server/static/assets/{diagram-VSXAHHWV-DHfYwp_9.js → diagram-VSXAHHWV-xzcERX0a.js} +1 -1
  111. ksadk/server/static/assets/{diagram-VX7I27RA-DdzD-4Le.js → diagram-VX7I27RA-tn2xM1xu.js} +1 -1
  112. ksadk/server/static/assets/{diagram-Z3DM3KII-Du-nAJ9F.js → diagram-Z3DM3KII-BZPGBkw5.js} +1 -1
  113. ksadk/server/static/assets/{dist-ClxGviKw.js → dist-4zN1_tq5.js} +1 -1
  114. ksadk/server/static/assets/{dist-BE7bu-DL.js → dist-8VWI-2bZ.js} +1 -1
  115. ksadk/server/static/assets/{dist-BX8z72IC.js → dist-B-46q3Hk.js} +1 -1
  116. ksadk/server/static/assets/{dist-BjU6y-A2.js → dist-B0gdk4rl.js} +1 -1
  117. ksadk/server/static/assets/{dist-C_wsv-Qd.js → dist-B1oWRmrH.js} +1 -1
  118. ksadk/server/static/assets/{dist-ngtN0DJB.js → dist-BI1qQH4n.js} +1 -1
  119. ksadk/server/static/assets/{dist-dydu68Fl.js → dist-BJLUwH6W.js} +1 -1
  120. ksadk/server/static/assets/{dist-Dq9gLD7L.js → dist-Bbwqa8TG.js} +1 -1
  121. ksadk/server/static/assets/{dist-CttYUqzS.js → dist-Btyo4vX4.js} +1 -1
  122. ksadk/server/static/assets/{dist-CXYYBw3_.js → dist-C71CsujO.js} +1 -1
  123. ksadk/server/static/assets/{dist-dDdADKFA.js → dist-CXpH_Rdd.js} +1 -1
  124. ksadk/server/static/assets/{dist-CSh_DE14.js → dist-Cq4OL6nO.js} +1 -1
  125. ksadk/server/static/assets/{dist-8hqcx0sU.js → dist-D1V3f1B4.js} +1 -1
  126. ksadk/server/static/assets/{dist-BVDffZ0U.js → dist-DJmCPRVB.js} +1 -1
  127. ksadk/server/static/assets/{dist-DnfXs8Vn.js → dist-DQAB0qn_.js} +1 -1
  128. ksadk/server/static/assets/{dist-DNp6rLRA.js → dist-DUwKa_l6.js} +1 -1
  129. ksadk/server/static/assets/{dist-B7seoj3d.js → dist-DdRBIceA.js} +1 -1
  130. ksadk/server/static/assets/{dist-BytlxIDT.js → dist-DfGFrb0v.js} +1 -1
  131. ksadk/server/static/assets/{dist-Bf2M8m3N.js → dist-DjhwGUgZ.js} +1 -1
  132. ksadk/server/static/assets/{dist-W-TIVntx.js → dist-DyqV4fL4.js} +1 -1
  133. ksadk/server/static/assets/{dist-DUX-id_F.js → dist-M67Hwu0n.js} +1 -1
  134. ksadk/server/static/assets/{dist-DmldrHd_.js → dist-u8tVH5Ak.js} +1 -1
  135. ksadk/server/static/assets/{ebnfDiagram-PWID7BFC-Da9C9NO1.js → ebnfDiagram-PWID7BFC-DVfMsLge.js} +1 -1
  136. ksadk/server/static/assets/{erDiagram-SSCWMZ5O-EAGqqR79.js → erDiagram-SSCWMZ5O-CWEZSYaw.js} +1 -1
  137. ksadk/server/static/assets/eventmodeling-NTZA5JFV-Dr5okd8V.js +1 -0
  138. ksadk/server/static/assets/flowDiagram-A5DVABFB-VEO-cVcO.js +1 -0
  139. ksadk/server/static/assets/{ganttDiagram-EL5Y4UJY-DqJsKb59.js → ganttDiagram-EL5Y4UJY-TWIxoyYL.js} +1 -1
  140. ksadk/server/static/assets/gitGraph-4MIJSDKK-CKiKY9Cf.js +1 -0
  141. ksadk/server/static/assets/{gitGraphDiagram-WWUBYQGX-B6g4dtDi.js → gitGraphDiagram-WWUBYQGX-DDh2hd2S.js} +1 -1
  142. ksadk/server/static/assets/index-B2k_urY8.js +241 -0
  143. ksadk/server/static/assets/index-ILPt6IWF.css +2 -0
  144. ksadk/server/static/assets/info-A6RAGUB7-ubA4Nm_p.js +1 -0
  145. ksadk/server/static/assets/{infoDiagram-RXCK75RN-Djm_nbd5.js → infoDiagram-RXCK75RN-CgoyWiDR.js} +1 -1
  146. ksadk/server/static/assets/{ishikawaDiagram-5VMMS53U-DWya9SG2.js → ishikawaDiagram-5VMMS53U-CyRIu_om.js} +1 -1
  147. ksadk/server/static/assets/{journeyDiagram-EYS64GPL-DutxhSNI.js → journeyDiagram-EYS64GPL-DN5JXHzN.js} +1 -1
  148. ksadk/server/static/assets/{kanban-definition-3QL26DDD-DJdOF8Fm.js → kanban-definition-3QL26DDD-CNP3ui66.js} +1 -1
  149. ksadk/server/static/assets/{katex-vFWytM5c.js → katex-DWEzQy1d.js} +1 -1
  150. ksadk/server/static/assets/{linear-BHjG8b-J.js → linear-C-qHp7d2.js} +1 -1
  151. ksadk/server/static/assets/{mermaid-parser.core-KGSy4jWT.js → mermaid-parser.core-Cl-K943T.js} +2 -2
  152. ksadk/server/static/assets/{mindmap-definition-FBJOCRG2-9-HC88D4.js → mindmap-definition-FBJOCRG2-D5DB5mYm.js} +1 -1
  153. ksadk/server/static/assets/packet-AYTQ26CC-C7R3NUlK.js +1 -0
  154. ksadk/server/static/assets/{pegDiagram-XKGWAZYB-DrEZd4fD.js → pegDiagram-XKGWAZYB-BQVc9ia_.js} +1 -1
  155. ksadk/server/static/assets/pie-WAS4IAKB-BIT6qjr3.js +1 -0
  156. ksadk/server/static/assets/{pieDiagram-E7YTZNPT-DpjqOFFp.js → pieDiagram-E7YTZNPT-B6UCAvtY.js} +1 -1
  157. ksadk/server/static/assets/{quadrantDiagram-AXDQQJYC-BiNhgOXX.js → quadrantDiagram-AXDQQJYC-mIr2d3eb.js} +1 -1
  158. ksadk/server/static/assets/radar-RG4KPBEZ-l5noef9X.js +1 -0
  159. ksadk/server/static/assets/railroad-74A4TZTK-Baea6ZvM.js +1 -0
  160. ksadk/server/static/assets/railroad-abnf-HS5TGJTU-OKn8zZnU.js +1 -0
  161. ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-DdKAKC4l.js +1 -0
  162. ksadk/server/static/assets/railroad-peg-WCYAUIDC-Bds8udlG.js +1 -0
  163. ksadk/server/static/assets/{railroadDiagram-O6MQD6OU-_Y3Ojg6G.js → railroadDiagram-O6MQD6OU-Dt3FFDmK.js} +1 -1
  164. ksadk/server/static/assets/{requirementDiagram-EFPCY7ZU-DeCFdg9K.js → requirementDiagram-EFPCY7ZU-Cz_vQtgF.js} +1 -1
  165. ksadk/server/static/assets/{sankeyDiagram-P5KCCOFB-CPH75rhw.js → sankeyDiagram-P5KCCOFB-wlO1TsH5.js} +1 -1
  166. ksadk/server/static/assets/{sequenceDiagram-WJ2MYXX4-OEpQQdr4.js → sequenceDiagram-WJ2MYXX4-CHRbElYq.js} +1 -1
  167. ksadk/server/static/assets/{sizeCapture-X5ZJPWSS-heUErzhI.js → sizeCapture-X5ZJPWSS-DXX54iyO.js} +1 -1
  168. ksadk/server/static/assets/{stateDiagram-HBIQ2CUA-DvA3jSMB.js → stateDiagram-HBIQ2CUA-Dp_lw9II.js} +1 -1
  169. ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-5b-8o2iS.js +1 -0
  170. ksadk/server/static/assets/{swimlanes-XN3QIQJK-CR-dfN_t.js → swimlanes-XN3QIQJK-D8O5UlLG.js} +1 -1
  171. ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-uf6WPRA4.js +8 -0
  172. ksadk/server/static/assets/{timeline-definition-24CTP7MA-DAVJf1TX.js → timeline-definition-24CTP7MA-8mfKcxvG.js} +1 -1
  173. ksadk/server/static/assets/treeView-Q6P3EWNA-CK5CSrSO.js +1 -0
  174. ksadk/server/static/assets/treemap-WGGIJYW6-Ch2BKgAM.js +1 -0
  175. ksadk/server/static/assets/{vennDiagram-4TSXK5OY-DwM8eDkh.js → vennDiagram-4TSXK5OY-EnNl0DE7.js} +1 -1
  176. ksadk/server/static/assets/wardley-WFR3VGLG-YW3Q0ml_.js +1 -0
  177. ksadk/server/static/assets/{wardleyDiagram-VM6X3IG4-DZL-Zz2t.js → wardleyDiagram-VM6X3IG4-DBu5fbYr.js} +1 -1
  178. ksadk/server/static/assets/{xychartDiagram-S5SC5T6Z-Dy6Yh_hu.js → xychartDiagram-S5SC5T6Z-BSVXs4IY.js} +1 -1
  179. ksadk/server/static/index.html +2 -2
  180. ksadk/skills/service_client.py +48 -0
  181. ksadk/studio/agent_lifecycle.py +26 -20
  182. ksadk/studio/api.py +513 -71
  183. ksadk/studio/api_catalog_routes.py +5 -0
  184. ksadk/studio/api_contracts.py +34 -1
  185. ksadk/studio/api_plugin_routes.py +480 -0
  186. ksadk/studio/attachment_store.py +187 -0
  187. ksadk/studio/authoring.py +10 -4
  188. ksadk/studio/authoring_coordinator.py +12 -2
  189. ksadk/studio/builder.py +123 -43
  190. ksadk/studio/cloud.py +4 -0
  191. ksadk/studio/codex_agent_service.py +12 -1
  192. ksadk/studio/codex_builder.py +54 -15
  193. ksadk/studio/codex_manifest.py +21 -1
  194. ksadk/studio/codex_run.py +16 -4
  195. ksadk/studio/compatibility_report.py +445 -0
  196. ksadk/studio/compiler.py +6 -1
  197. ksadk/studio/contracts.py +123 -4
  198. ksadk/studio/dsh_provider_registration.py +768 -0
  199. ksadk/studio/event_store.py +54 -6
  200. ksadk/studio/framework_run.py +37 -4
  201. ksadk/studio/hosted_kernel.py +1 -1
  202. ksadk/studio/operations.py +12 -2
  203. ksadk/studio/plugin_composition.py +301 -0
  204. ksadk/studio/plugin_kernel_adapter.py +128 -0
  205. ksadk/studio/plugin_runtime.py +652 -0
  206. ksadk/studio/resource_catalog.py +6 -1
  207. ksadk/studio/run_service.py +713 -75
  208. ksadk/studio/runtime_source.py +9 -1
  209. ksadk/studio/scheduler_runtime.py +374 -0
  210. ksadk/studio/scheduler_service.py +211 -0
  211. ksadk/studio/service.py +672 -82
  212. ksadk/studio/shared_web.py +167 -28
  213. ksadk/studio/soul.py +52 -0
  214. ksadk/studio/static/assets/index-DXs1Eq4M.css +1 -0
  215. ksadk/studio/static/assets/index-DeDqwTsD.js +283 -0
  216. ksadk/studio/static/index.html +2 -2
  217. ksadk/studio/validator.py +1 -1
  218. ksadk/studio/workspace.py +1 -0
  219. ksadk/version.py +1 -1
  220. ksadk_runtime_common/workspace_files/path_utils.py +41 -0
  221. ksadk/server/static/assets/architecture-7GRP2DOG-DjKratdf.js +0 -1
  222. ksadk/server/static/assets/channel-4cQHKtx1.js +0 -1
  223. ksadk/server/static/assets/chunk-2Q5K7J3B-DmgWkESh.js +0 -1
  224. ksadk/server/static/assets/classDiagram-DTDB5LWJ-DLFd9Xi0.js +0 -1
  225. ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-DLFd9Xi0.js +0 -1
  226. ksadk/server/static/assets/cynefin-OW5HDTMX-DjEM48Qp.js +0 -1
  227. ksadk/server/static/assets/eventmodeling-NTZA5JFV-CMgMaJrC.js +0 -1
  228. ksadk/server/static/assets/flowDiagram-A5DVABFB-BBKL0x3P.js +0 -1
  229. ksadk/server/static/assets/gitGraph-4MIJSDKK-Dw63mrhw.js +0 -1
  230. ksadk/server/static/assets/index-8ipRcQ-M.js +0 -240
  231. ksadk/server/static/assets/index-ByFCqjlh.css +0 -2
  232. ksadk/server/static/assets/info-A6RAGUB7-B6iAblII.js +0 -1
  233. ksadk/server/static/assets/packet-AYTQ26CC-BOYAipuq.js +0 -1
  234. ksadk/server/static/assets/pie-WAS4IAKB-Ctq0Kbku.js +0 -1
  235. ksadk/server/static/assets/radar-RG4KPBEZ-DN0-ul_0.js +0 -1
  236. ksadk/server/static/assets/railroad-74A4TZTK-C6cC_to0.js +0 -1
  237. ksadk/server/static/assets/railroad-abnf-HS5TGJTU-BimjIuRc.js +0 -1
  238. ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-CvY65ABq.js +0 -1
  239. ksadk/server/static/assets/railroad-peg-WCYAUIDC-D72Iq_Tv.js +0 -1
  240. ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BgQY03nz.js +0 -1
  241. ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-DLVw3q5Q.js +0 -8
  242. ksadk/server/static/assets/treeView-Q6P3EWNA-BMpO0wsB.js +0 -1
  243. ksadk/server/static/assets/treemap-WGGIJYW6-Et0aqzU3.js +0 -1
  244. ksadk/server/static/assets/wardley-WFR3VGLG-CbY0mhpl.js +0 -1
  245. ksadk/studio/static/assets/index-BLpcdrgW.css +0 -1
  246. ksadk/studio/static/assets/index-CYekR2Xv.js +0 -260
  247. {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/WHEEL +0 -0
  248. {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/entry_points.txt +0 -0
  249. {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/licenses/LICENSE +0 -0
  250. {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,768 @@
1
+ """Managed DSH AgentProvider registration for the normal Studio lifecycle.
2
+
3
+ Studio treats the selected DSH Profile as the installed/enabled source of
4
+ truth, then starts each AgentProvider contribution in its own fixed-command
5
+ sidecar. Only descriptor-fenced, ready registrations enter Studio's selector;
6
+ ordinary client-only DSH bundles remain visible to plugin management without
7
+ being mistaken for AgentProviders.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import asyncio
13
+ import json
14
+ import os
15
+ import shutil
16
+ import tempfile
17
+ from collections.abc import Callable, Mapping, Sequence
18
+ from dataclasses import dataclass
19
+ from pathlib import Path
20
+ from typing import Any, Literal
21
+
22
+ from pydantic import BaseModel, ConfigDict
23
+
24
+ from ksadk.plugins.bridges.dsh import (
25
+ DshPluginInventory,
26
+ DshProfilePluginBridge,
27
+ DshProfileProjection,
28
+ )
29
+ from ksadk.plugins.contracts import CompositionProfile, PluginManifest
30
+ from ksadk.plugins.dsh_toolchain import DshToolchainManager
31
+ from ksadk.plugins.host import ManagedPlugin, PluginHostError
32
+ from ksadk.plugins.providers.codex_dsh import (
33
+ SHIPPED_CODEX_DSH_PACKAGE,
34
+ SHIPPED_CODEX_PROVIDER_VERSION,
35
+ KsADKCodexDshBridgeFactory,
36
+ shipped_codex_dsh_bundle,
37
+ shipped_codex_dsh_host_command,
38
+ )
39
+ from ksadk.plugins.providers.dsh import (
40
+ DshAgentProviderFactory,
41
+ DshAgentProviderHost,
42
+ DshAgentProviderRegistration,
43
+ )
44
+ from ksadk.plugins.providers.harness_dsh import (
45
+ SHIPPED_HARNESS_DSH_PACKAGE,
46
+ SHIPPED_HARNESS_PROVIDER_VERSION,
47
+ KsADKHarnessDshBridgeFactory,
48
+ shipped_harness_dsh_bundle,
49
+ shipped_harness_dsh_host_command,
50
+ )
51
+
52
+ _PROFILE_FILES = ("package.json", "cordis.patch.yml", "index.mjs")
53
+ _MAX_PACKAGE_JSON_BYTES = 2 * 1024 * 1024
54
+ _DSH_PLATFORM_BUNDLES = frozenset({"@deepseek-ai/dsh-base"})
55
+ _SHIPPED_PROVIDER_PACKAGES = frozenset({SHIPPED_CODEX_DSH_PACKAGE, SHIPPED_HARNESS_DSH_PACKAGE})
56
+
57
+
58
+ class StudioDshProviderRegistrationError(RuntimeError):
59
+ """A selected DSH Profile could not produce trustworthy registrations."""
60
+
61
+ def __init__(self, code: str, message: str) -> None:
62
+ super().__init__(message)
63
+ self.code = code
64
+
65
+
66
+ class StudioDshProviderStatus(BaseModel):
67
+ """Lifecycle evidence for one installed DSH Profile package."""
68
+
69
+ model_config = ConfigDict(extra="forbid", frozen=True)
70
+
71
+ package_name: str
72
+ package_version: str
73
+ display_name: str = ""
74
+ state: Literal["installed", "enabled", "ready", "bound", "failed", "disposed"]
75
+ provider_ref: str | None = None
76
+ error_code: str | None = None
77
+
78
+
79
+ class StudioDshProviderInventory(BaseModel):
80
+ model_config = ConfigDict(extra="forbid", frozen=True)
81
+
82
+ state: Literal["stopped", "starting", "ready", "bound", "failed", "disposed"]
83
+ profile: str
84
+ profile_digest: str | None = None
85
+ providers: tuple[str, ...] = ()
86
+ packages: tuple[StudioDshProviderStatus, ...] = ()
87
+ error_code: str | None = None
88
+
89
+
90
+ @dataclass(frozen=True)
91
+ class StudioDshProviderRegistrations:
92
+ manifests: Mapping[str, PluginManifest]
93
+ factories: Mapping[str, Any]
94
+ inventory: StudioDshProviderInventory
95
+
96
+
97
+ @dataclass(frozen=True)
98
+ class _ProfileSnapshot:
99
+ projection: DshProfileProjection
100
+ packages: tuple[DshPluginInventory, ...]
101
+
102
+
103
+ BridgeFactory = Callable[..., DshProfilePluginBridge]
104
+ HostFactory = Callable[..., DshAgentProviderHost]
105
+
106
+
107
+ class _FreshDshAgentProviderFactory:
108
+ """Give every PluginHost graph an independently owned provider process."""
109
+
110
+ def __init__(
111
+ self,
112
+ command: Sequence[str],
113
+ *,
114
+ projection: DshProfileProjection,
115
+ cwd: Path,
116
+ environment: Mapping[str, str],
117
+ registration: DshAgentProviderRegistration,
118
+ host_factory: HostFactory,
119
+ ) -> None:
120
+ self._command = tuple(command)
121
+ self._projection = projection
122
+ self._cwd = cwd
123
+ self._environment = dict(environment)
124
+ self._registration = registration
125
+ self._host_factory = host_factory
126
+
127
+ async def stage(
128
+ self,
129
+ manifest: PluginManifest,
130
+ *,
131
+ profile: CompositionProfile,
132
+ services: Mapping[str, Any],
133
+ ) -> ManagedPlugin:
134
+ host = self._host_factory(
135
+ self._command,
136
+ projection=self._projection,
137
+ cwd=self._cwd,
138
+ environment=self._environment,
139
+ )
140
+ try:
141
+ current = await host.registration()
142
+ if current != self._registration:
143
+ raise PluginHostError(
144
+ "dsh_provider_registration_changed",
145
+ "DSH provider registration changed after Studio discovery",
146
+ )
147
+ return await DshAgentProviderFactory(host, current).stage(
148
+ manifest,
149
+ profile=profile,
150
+ services=services,
151
+ )
152
+ except BaseException:
153
+ await host.dispose()
154
+ raise
155
+
156
+
157
+ class StudioDshProviderRegistrationManager:
158
+ """Own Profile discovery, provider preflight, registration, and disposal."""
159
+
160
+ def __init__(
161
+ self,
162
+ workspace: Path,
163
+ *,
164
+ dsh_home: Path,
165
+ profile: str = "studio",
166
+ dsh_command: Sequence[str] | None = None,
167
+ node_command: Sequence[str] | None = None,
168
+ cordis_module: Path | None = None,
169
+ bridge_factory: BridgeFactory = DshProfilePluginBridge,
170
+ host_factory: HostFactory = DshAgentProviderHost,
171
+ ) -> None:
172
+ self._workspace = workspace.resolve()
173
+ self._dsh_home = dsh_home.expanduser().resolve()
174
+ self._profile = profile
175
+ self._dsh_command = tuple(dsh_command) if dsh_command is not None else None
176
+ self._node_command = tuple(node_command) if node_command is not None else None
177
+ self._cordis_module = cordis_module.resolve() if cordis_module is not None else None
178
+ self._bridge_factory = bridge_factory
179
+ self._host_factory = host_factory
180
+ self._lock = asyncio.Lock()
181
+ self._hosts: dict[str, DshAgentProviderHost] = {}
182
+ self._registrations: StudioDshProviderRegistrations | None = None
183
+ self._inventory = StudioDshProviderInventory(state="stopped", profile=profile)
184
+
185
+ @classmethod
186
+ def discover(cls, workspace: Path) -> "StudioDshProviderRegistrationManager | None":
187
+ """Discover an initialized managed Profile without downloading tools."""
188
+
189
+ configured_home = os.environ.get("KSADK_DSH_HOME", "").strip()
190
+ home = (
191
+ Path(configured_home).expanduser()
192
+ if configured_home
193
+ else workspace / ".agentkit" / "dsh-home"
194
+ )
195
+ profile = os.environ.get("KSADK_DSH_PROFILE", "").strip() or "studio"
196
+ configured_bin = os.environ.get("KSADK_DSH_BIN", "").strip()
197
+ manifest = home / "profiles" / profile / "package.json"
198
+ if not manifest.is_file():
199
+ return None
200
+ try:
201
+ payload = json.loads(manifest.read_text(encoding="utf-8"))
202
+ except (OSError, UnicodeError, json.JSONDecodeError):
203
+ return None
204
+ dsh = payload.get("dsh") if isinstance(payload, dict) else None
205
+ profile_payload = dsh.get("profile") if isinstance(dsh, dict) else None
206
+ bundles = profile_payload.get("bundles") if isinstance(profile_payload, dict) else None
207
+ if not isinstance(bundles, list) or any(not isinstance(item, str) for item in bundles):
208
+ return None
209
+ command = (str(Path(configured_bin).expanduser()),) if configured_bin else None
210
+ return cls(workspace, dsh_home=home, profile=profile, dsh_command=command)
211
+
212
+ @classmethod
213
+ def discover_or_create_workspace_default(
214
+ cls, workspace: Path
215
+ ) -> "StudioDshProviderRegistrationManager | None":
216
+ """Discover DSH or prepare the isolated first-run Studio Profile.
217
+
218
+ The default is deliberately narrower than :meth:`discover`: only the
219
+ Studio-owned ``.agentkit/dsh-home`` and ``studio`` Profile qualify for
220
+ automatic official-provider bootstrap. Explicit DSH homes/profiles
221
+ are user-owned and are never mutated by Studio startup.
222
+ """
223
+
224
+ configured_home = os.environ.get("KSADK_DSH_HOME", "").strip()
225
+ configured_profile = os.environ.get("KSADK_DSH_PROFILE", "").strip()
226
+ if configured_home or configured_profile:
227
+ return cls.discover(workspace)
228
+ root = workspace.resolve()
229
+ home = root / ".agentkit" / "dsh-home"
230
+ configured_bin = os.environ.get("KSADK_DSH_BIN", "").strip()
231
+ if configured_bin:
232
+ command: Sequence[str] | None = (str(Path(configured_bin).expanduser()),)
233
+ else:
234
+ try:
235
+ command = DshToolchainManager().require_command()
236
+ except Exception:
237
+ # DSH is optional. A missing toolchain must not make the
238
+ # normal Studio/legacy Codex path unavailable.
239
+ return None
240
+ return cls(root, dsh_home=home, profile="studio", dsh_command=command)
241
+
242
+ @property
243
+ def inventory(self) -> StudioDshProviderInventory:
244
+ return self._inventory
245
+
246
+ @property
247
+ def _owns_workspace_default_profile(self) -> bool:
248
+ configured_home = os.environ.get("KSADK_DSH_HOME", "").strip()
249
+ configured_profile = os.environ.get("KSADK_DSH_PROFILE", "").strip()
250
+ expected_home = (self._workspace / ".agentkit" / "dsh-home").resolve()
251
+ return (
252
+ not configured_home
253
+ and not configured_profile
254
+ and self._profile == "studio"
255
+ and self._dsh_home == expected_home
256
+ )
257
+
258
+ async def bootstrap_official_codex_provider(
259
+ self,
260
+ ) -> Literal["installed", "already_enabled", "disabled", "skipped"]:
261
+ """Apply the wheel-owned Codex default once for the Studio Profile.
262
+
263
+ This method never enables an existing disabled package and never
264
+ mutates an explicit/user-owned DSH Profile. The marker also preserves
265
+ an explicit uninstall across future Studio starts.
266
+ """
267
+
268
+ if not self._owns_workspace_default_profile:
269
+ return "skipped"
270
+ async with self._lock:
271
+ return await asyncio.to_thread(self._bootstrap_official_codex_provider_sync)
272
+
273
+ def _bootstrap_official_codex_provider_sync(
274
+ self,
275
+ ) -> Literal["installed", "already_enabled", "disabled", "skipped"]:
276
+ """Perform the blocking DSH package mutation outside the event loop."""
277
+
278
+ if not self._owns_workspace_default_profile:
279
+ return "skipped"
280
+ marker = self._default_marker_path
281
+ marker_payload = self._read_default_marker(marker)
282
+ command = self._dsh_command or DshToolchainManager().require_command()
283
+ shipped = shipped_codex_dsh_bundle()
284
+ with self._bridge_factory(
285
+ dsh_home=self._dsh_home,
286
+ profile=self._profile,
287
+ dsh_command=command,
288
+ cwd=self._workspace,
289
+ ) as bridge:
290
+ installed = {item.name: item for item in bridge.list_plugins()}
291
+ current = installed.get(SHIPPED_CODEX_DSH_PACKAGE)
292
+ if current is None:
293
+ if marker_payload.get("codexProviderApplied") is True:
294
+ return "skipped"
295
+ current = bridge.install_plugin(str(shipped.root), accept_host_permissions=True)
296
+ if current.name != SHIPPED_CODEX_DSH_PACKAGE:
297
+ raise StudioDshProviderRegistrationError(
298
+ "codex_dsh_package_mismatch",
299
+ "the official Codex install returned a different package",
300
+ )
301
+ result: Literal["installed", "already_enabled", "disabled"] = "installed"
302
+ else:
303
+ result = "already_enabled" if current.enabled else "disabled"
304
+ if current.version != SHIPPED_CODEX_PROVIDER_VERSION:
305
+ raise StudioDshProviderRegistrationError(
306
+ "codex_dsh_bundle_not_active",
307
+ "the official Codex DSH Bundle version is not supported",
308
+ )
309
+ if not current.enabled and result == "installed":
310
+ current = bridge.set_enabled(SHIPPED_CODEX_DSH_PACKAGE, enabled=True)
311
+ if not current.enabled:
312
+ raise StudioDshProviderRegistrationError(
313
+ "codex_dsh_enable_failed",
314
+ "the official Codex DSH Bundle did not become enabled",
315
+ )
316
+ self._verify_shipped_bundle_bytes(SHIPPED_CODEX_DSH_PACKAGE)
317
+ if marker_payload.get("codexProviderApplied") is not True:
318
+ self._write_default_marker(
319
+ marker,
320
+ {
321
+ "version": 1,
322
+ "codexProviderApplied": True,
323
+ "codexProviderVersion": SHIPPED_CODEX_PROVIDER_VERSION,
324
+ },
325
+ )
326
+ return result
327
+
328
+ @property
329
+ def _default_marker_path(self) -> Path:
330
+ return self._workspace / ".agentkit" / "official-dsh-defaults.json"
331
+
332
+ @staticmethod
333
+ def _read_default_marker(path: Path) -> dict[str, object]:
334
+ try:
335
+ payload = json.loads(path.read_text(encoding="utf-8")) if path.is_file() else {}
336
+ except (OSError, UnicodeError, json.JSONDecodeError) as error:
337
+ raise StudioDshProviderRegistrationError(
338
+ "dsh_default_marker_invalid",
339
+ "the Studio official DSH defaults marker is unreadable",
340
+ ) from error
341
+ if not isinstance(payload, dict):
342
+ raise StudioDshProviderRegistrationError(
343
+ "dsh_default_marker_invalid",
344
+ "the Studio official DSH defaults marker is invalid",
345
+ )
346
+ return payload
347
+
348
+ @staticmethod
349
+ def _write_default_marker(path: Path, payload: Mapping[str, object]) -> None:
350
+ path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
351
+ descriptor, temporary = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
352
+ try:
353
+ with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
354
+ json.dump(payload, handle, ensure_ascii=False, sort_keys=True)
355
+ handle.write("\n")
356
+ handle.flush()
357
+ os.fsync(handle.fileno())
358
+ os.chmod(temporary, 0o600)
359
+ os.replace(temporary, path)
360
+ finally:
361
+ try:
362
+ os.unlink(temporary)
363
+ except FileNotFoundError:
364
+ pass
365
+
366
+ @property
367
+ def host_pid(self) -> int | None:
368
+ return next(iter(self.host_pids), None)
369
+
370
+ @property
371
+ def host_pids(self) -> tuple[int, ...]:
372
+ return tuple(host.pid for host in self._hosts.values() if host.pid is not None)
373
+
374
+ async def start(self) -> StudioDshProviderRegistrations:
375
+ async with self._lock:
376
+ if self._registrations is not None:
377
+ return self._registrations
378
+ if self._inventory.state == "disposed":
379
+ raise StudioDshProviderRegistrationError(
380
+ "dsh_provider_manager_disposed",
381
+ "Studio DSH provider registration manager is disposed",
382
+ )
383
+ self._inventory = StudioDshProviderInventory(state="starting", profile=self._profile)
384
+ try:
385
+ snapshot = await asyncio.to_thread(self._discover_profile)
386
+ result = await self._register_snapshot(snapshot)
387
+ except BaseException as error:
388
+ await self._dispose_hosts()
389
+ code = str(getattr(error, "code", "dsh_provider_registration_failed"))
390
+ self._inventory = StudioDshProviderInventory(
391
+ state="failed", profile=self._profile, error_code=code
392
+ )
393
+ if isinstance(error, (asyncio.CancelledError, KeyboardInterrupt, SystemExit)):
394
+ raise
395
+ raise StudioDshProviderRegistrationError(
396
+ code,
397
+ "managed DSH Profile could not be projected safely",
398
+ ) from error
399
+ self._registrations = result
400
+ self._inventory = result.inventory
401
+ return result
402
+
403
+ async def refresh(self) -> StudioDshProviderRegistrations:
404
+ async with self._lock:
405
+ if self._inventory.state == "disposed":
406
+ raise StudioDshProviderRegistrationError(
407
+ "dsh_provider_manager_disposed",
408
+ "Studio DSH provider registration manager is disposed",
409
+ )
410
+ self._registrations = None
411
+ await self._dispose_hosts()
412
+ self._inventory = StudioDshProviderInventory(state="stopped", profile=self._profile)
413
+ return await self.start()
414
+
415
+ def mark_bound(self, provider_refs: Sequence[str]) -> None:
416
+ if self._registrations is None or self._inventory.state != "ready":
417
+ raise StudioDshProviderRegistrationError(
418
+ "dsh_provider_registration_not_ready",
419
+ "DSH provider registrations cannot be bound before preflight",
420
+ )
421
+ expected = tuple(self._registrations.manifests)
422
+ if tuple(provider_refs) != expected:
423
+ raise StudioDshProviderRegistrationError(
424
+ "dsh_provider_registration_mismatch",
425
+ "Studio did not bind the exact ready DSH provider registration set",
426
+ )
427
+ packages = tuple(
428
+ item.model_copy(update={"state": "bound"})
429
+ if item.state == "ready" and item.provider_ref in expected
430
+ else item
431
+ for item in self._inventory.packages
432
+ )
433
+ self._inventory = self._inventory.model_copy(
434
+ update={"state": "bound", "packages": packages}
435
+ )
436
+
437
+ async def aclose(self) -> None:
438
+ async with self._lock:
439
+ self._registrations = None
440
+ await self._dispose_hosts()
441
+ packages = tuple(
442
+ item.model_copy(update={"state": "disposed"})
443
+ if item.state in {"ready", "bound"}
444
+ else item
445
+ for item in self._inventory.packages
446
+ )
447
+ self._inventory = StudioDshProviderInventory(
448
+ state="disposed",
449
+ profile=self._profile,
450
+ profile_digest=self._inventory.profile_digest,
451
+ packages=packages,
452
+ )
453
+
454
+ def _discover_profile(self) -> _ProfileSnapshot:
455
+ command = self._dsh_command or DshToolchainManager().require_command()
456
+ with self._bridge_factory(
457
+ dsh_home=self._dsh_home,
458
+ profile=self._profile,
459
+ dsh_command=command,
460
+ cwd=self._workspace,
461
+ ) as bridge:
462
+ packages = bridge.list_plugins()
463
+ projection = bridge.project_profile()
464
+ enabled = {item.name for item in packages if item.enabled}
465
+ projected = set(projection.bundles)
466
+ if not enabled.issubset(projected) or projected - enabled - _DSH_PLATFORM_BUNDLES:
467
+ raise StudioDshProviderRegistrationError(
468
+ "dsh_provider_profile_inventory_mismatch",
469
+ "DSH installed inventory and projected Profile disagree",
470
+ )
471
+ return _ProfileSnapshot(projection=projection, packages=packages)
472
+
473
+ async def _register_snapshot(
474
+ self, snapshot: _ProfileSnapshot
475
+ ) -> StudioDshProviderRegistrations:
476
+ manifests: dict[str, PluginManifest] = {}
477
+ factories: dict[str, Any] = {}
478
+ statuses = [
479
+ StudioDshProviderStatus(
480
+ package_name=item.name,
481
+ package_version=item.version,
482
+ display_name=item.display_name,
483
+ state="enabled" if item.enabled else "installed",
484
+ )
485
+ for item in snapshot.packages
486
+ ]
487
+ provider_packages: dict[str, str] = {}
488
+ for index, package in enumerate(snapshot.packages):
489
+ if not package.enabled:
490
+ continue
491
+ host: DshAgentProviderHost | None = None
492
+ try:
493
+ registration, factory, host = await self._register_package(
494
+ package, snapshot.projection
495
+ )
496
+ if registration is None or factory is None or host is None:
497
+ continue
498
+ descriptor = registration.descriptor
499
+ provider_ref = f"plugin://{descriptor.provider_id}@{descriptor.provider_version}"
500
+ prior_package = provider_packages.get(provider_ref)
501
+ if prior_package is not None:
502
+ await host.dispose()
503
+ prior_host = self._hosts.pop(prior_package, None)
504
+ if prior_host is not None:
505
+ await prior_host.dispose()
506
+ manifests.pop(provider_ref, None)
507
+ factories.pop(provider_ref, None)
508
+ statuses[index] = statuses[index].model_copy(
509
+ update={
510
+ "state": "failed",
511
+ "provider_ref": provider_ref,
512
+ "error_code": "dsh_provider_registration_conflict",
513
+ }
514
+ )
515
+ for prior_index, status in enumerate(statuses):
516
+ if status.package_name == prior_package:
517
+ statuses[prior_index] = status.model_copy(
518
+ update={
519
+ "state": "failed",
520
+ "error_code": "dsh_provider_registration_conflict",
521
+ }
522
+ )
523
+ break
524
+ continue
525
+ provider_packages[provider_ref] = package.name
526
+ manifests[provider_ref] = registration.manifest
527
+ factories[provider_ref] = factory
528
+ self._hosts[package.name] = host
529
+ statuses[index] = statuses[index].model_copy(
530
+ update={
531
+ "state": "ready",
532
+ "provider_ref": provider_ref,
533
+ "display_name": descriptor.display_name,
534
+ }
535
+ )
536
+ except BaseException as error:
537
+ if host is not None:
538
+ try:
539
+ await host.dispose()
540
+ except BaseException:
541
+ pass
542
+ if isinstance(error, (asyncio.CancelledError, KeyboardInterrupt, SystemExit)):
543
+ raise
544
+ statuses[index] = statuses[index].model_copy(
545
+ update={
546
+ "state": "failed",
547
+ "error_code": str(
548
+ getattr(error, "code", "dsh_provider_registration_failed")
549
+ ),
550
+ }
551
+ )
552
+ inventory = StudioDshProviderInventory(
553
+ state="ready",
554
+ profile=self._profile,
555
+ profile_digest=snapshot.projection.config_digest,
556
+ providers=tuple(manifests),
557
+ packages=tuple(statuses),
558
+ )
559
+ return StudioDshProviderRegistrations(
560
+ manifests=manifests, factories=factories, inventory=inventory
561
+ )
562
+
563
+ async def _register_package(
564
+ self,
565
+ package: DshPluginInventory,
566
+ projection: DshProfileProjection,
567
+ ) -> tuple[
568
+ DshAgentProviderRegistration | None,
569
+ Any | None,
570
+ DshAgentProviderHost | None,
571
+ ]:
572
+ if package.name in _SHIPPED_PROVIDER_PACKAGES:
573
+ error_prefix = (
574
+ "codex_dsh" if package.name == SHIPPED_CODEX_DSH_PACKAGE else "harness_dsh"
575
+ )
576
+ expected_version = (
577
+ SHIPPED_CODEX_PROVIDER_VERSION
578
+ if package.name == SHIPPED_CODEX_DSH_PACKAGE
579
+ else SHIPPED_HARNESS_PROVIDER_VERSION
580
+ )
581
+ if package.version != expected_version:
582
+ raise StudioDshProviderRegistrationError(
583
+ f"{error_prefix}_bundle_not_active",
584
+ "the exact shipped DSH AgentProvider Bundle is not active",
585
+ )
586
+ self._verify_shipped_bundle_bytes(package.name)
587
+ command = (
588
+ shipped_codex_dsh_host_command()
589
+ if package.name == SHIPPED_CODEX_DSH_PACKAGE
590
+ else shipped_harness_dsh_host_command()
591
+ )
592
+ cwd = self._workspace
593
+ environment: dict[str, str] = {}
594
+ else:
595
+ entry = self._provider_host_entry(package.name)
596
+ if entry is None:
597
+ return None, None, None
598
+ command = (*self._resolve_node_command(), str(entry))
599
+ cwd = self._profile_root
600
+ environment = {"KSADK_DSH_CORDIS_MODULE": str(self._resolve_cordis_module())}
601
+ host = self._host_factory(
602
+ command,
603
+ projection=projection,
604
+ cwd=cwd,
605
+ environment=environment,
606
+ )
607
+ try:
608
+ registration = await host.registration()
609
+ if registration.descriptor.plugin_name != package.name:
610
+ raise PluginHostError(
611
+ "dsh_provider_package_mismatch",
612
+ "DSH provider descriptor does not match its installed package",
613
+ )
614
+ provider_inventory = await host.inventory()
615
+ if provider_inventory.state != "ready":
616
+ raise PluginHostError(
617
+ "dsh_provider_inventory_not_ready",
618
+ "DSH AgentProvider inventory is not ready",
619
+ )
620
+ if package.name == SHIPPED_CODEX_DSH_PACKAGE:
621
+ factory: Any = KsADKCodexDshBridgeFactory(host, registration, owns_host=False)
622
+ elif package.name == SHIPPED_HARNESS_DSH_PACKAGE:
623
+ factory: Any = KsADKHarnessDshBridgeFactory(host, registration, owns_host=False)
624
+ else:
625
+ factory = _FreshDshAgentProviderFactory(
626
+ command,
627
+ projection=projection,
628
+ cwd=cwd,
629
+ environment=environment,
630
+ registration=registration,
631
+ host_factory=self._host_factory,
632
+ )
633
+ return registration, factory, host
634
+ except BaseException:
635
+ await host.dispose()
636
+ raise
637
+
638
+ @property
639
+ def _profile_root(self) -> Path:
640
+ return self._dsh_home / "profiles" / self._profile
641
+
642
+ def _provider_host_entry(self, package_name: str) -> Path | None:
643
+ package_root = self._profile_root / "node_modules"
644
+ for segment in package_name.split("/"):
645
+ package_root /= segment
646
+ manifest_path = package_root / "package.json"
647
+ try:
648
+ if manifest_path.stat().st_size > _MAX_PACKAGE_JSON_BYTES:
649
+ raise ValueError("package.json is too large")
650
+ payload = json.loads(manifest_path.read_text(encoding="utf-8"))
651
+ except (OSError, UnicodeError, json.JSONDecodeError, ValueError) as error:
652
+ raise PluginHostError(
653
+ "dsh_provider_package_invalid",
654
+ "installed DSH provider package.json is invalid",
655
+ ) from error
656
+ exports = payload.get("exports") if isinstance(payload, dict) else None
657
+ if not isinstance(exports, dict) or "./provider-host" not in exports:
658
+ return None
659
+ relative = exports.get("./provider-host")
660
+ if not isinstance(relative, str) or not relative.startswith("./"):
661
+ raise PluginHostError(
662
+ "dsh_provider_host_export_invalid",
663
+ "DSH provider-host export must be one relative file",
664
+ )
665
+ try:
666
+ resolved_root = package_root.resolve(strict=True)
667
+ entry = (resolved_root / relative).resolve(strict=True)
668
+ except (OSError, RuntimeError) as error:
669
+ raise PluginHostError(
670
+ "dsh_provider_host_unavailable",
671
+ "DSH provider-host export is unavailable",
672
+ ) from error
673
+ if not entry.is_relative_to(resolved_root) or not entry.is_file():
674
+ raise PluginHostError(
675
+ "dsh_provider_host_export_invalid",
676
+ "DSH provider-host export escapes its installed package",
677
+ )
678
+ return entry
679
+
680
+ def _resolve_node_command(self) -> tuple[str, ...]:
681
+ if self._node_command is not None:
682
+ if not self._node_command:
683
+ raise PluginHostError("dsh_provider_node_unavailable", "Node command is empty")
684
+ return self._node_command
685
+ configured = os.environ.get("NODE", "").strip() or "node"
686
+ resolved = shutil.which(configured)
687
+ if resolved is None:
688
+ raise PluginHostError("dsh_provider_node_unavailable", "Node executable is unavailable")
689
+ return (str(Path(resolved).resolve()),)
690
+
691
+ def _resolve_cordis_module(self) -> Path:
692
+ if self._cordis_module is not None:
693
+ return self._cordis_module
694
+ return DshToolchainManager().resolve_module_entry("@deepseek-ai/cordis")
695
+
696
+ async def _dispose_hosts(self) -> None:
697
+ hosts = tuple(self._hosts.values())
698
+ self._hosts.clear()
699
+ for host in hosts:
700
+ try:
701
+ await host.dispose()
702
+ except BaseException:
703
+ pass
704
+
705
+ def _verify_shipped_bundle_bytes(self, package_name: str) -> None:
706
+ if package_name == SHIPPED_CODEX_DSH_PACKAGE:
707
+ shipped = shipped_codex_dsh_bundle().root
708
+ error_prefix = "codex_dsh"
709
+ elif package_name == SHIPPED_HARNESS_DSH_PACKAGE:
710
+ shipped = shipped_harness_dsh_bundle().root
711
+ error_prefix = "harness_dsh"
712
+ else: # pragma: no cover - callers use the closed official allowlist
713
+ raise ValueError(f"unsupported shipped DSH package {package_name!r}")
714
+ installed = self._profile_root / "node_modules"
715
+ for segment in package_name.split("/"):
716
+ installed /= segment
717
+ try:
718
+ matches = all(
719
+ (installed / name).read_bytes().rstrip() == (shipped / name).read_bytes().rstrip()
720
+ for name in _PROFILE_FILES
721
+ )
722
+ except OSError as error:
723
+ raise StudioDshProviderRegistrationError(
724
+ f"{error_prefix}_bundle_unreadable",
725
+ "the installed DSH AgentProvider Bundle cannot be verified",
726
+ ) from error
727
+ if not matches:
728
+ raise StudioDshProviderRegistrationError(
729
+ f"{error_prefix}_bundle_digest_mismatch",
730
+ "the installed DSH AgentProvider Bundle differs from the wheel-owned Bundle",
731
+ )
732
+
733
+
734
+ def merge_provider_registrations(
735
+ *registrations: StudioDshProviderRegistrations,
736
+ ) -> tuple[dict[str, PluginManifest], dict[str, Any]]:
737
+ """Merge registration sources without last-writer-wins ambiguity."""
738
+
739
+ manifests: dict[str, PluginManifest] = {}
740
+ factories: dict[str, Any] = {}
741
+ for item in registrations:
742
+ if item.manifests.keys() != item.factories.keys():
743
+ raise StudioDshProviderRegistrationError(
744
+ "dsh_provider_registration_partial",
745
+ "provider manifests and factories must have identical exact references",
746
+ )
747
+ for provider_ref, manifest in item.manifests.items():
748
+ if provider_ref in manifests and (
749
+ manifests[provider_ref] != manifest
750
+ or factories[provider_ref] is not item.factories[provider_ref]
751
+ ):
752
+ raise StudioDshProviderRegistrationError(
753
+ "dsh_provider_registration_conflict",
754
+ "multiple DSH registrations disagree for one exact provider reference",
755
+ )
756
+ manifests[provider_ref] = manifest
757
+ factories[provider_ref] = item.factories[provider_ref]
758
+ return manifests, factories
759
+
760
+
761
+ __all__ = [
762
+ "StudioDshProviderInventory",
763
+ "StudioDshProviderRegistrationError",
764
+ "StudioDshProviderRegistrationManager",
765
+ "StudioDshProviderRegistrations",
766
+ "StudioDshProviderStatus",
767
+ "merge_provider_registrations",
768
+ ]