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,951 @@
1
+ """DeepSeek Harness Profile -> AgentProvider sidecar vertical.
2
+
3
+ DSH owns its Bundle/Profile ABI and executes all Cordis plugin code. KsADK
4
+ only starts a trusted, fixed host command and consumes the host's typed
5
+ ``agent.provider/v1`` descriptor over a bounded JSONL RPC protocol. The
6
+ descriptor is projected into an internal :class:`PluginManifest` solely so the
7
+ existing PluginHost can keep ownership of admission, activation fencing, and
8
+ cleanup; DSH package authors do not need to publish a KsADK manifest.
9
+
10
+ The sidecar is language-neutral: any executable that implements the frozen
11
+ JSONL protocol can host the Cordis composition. Python is not part of the ABI.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import asyncio
17
+ import hashlib
18
+ import json
19
+ import os
20
+ import re
21
+ import time
22
+ from collections import deque
23
+ from collections.abc import Callable, Mapping, Sequence
24
+ from dataclasses import dataclass
25
+ from pathlib import Path
26
+ from typing import Any, Literal
27
+
28
+ from pydantic import ConfigDict, Field, field_validator
29
+
30
+ from ksadk.plugins.bridges.dsh import DshProfileProjection
31
+ from ksadk.plugins.bundle import ResolvedPluginBundle
32
+ from ksadk.plugins.contracts import PluginContractModel, PluginManifest
33
+ from ksadk.plugins.host import PluginExecutionContext, PluginHostError
34
+
35
+ DSH_AGENT_PROVIDER_HOST_PROTOCOL = "ksadk.dsh-agent-provider-host/v1"
36
+ DSH_AGENT_PROVIDER_HOST_METHODS = frozenset(
37
+ {
38
+ "handshake",
39
+ "describe",
40
+ "preflight",
41
+ "activate",
42
+ "inventory",
43
+ "execute",
44
+ "cancel",
45
+ "health",
46
+ "drain",
47
+ "dispose",
48
+ }
49
+ )
50
+ DSH_HOST_USER_PERMISSION = "process:host-user"
51
+
52
+ _MAX_LINE_BYTES = 1024 * 1024
53
+ _ID = re.compile(r"^[a-z0-9]+(?:[._-][a-z0-9]+)*$")
54
+ _SEMVER = re.compile(r"^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$")
55
+ _DIGEST = re.compile(r"^sha256:[0-9a-f]{64}$")
56
+ _PACKAGE = re.compile(r"^(?:@[A-Za-z0-9._-]+/)?[A-Za-z0-9._-]+$")
57
+ _ENV_NAME = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
58
+ _SECRET_ENV = re.compile(
59
+ r"(?:^|_)(?:API_?KEY|ACCESS_?KEY|AUTH|BEARER|CREDENTIAL|PASSWORD|"
60
+ r"PRIVATE_?KEY|SECRET|TOKEN)(?:_|$)",
61
+ re.IGNORECASE,
62
+ )
63
+ _DIAGNOSTIC_SECRET = re.compile(
64
+ r"(?ix)(?:\bsk-[a-z0-9_-]{12,}\b|\bBearer\s+[a-z0-9._~+/=-]{8,}|"
65
+ r"\b(?:api[_-]?key|access[_-]?token|auth[_-]?token|password|secret)"
66
+ r"\s*(?:=|:)\s*[^\s,;]+)"
67
+ )
68
+ _SAFE_INHERITED_ENV = frozenset(
69
+ {"LANG", "LC_ALL", "PATH", "SYSTEMROOT", "TEMP", "TMP", "TMPDIR", "TZ", "WINDIR"}
70
+ )
71
+ _BLOCKED_ENV = frozenset({"PYTHONHOME", "PYTHONPATH", "LD_PRELOAD"})
72
+
73
+
74
+ @dataclass(frozen=True)
75
+ class DshCircuitSnapshot:
76
+ """Observable host health state; it contains no command or credential data."""
77
+
78
+ state: Literal["closed", "open", "half-open"]
79
+ consecutive_failures: int
80
+ retry_after_seconds: float
81
+
82
+
83
+ class DshCircuitBreaker:
84
+ """Single-probe circuit breaker for DSH host transport and protocol faults.
85
+
86
+ It deliberately does not retry RPC calls. An ``execute`` frame can create
87
+ external side effects, so replay remains the responsibility of the
88
+ caller's idempotent run/session protocol.
89
+ """
90
+
91
+ def __init__(
92
+ self,
93
+ *,
94
+ failure_threshold: int = 3,
95
+ recovery_timeout: float = 5.0,
96
+ clock: Callable[[], float] = time.monotonic,
97
+ ) -> None:
98
+ if failure_threshold < 1:
99
+ raise ValueError("failure_threshold must be positive")
100
+ if recovery_timeout < 0:
101
+ raise ValueError("recovery_timeout cannot be negative")
102
+ self._failure_threshold = failure_threshold
103
+ self._recovery_timeout = recovery_timeout
104
+ self._clock = clock
105
+ self._failures = 0
106
+ self._opened_at: float | None = None
107
+ self._half_open_claimed = False
108
+
109
+ def acquire(self) -> bool:
110
+ if self._opened_at is None:
111
+ return True
112
+ if self._clock() - self._opened_at < self._recovery_timeout:
113
+ return False
114
+ if self._half_open_claimed:
115
+ return False
116
+ self._half_open_claimed = True
117
+ return True
118
+
119
+ def succeed(self) -> None:
120
+ self._failures = 0
121
+ self._opened_at = None
122
+ self._half_open_claimed = False
123
+
124
+ def fail(self) -> None:
125
+ self._failures += 1
126
+ self._half_open_claimed = False
127
+ if self._failures >= self._failure_threshold:
128
+ self._opened_at = self._clock()
129
+
130
+ def snapshot(self) -> DshCircuitSnapshot:
131
+ if self._opened_at is None:
132
+ return DshCircuitSnapshot("closed", self._failures, 0.0)
133
+ retry_after = max(0.0, self._recovery_timeout - (self._clock() - self._opened_at))
134
+ return DshCircuitSnapshot(
135
+ "half-open" if retry_after == 0 else "open",
136
+ self._failures,
137
+ retry_after,
138
+ )
139
+
140
+
141
+ class _DshProviderModel(PluginContractModel):
142
+ model_config = ConfigDict(
143
+ alias_generator=lambda value: (
144
+ value.split("_")[0] + "".join(part.capitalize() for part in value.split("_")[1:])
145
+ ),
146
+ populate_by_name=True,
147
+ extra="forbid",
148
+ frozen=True,
149
+ )
150
+
151
+
152
+ class DshAgentProviderDescriptor(_DshProviderModel):
153
+ """One AgentProvider contribution exposed by the composed DSH host."""
154
+
155
+ descriptor_format: Literal["dsh.agent-provider-descriptor/v1"] = (
156
+ "dsh.agent-provider-descriptor/v1"
157
+ )
158
+ ecosystem: Literal["dsh"] = "dsh"
159
+ provider_id: str = Field(min_length=3, max_length=128)
160
+ provider_version: str
161
+ display_name: str = Field(min_length=1, max_length=256)
162
+ plugin_name: str = Field(min_length=1, max_length=256)
163
+ profile: str = Field(min_length=1, max_length=64)
164
+ profile_digest: str
165
+ definition: Literal["agent.provider/v1"] = "agent.provider/v1"
166
+ slot: Literal["agent.execution"] = "agent.execution"
167
+ runtime_protocols: tuple[str, ...] = ()
168
+
169
+ @field_validator("provider_id")
170
+ @classmethod
171
+ def validate_provider_id(cls, value: str) -> str:
172
+ if not _ID.fullmatch(value):
173
+ raise ValueError("providerId must be a lowercase qualified id")
174
+ return value
175
+
176
+ @field_validator("provider_version")
177
+ @classmethod
178
+ def validate_provider_version(cls, value: str) -> str:
179
+ if not _SEMVER.fullmatch(value):
180
+ raise ValueError("providerVersion must use exact semantic versioning")
181
+ return value
182
+
183
+ @field_validator("plugin_name")
184
+ @classmethod
185
+ def validate_plugin_name(cls, value: str) -> str:
186
+ if not _PACKAGE.fullmatch(value):
187
+ raise ValueError("pluginName must be a valid DSH package name")
188
+ return value
189
+
190
+ @field_validator("profile_digest")
191
+ @classmethod
192
+ def validate_profile_digest(cls, value: str) -> str:
193
+ if not _DIGEST.fullmatch(value):
194
+ raise ValueError("profileDigest must be a lowercase sha256 digest")
195
+ return value
196
+
197
+ @field_validator("runtime_protocols")
198
+ @classmethod
199
+ def validate_runtime_protocols(cls, value: tuple[str, ...]) -> tuple[str, ...]:
200
+ if any(not item or len(item) > 128 for item in value):
201
+ raise ValueError("runtimeProtocols entries must be non-empty and bounded")
202
+ if len(value) != len(set(value)):
203
+ raise ValueError("runtimeProtocols entries must be unique")
204
+ return value
205
+
206
+ @property
207
+ def descriptor_digest(self) -> str:
208
+ payload = json.dumps(
209
+ self.model_dump(by_alias=True, exclude_none=True, mode="json"),
210
+ ensure_ascii=False,
211
+ sort_keys=True,
212
+ separators=(",", ":"),
213
+ ).encode("utf-8")
214
+ return f"sha256:{hashlib.sha256(payload).hexdigest()}"
215
+
216
+
217
+ class DshAgentProviderPreflight(_DshProviderModel):
218
+ ready: bool
219
+ descriptor_digest: str
220
+ profile_digest: str
221
+
222
+ @field_validator("descriptor_digest", "profile_digest")
223
+ @classmethod
224
+ def validate_digest(cls, value: str) -> str:
225
+ if not _DIGEST.fullmatch(value):
226
+ raise ValueError("preflight digests must be lowercase sha256 values")
227
+ return value
228
+
229
+
230
+ class DshAgentProviderInventory(_DshProviderModel):
231
+ provider_id: str = Field(min_length=3, max_length=128)
232
+ provider_version: str
233
+ profile: str = Field(min_length=1, max_length=64)
234
+ profile_digest: str
235
+ descriptor_digest: str
236
+ state: Literal["ready", "draining", "disposed", "failed"]
237
+ activation_count: int = Field(ge=0)
238
+
239
+ @field_validator("profile_digest", "descriptor_digest")
240
+ @classmethod
241
+ def validate_digest(cls, value: str) -> str:
242
+ if not _DIGEST.fullmatch(value):
243
+ raise ValueError("inventory digests must be lowercase sha256 values")
244
+ return value
245
+
246
+
247
+ @dataclass(frozen=True)
248
+ class DshAgentProviderRegistration:
249
+ """Selector-safe registration emitted only after successful preflight."""
250
+
251
+ descriptor: DshAgentProviderDescriptor
252
+ preflight: DshAgentProviderPreflight
253
+ manifest: PluginManifest
254
+
255
+
256
+ class DshAgentProviderHost:
257
+ """Supervise one fixed DSH provider host for an immutable Profile projection."""
258
+
259
+ def __init__(
260
+ self,
261
+ command: Sequence[str],
262
+ *,
263
+ projection: DshProfileProjection,
264
+ cwd: str | Path | None = None,
265
+ environment: Mapping[str, str] | None = None,
266
+ startup_timeout: float = 5.0,
267
+ request_timeout: float = 30.0,
268
+ shutdown_timeout: float = 2.0,
269
+ circuit_failure_threshold: int = 3,
270
+ circuit_recovery_timeout: float = 5.0,
271
+ ) -> None:
272
+ self._command = _validate_command(command)
273
+ self._projection = projection
274
+ self._cwd = Path(cwd).resolve() if cwd is not None else None
275
+ self._environment = _minimal_environment(environment or {})
276
+ self._startup_timeout = _positive_timeout(startup_timeout, "startup_timeout")
277
+ self._request_timeout = _positive_timeout(request_timeout, "request_timeout")
278
+ self._shutdown_timeout = _positive_timeout(shutdown_timeout, "shutdown_timeout")
279
+ self._circuit = DshCircuitBreaker(
280
+ failure_threshold=circuit_failure_threshold,
281
+ recovery_timeout=circuit_recovery_timeout,
282
+ )
283
+ self._process: asyncio.subprocess.Process | None = None
284
+ self._request_lock = asyncio.Lock()
285
+ self._lifecycle_lock = asyncio.Lock()
286
+ self._stderr_task: asyncio.Task[None] | None = None
287
+ self._stderr_tail: deque[str] = deque(maxlen=64)
288
+ self._next_request_id = 0
289
+ self._descriptor: DshAgentProviderDescriptor | None = None
290
+ self._preflight: DshAgentProviderPreflight | None = None
291
+ self._disposed = False
292
+
293
+ @property
294
+ def pid(self) -> int | None:
295
+ process = self._process
296
+ return process.pid if process is not None and process.returncode is None else None
297
+
298
+ @property
299
+ def stderr_tail(self) -> tuple[str, ...]:
300
+ return tuple(self._stderr_tail)
301
+
302
+ @property
303
+ def circuit_snapshot(self) -> DshCircuitSnapshot:
304
+ return self._circuit.snapshot()
305
+
306
+ async def start(self) -> None:
307
+ async with self._lifecycle_lock:
308
+ if self.pid is not None:
309
+ return
310
+ if self._disposed:
311
+ raise PluginHostError("dsh_provider_host_disposed", "DSH provider host is disposed")
312
+ self._require_circuit_probe()
313
+ try:
314
+ process = await asyncio.create_subprocess_exec(
315
+ *self._command,
316
+ cwd=str(self._cwd) if self._cwd is not None else None,
317
+ env=self._environment,
318
+ stdin=asyncio.subprocess.PIPE,
319
+ stdout=asyncio.subprocess.PIPE,
320
+ stderr=asyncio.subprocess.PIPE,
321
+ limit=_MAX_LINE_BYTES + 1,
322
+ start_new_session=os.name == "posix",
323
+ )
324
+ except (OSError, ValueError) as error:
325
+ self._circuit.fail()
326
+ raise PluginHostError(
327
+ "dsh_provider_host_start_failed",
328
+ f"cannot start fixed DSH provider host: {type(error).__name__}",
329
+ ) from error
330
+ self._process = process
331
+ self._stderr_task = asyncio.create_task(self._capture_stderr(process))
332
+ try:
333
+ handshake = await self._request(
334
+ "handshake",
335
+ {"profile": self._projection_payload()},
336
+ timeout=self._startup_timeout,
337
+ circuit_probe_acquired=True,
338
+ )
339
+ try:
340
+ self._validate_handshake(handshake)
341
+ except PluginHostError:
342
+ self._circuit.fail()
343
+ raise
344
+ except BaseException:
345
+ await self._terminate()
346
+ raise
347
+
348
+ async def describe(self) -> DshAgentProviderDescriptor:
349
+ await self.start()
350
+ if self._descriptor is not None:
351
+ return self._descriptor
352
+ payload = await self._request("describe", {"profile": self._projection_payload()})
353
+ try:
354
+ descriptor = DshAgentProviderDescriptor.model_validate(payload)
355
+ except Exception as error: # noqa: BLE001 - external protocol boundary
356
+ raise PluginHostError(
357
+ "dsh_provider_descriptor_invalid",
358
+ "DSH host returned an invalid provider descriptor",
359
+ ) from error
360
+ if descriptor.profile != self._projection.profile:
361
+ raise PluginHostError(
362
+ "dsh_provider_descriptor_mismatch", "DSH provider descriptor profile does not match"
363
+ )
364
+ if descriptor.profile_digest != self._projection.config_digest:
365
+ raise PluginHostError(
366
+ "dsh_provider_descriptor_mismatch",
367
+ "DSH provider descriptor is not fenced to the selected Profile digest",
368
+ )
369
+ if descriptor.plugin_name not in self._projection.bundles:
370
+ raise PluginHostError(
371
+ "dsh_provider_descriptor_mismatch",
372
+ "DSH provider descriptor does not belong to an active Profile bundle",
373
+ )
374
+ self._descriptor = descriptor
375
+ return descriptor
376
+
377
+ async def preflight(self) -> DshAgentProviderPreflight:
378
+ descriptor = await self.describe()
379
+ payload = await self._request(
380
+ "preflight",
381
+ {
382
+ "profileDigest": self._projection.config_digest,
383
+ "descriptorDigest": descriptor.descriptor_digest,
384
+ },
385
+ )
386
+ try:
387
+ result = DshAgentProviderPreflight.model_validate(payload)
388
+ except Exception as error: # noqa: BLE001 - external protocol boundary
389
+ raise PluginHostError(
390
+ "dsh_provider_preflight_invalid", "DSH host returned an invalid preflight result"
391
+ ) from error
392
+ if (
393
+ result.descriptor_digest != descriptor.descriptor_digest
394
+ or result.profile_digest != self._projection.config_digest
395
+ ):
396
+ raise PluginHostError(
397
+ "dsh_provider_preflight_mismatch", "DSH preflight result crossed a Profile fence"
398
+ )
399
+ if not result.ready:
400
+ raise PluginHostError(
401
+ "dsh_provider_preflight_failed", "DSH AgentProvider is not ready for activation"
402
+ )
403
+ self._preflight = result
404
+ return result
405
+
406
+ async def registration(self) -> DshAgentProviderRegistration:
407
+ """Return a selector-visible projection only after the provider is ready."""
408
+
409
+ descriptor = await self.describe()
410
+ preflight = await self.preflight()
411
+ return DshAgentProviderRegistration(
412
+ descriptor=descriptor,
413
+ preflight=preflight,
414
+ manifest=dsh_agent_provider_manifest(descriptor, preflight=preflight),
415
+ )
416
+
417
+ async def activate(
418
+ self,
419
+ bundle: ResolvedPluginBundle,
420
+ capabilities: PluginExecutionContext,
421
+ ) -> str:
422
+ descriptor = await self.describe()
423
+ await self.preflight()
424
+ payload = await self._request(
425
+ "activate",
426
+ {
427
+ "descriptorDigest": descriptor.descriptor_digest,
428
+ "bundle": _bundle_payload(bundle),
429
+ "capabilities": _capability_payload(capabilities),
430
+ },
431
+ )
432
+ if not isinstance(payload, dict):
433
+ raise PluginHostError(
434
+ "dsh_provider_activation_invalid", "DSH host activation result must be an object"
435
+ )
436
+ activation_id = str(payload.get("activationId") or "").strip()
437
+ if not activation_id or len(activation_id) > 256:
438
+ raise PluginHostError(
439
+ "dsh_provider_activation_invalid", "DSH host returned no valid activationId"
440
+ )
441
+ return activation_id
442
+
443
+ async def inventory(self) -> DshAgentProviderInventory:
444
+ descriptor = await self.describe()
445
+ payload = await self._request(
446
+ "inventory", {"descriptorDigest": descriptor.descriptor_digest}
447
+ )
448
+ try:
449
+ result = DshAgentProviderInventory.model_validate(payload)
450
+ except Exception as error: # noqa: BLE001 - external protocol boundary
451
+ raise PluginHostError(
452
+ "dsh_provider_inventory_invalid", "DSH host returned invalid provider inventory"
453
+ ) from error
454
+ if (
455
+ result.provider_id != descriptor.provider_id
456
+ or result.provider_version != descriptor.provider_version
457
+ or result.profile != descriptor.profile
458
+ or result.profile_digest != descriptor.profile_digest
459
+ or result.descriptor_digest != descriptor.descriptor_digest
460
+ ):
461
+ raise PluginHostError(
462
+ "dsh_provider_inventory_mismatch",
463
+ "DSH provider inventory crossed a descriptor fence",
464
+ )
465
+ return result
466
+
467
+ async def health(self, *, activation_id: str | None = None) -> bool:
468
+ params: dict[str, Any] = {"scope": "activation" if activation_id else "provider"}
469
+ if activation_id:
470
+ params["activationId"] = activation_id
471
+ payload = await self._request("health", params)
472
+ if not isinstance(payload, dict) or not isinstance(payload.get("healthy"), bool):
473
+ raise PluginHostError(
474
+ "dsh_provider_health_invalid", "DSH provider health must contain boolean healthy"
475
+ )
476
+ return bool(payload["healthy"])
477
+
478
+ async def execute(self, activation_id: str, request: Any) -> Any:
479
+ return await self._request(
480
+ "execute", {"activationId": activation_id, "request": _json_value(request)}
481
+ )
482
+
483
+ async def cancel_activation(self, activation_id: str) -> None:
484
+ await self._request("cancel", {"activationId": activation_id})
485
+
486
+ async def drain(self, *, activation_id: str | None = None) -> None:
487
+ params: dict[str, Any] = {"scope": "activation" if activation_id else "provider"}
488
+ if activation_id:
489
+ params["activationId"] = activation_id
490
+ await self._request("drain", params)
491
+
492
+ async def dispose_activation(self, activation_id: str) -> None:
493
+ await self._request("dispose", {"scope": "activation", "activationId": activation_id})
494
+
495
+ async def dispose(self) -> None:
496
+ async with self._lifecycle_lock:
497
+ if self._disposed:
498
+ return
499
+ self._disposed = True
500
+ failure: Exception | None = None
501
+ if self.pid is not None:
502
+ try:
503
+ await self._request("dispose", {"scope": "host"})
504
+ except Exception as error: # cleanup must still terminate the sidecar
505
+ failure = error
506
+ await self._terminate()
507
+ if failure is not None:
508
+ raise failure
509
+
510
+ async def _request(
511
+ self,
512
+ method: str,
513
+ params: Mapping[str, Any],
514
+ *,
515
+ timeout: float | None = None,
516
+ circuit_probe_acquired: bool = False,
517
+ ) -> Any:
518
+ if method not in DSH_AGENT_PROVIDER_HOST_METHODS:
519
+ raise PluginHostError(
520
+ "dsh_provider_method_denied", f"DSH provider host method {method!r} is denied"
521
+ )
522
+ if not circuit_probe_acquired:
523
+ self._require_circuit_probe()
524
+ self._next_request_id += 1
525
+ request_id = f"dsh-{self._next_request_id}"
526
+ try:
527
+ encoded = json.dumps(
528
+ {"id": request_id, "method": method, "params": dict(params)},
529
+ ensure_ascii=False,
530
+ allow_nan=False,
531
+ separators=(",", ":"),
532
+ ).encode("utf-8")
533
+ except (TypeError, ValueError) as error:
534
+ raise PluginHostError(
535
+ "dsh_provider_request_invalid", "DSH provider request is not strict JSON"
536
+ ) from error
537
+ if len(encoded) > _MAX_LINE_BYTES:
538
+ raise PluginHostError(
539
+ "dsh_provider_request_too_large", "DSH provider request exceeds the protocol limit"
540
+ )
541
+ async with self._request_lock:
542
+ process = self._process
543
+ if (
544
+ process is None
545
+ or process.returncode is not None
546
+ or process.stdin is None
547
+ or process.stdout is None
548
+ ):
549
+ self._circuit.fail()
550
+ raise PluginHostError(
551
+ "dsh_provider_host_unavailable", "DSH provider host is not running"
552
+ )
553
+ try:
554
+ process.stdin.write(encoded + b"\n")
555
+ await process.stdin.drain()
556
+ line = await asyncio.wait_for(
557
+ process.stdout.readline(),
558
+ timeout=timeout if timeout is not None else self._request_timeout,
559
+ )
560
+ except asyncio.TimeoutError as error:
561
+ await self._terminate()
562
+ self._circuit.fail()
563
+ raise PluginHostError(
564
+ "dsh_provider_request_timeout", f"DSH provider host timed out during {method}"
565
+ ) from error
566
+ except (BrokenPipeError, ConnectionError, OSError, ValueError) as error:
567
+ await self._terminate()
568
+ self._circuit.fail()
569
+ raise PluginHostError(
570
+ "dsh_provider_transport_failed", f"DSH provider host failed during {method}"
571
+ ) from error
572
+ if not line or len(line) > _MAX_LINE_BYTES:
573
+ await self._terminate()
574
+ self._circuit.fail()
575
+ raise PluginHostError(
576
+ "dsh_provider_protocol_invalid", "DSH provider host emitted no bounded response"
577
+ )
578
+ try:
579
+ response = json.loads(line)
580
+ except (UnicodeDecodeError, ValueError) as error:
581
+ await self._terminate()
582
+ self._circuit.fail()
583
+ raise PluginHostError(
584
+ "dsh_provider_protocol_invalid", "DSH provider host emitted invalid JSONL"
585
+ ) from error
586
+ if not isinstance(response, dict) or response.get("id") != request_id:
587
+ await self._terminate()
588
+ self._circuit.fail()
589
+ raise PluginHostError(
590
+ "dsh_provider_response_mismatch", "DSH provider response id does not match"
591
+ )
592
+ if response.get("error") is not None:
593
+ raise PluginHostError(
594
+ "dsh_provider_remote_error", "DSH provider host rejected the request"
595
+ )
596
+ if "result" not in response:
597
+ self._circuit.fail()
598
+ raise PluginHostError(
599
+ "dsh_provider_protocol_invalid", "DSH provider response has no result"
600
+ )
601
+ self._circuit.succeed()
602
+ return response["result"]
603
+
604
+ def _require_circuit_probe(self) -> None:
605
+ if self._circuit.acquire():
606
+ return
607
+ snapshot = self._circuit.snapshot()
608
+ raise PluginHostError(
609
+ "dsh_provider_circuit_open",
610
+ "DSH provider host circuit is open; wait "
611
+ f"{snapshot.retry_after_seconds:.1f}s before another probe",
612
+ )
613
+
614
+ def _validate_handshake(self, payload: Any) -> None:
615
+ if not isinstance(payload, dict):
616
+ raise PluginHostError(
617
+ "dsh_provider_handshake_invalid", "DSH provider handshake must be an object"
618
+ )
619
+ methods = payload.get("methods")
620
+ host_version = payload.get("hostVersion")
621
+ if (
622
+ payload.get("protocolVersion") != DSH_AGENT_PROVIDER_HOST_PROTOCOL
623
+ or not isinstance(methods, list)
624
+ or set(methods) != DSH_AGENT_PROVIDER_HOST_METHODS
625
+ or len(methods) != len(DSH_AGENT_PROVIDER_HOST_METHODS)
626
+ or not isinstance(host_version, str)
627
+ or not _SEMVER.fullmatch(host_version)
628
+ ):
629
+ raise PluginHostError(
630
+ "dsh_provider_handshake_invalid",
631
+ "DSH provider host protocol, methods, or version is incompatible",
632
+ )
633
+
634
+ def _projection_payload(self) -> dict[str, Any]:
635
+ return self._projection.model_dump(by_alias=True, mode="json")
636
+
637
+ async def _capture_stderr(self, process: asyncio.subprocess.Process) -> None:
638
+ if process.stderr is None:
639
+ return
640
+ while True:
641
+ line = await process.stderr.readline()
642
+ if not line:
643
+ return
644
+ diagnostic = line.decode("utf-8", errors="replace").rstrip()[:4096]
645
+ self._stderr_tail.append(_DIAGNOSTIC_SECRET.sub("[REDACTED]", diagnostic))
646
+
647
+ async def _terminate(self) -> None:
648
+ process = self._process
649
+ self._process = None
650
+ if process is not None and process.returncode is None:
651
+ process.terminate()
652
+ try:
653
+ await asyncio.wait_for(process.wait(), timeout=self._shutdown_timeout)
654
+ except asyncio.TimeoutError:
655
+ process.kill()
656
+ await process.wait()
657
+ task = self._stderr_task
658
+ self._stderr_task = None
659
+ if task is not None and task is not asyncio.current_task():
660
+ try:
661
+ await asyncio.wait_for(task, timeout=self._shutdown_timeout)
662
+ except asyncio.TimeoutError:
663
+ task.cancel()
664
+ await asyncio.gather(task, return_exceptions=True)
665
+
666
+
667
+ class DshAgentProviderRuntime:
668
+ """Existing PluginHost AgentProvider SPI backed by one DSH host."""
669
+
670
+ def __init__(self, host: DshAgentProviderHost) -> None:
671
+ self._host = host
672
+ self._ready = False
673
+ self._disposed = False
674
+
675
+ async def start(self) -> None:
676
+ if self._disposed:
677
+ raise PluginHostError("dsh_provider_disposed", "DSH AgentProvider is disposed")
678
+ await self._host.preflight()
679
+ self._ready = True
680
+
681
+ async def health(self) -> bool:
682
+ return (
683
+ self._ready
684
+ and not self._disposed
685
+ and self._host.pid is not None
686
+ and await self._host.health()
687
+ )
688
+
689
+ async def prepare(
690
+ self,
691
+ bundle: ResolvedPluginBundle,
692
+ *,
693
+ capabilities: PluginExecutionContext,
694
+ ) -> "DshPreparedAgent":
695
+ if not await self.health():
696
+ raise PluginHostError("dsh_provider_unavailable", "DSH AgentProvider is not healthy")
697
+ activation_id = await self._host.activate(bundle, capabilities)
698
+ return DshPreparedAgent(self._host, activation_id)
699
+
700
+ async def inventory(self) -> DshAgentProviderInventory:
701
+ return await self._host.inventory()
702
+
703
+ async def drain(self) -> None:
704
+ if self._ready and not self._disposed and self._host.pid is not None:
705
+ await self._host.drain()
706
+ self._ready = False
707
+
708
+ async def dispose(self) -> None:
709
+ if self._disposed:
710
+ return
711
+ self._disposed = True
712
+ self._ready = False
713
+ await self._host.dispose()
714
+
715
+
716
+ class DshPreparedAgent:
717
+ def __init__(self, host: DshAgentProviderHost, activation_id: str) -> None:
718
+ self._host = host
719
+ self._activation_id = activation_id
720
+ self._started = False
721
+ self._drained = False
722
+ self._disposed = False
723
+
724
+ async def start(self) -> None:
725
+ if self._disposed:
726
+ raise PluginHostError("dsh_activation_disposed", "DSH activation is disposed")
727
+ self._started = True
728
+
729
+ async def health(self) -> bool:
730
+ return (
731
+ self._started
732
+ and not self._drained
733
+ and not self._disposed
734
+ and await self._host.health(activation_id=self._activation_id)
735
+ )
736
+
737
+ async def execute(self, request: Any) -> Any:
738
+ if not await self.health():
739
+ raise PluginHostError("dsh_activation_unavailable", "DSH activation is not healthy")
740
+ return await self._host.execute(self._activation_id, request)
741
+
742
+ async def cancel(self) -> None:
743
+ """Request bounded cancellation without losing the disposable handle."""
744
+
745
+ if self._disposed:
746
+ return
747
+ await self._host.cancel_activation(self._activation_id)
748
+
749
+ async def drain(self) -> None:
750
+ if self._disposed or self._drained:
751
+ return
752
+ await self._host.drain(activation_id=self._activation_id)
753
+ self._drained = True
754
+
755
+ async def dispose(self) -> None:
756
+ if self._disposed:
757
+ return
758
+ self._disposed = True
759
+ if self._host.pid is not None:
760
+ await self._host.dispose_activation(self._activation_id)
761
+
762
+
763
+ class DshAgentProviderFactory:
764
+ """Project one discovered DSH provider into the internal PluginFactory seam."""
765
+
766
+ def __init__(
767
+ self,
768
+ host: DshAgentProviderHost,
769
+ registration: DshAgentProviderRegistration,
770
+ ) -> None:
771
+ self._host = host
772
+ self._registration = registration
773
+ self.runtime: DshAgentProviderRuntime | None = None
774
+
775
+ async def stage(
776
+ self,
777
+ manifest: PluginManifest,
778
+ *,
779
+ profile: Any,
780
+ services: Mapping[str, Any],
781
+ ) -> DshAgentProviderRuntime:
782
+ del profile, services
783
+ expected = dsh_agent_provider_manifest(
784
+ self._registration.descriptor,
785
+ preflight=self._registration.preflight,
786
+ )
787
+ if manifest != expected:
788
+ raise PluginHostError(
789
+ "dsh_provider_manifest_mismatch",
790
+ "internal DSH provider projection does not match the discovered descriptor",
791
+ )
792
+ self.runtime = DshAgentProviderRuntime(self._host)
793
+ return self.runtime
794
+
795
+
796
+ def dsh_agent_provider_manifest(
797
+ descriptor: DshAgentProviderDescriptor,
798
+ *,
799
+ preflight: DshAgentProviderPreflight,
800
+ ) -> PluginManifest:
801
+ """Create an internal admission projection; this is not a DSH package format."""
802
+
803
+ if (
804
+ not preflight.ready
805
+ or preflight.descriptor_digest != descriptor.descriptor_digest
806
+ or preflight.profile_digest != descriptor.profile_digest
807
+ ):
808
+ raise PluginHostError(
809
+ "dsh_provider_not_ready",
810
+ "DSH AgentProvider cannot enter the selector before matching preflight",
811
+ )
812
+
813
+ return PluginManifest.model_validate(
814
+ {
815
+ "metadata": {
816
+ "id": descriptor.provider_id,
817
+ "version": descriptor.provider_version,
818
+ },
819
+ "spec": {
820
+ "domain": "runtime-native",
821
+ "runtime": "process",
822
+ "entrypoint": "deepseek-harness:profile-agent-provider",
823
+ "provides": [
824
+ {
825
+ "definition": descriptor.definition,
826
+ "slot": descriptor.slot,
827
+ "mode": "unique",
828
+ }
829
+ ],
830
+ "permissions": [DSH_HOST_USER_PERMISSION],
831
+ "isolation": "sidecar",
832
+ "compatibility": {
833
+ "kernelApi": ">=1,<2",
834
+ "runtimeProtocols": list(descriptor.runtime_protocols),
835
+ },
836
+ "healthContract": "plugin.health/v1",
837
+ "provenance": {
838
+ "source": "runtime-native",
839
+ "digest": descriptor.descriptor_digest,
840
+ },
841
+ },
842
+ }
843
+ )
844
+
845
+
846
+ def _validate_command(command: Sequence[str]) -> tuple[str, ...]:
847
+ if isinstance(command, (str, bytes)):
848
+ raise PluginHostError(
849
+ "dsh_provider_command_invalid", "DSH host command must be an argv sequence"
850
+ )
851
+ normalized = tuple(str(item) for item in command)
852
+ if not normalized or any(not item or "\x00" in item for item in normalized):
853
+ raise PluginHostError(
854
+ "dsh_provider_command_invalid", "DSH host command contains an invalid argv item"
855
+ )
856
+ return normalized
857
+
858
+
859
+ def _minimal_environment(explicit: Mapping[str, str]) -> dict[str, str]:
860
+ environment = {
861
+ key: value
862
+ for key, value in os.environ.items()
863
+ if key in _SAFE_INHERITED_ENV and not _SECRET_ENV.search(key)
864
+ }
865
+ for key, value in explicit.items():
866
+ if (
867
+ not _ENV_NAME.fullmatch(key)
868
+ or key in _BLOCKED_ENV
869
+ or key.startswith("DYLD_")
870
+ or _SECRET_ENV.search(key)
871
+ ):
872
+ raise PluginHostError(
873
+ "dsh_provider_environment_denied",
874
+ f"DSH host environment key {key!r} is not allowed",
875
+ )
876
+ environment[key] = str(value)
877
+ return environment
878
+
879
+
880
+ def _positive_timeout(value: float, field: str) -> float:
881
+ normalized = float(value)
882
+ if normalized <= 0:
883
+ raise PluginHostError("dsh_provider_timeout_invalid", f"{field} must be positive")
884
+ return normalized
885
+
886
+
887
+ def _bundle_payload(bundle: ResolvedPluginBundle) -> dict[str, Any]:
888
+ return {
889
+ "root": str(bundle.root),
890
+ "manifest": bundle.manifest.model_dump(by_alias=True, exclude_none=True, mode="json"),
891
+ "resolvedAgentSpec": _json_value(bundle.resolved_agent_spec),
892
+ "composition": {
893
+ "profileDigest": bundle.composition.profile_digest,
894
+ "pluginLockDigest": bundle.composition.plugin_lock_digest,
895
+ },
896
+ }
897
+
898
+
899
+ def _capability_payload(context: PluginExecutionContext) -> dict[str, Any]:
900
+ return {
901
+ "profileDigest": context.profile_digest,
902
+ "pluginLockDigest": context.plugin_lock_digest,
903
+ "bindings": [
904
+ {
905
+ "pluginId": binding.plugin_id,
906
+ "pluginVersion": binding.plugin_version,
907
+ "definition": binding.definition,
908
+ "slot": binding.slot,
909
+ }
910
+ for binding in context.bindings
911
+ ],
912
+ }
913
+
914
+
915
+ def _json_value(value: Any) -> Any:
916
+ if isinstance(value, Mapping):
917
+ normalized: Any = {str(key): _json_value(child) for key, child in value.items()}
918
+ elif isinstance(value, (list, tuple)):
919
+ normalized = [_json_value(child) for child in value]
920
+ elif value is None or isinstance(value, (str, int, float, bool)):
921
+ normalized = value
922
+ else:
923
+ raise PluginHostError(
924
+ "dsh_provider_request_invalid",
925
+ f"DSH provider value {type(value).__name__} is not JSON compatible",
926
+ )
927
+ try:
928
+ json.dumps(normalized, ensure_ascii=False, allow_nan=False)
929
+ except (TypeError, ValueError) as error:
930
+ raise PluginHostError(
931
+ "dsh_provider_request_invalid", "DSH provider value is not strict JSON"
932
+ ) from error
933
+ return normalized
934
+
935
+
936
+ __all__ = [
937
+ "DSH_AGENT_PROVIDER_HOST_METHODS",
938
+ "DSH_AGENT_PROVIDER_HOST_PROTOCOL",
939
+ "DSH_HOST_USER_PERMISSION",
940
+ "DshAgentProviderDescriptor",
941
+ "DshAgentProviderFactory",
942
+ "DshAgentProviderHost",
943
+ "DshAgentProviderInventory",
944
+ "DshAgentProviderPreflight",
945
+ "DshAgentProviderRegistration",
946
+ "DshAgentProviderRuntime",
947
+ "DshCircuitBreaker",
948
+ "DshCircuitSnapshot",
949
+ "DshPreparedAgent",
950
+ "dsh_agent_provider_manifest",
951
+ ]