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.
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/METADATA +18 -25
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/RECORD +224 -169
- ksadk/_build_provenance.json +1 -0
- ksadk/agui/_agent_helpers.py +4 -0
- ksadk/api/client.py +3 -0
- ksadk/builders/managed_runtime_builder.py +3 -0
- ksadk/cli/__init__.py +6 -0
- ksadk/cli/cmd_plugin.py +657 -0
- ksadk/codex/runtime.py +50 -8
- ksadk/configs/env_registry.py +31 -3
- ksadk/configs/settings.py +9 -0
- ksadk/context_engine/contributors.py +73 -8
- ksadk/conversations/__init__.py +19 -0
- ksadk/conversations/context.py +104 -24
- ksadk/conversations/contracts.py +266 -0
- ksadk/conversations/message_projection.py +19 -1
- ksadk/conversations/projector.py +616 -0
- ksadk/conversations/reducer.py +98 -0
- ksadk/events/_v1_compat/projection.py +2 -0
- ksadk/events/adapters/codex.py +190 -0
- ksadk/harness/__init__.py +45 -35
- ksadk/harness/runtime.py +60 -1
- ksadk/kernel/contract_fingerprints.py +5 -4
- ksadk/kernel/contracts.py +8 -0
- ksadk/kernel/control.py +1 -0
- ksadk/kernel/ingress.py +21 -0
- ksadk/kernel/memory_store.py +1 -0
- ksadk/kernel/postgres_store.py +1 -0
- ksadk/kernel/sqlite_store.py +1 -0
- ksadk/kernel/worker.py +11 -0
- ksadk/memory/providers/local_sqlite.py +7 -0
- ksadk/plugins/__init__.py +167 -0
- ksadk/plugins/bridges/__init__.py +45 -0
- ksadk/plugins/bridges/codex.py +552 -0
- ksadk/plugins/bridges/dsh.py +1116 -0
- ksadk/plugins/builtins.py +747 -0
- ksadk/plugins/bundle.py +246 -0
- ksadk/plugins/bundle_security.py +167 -0
- ksadk/plugins/composition.py +529 -0
- ksadk/plugins/context_contributor.py +437 -0
- ksadk/plugins/contracts.py +451 -0
- ksadk/plugins/dsh_toolchain.py +935 -0
- ksadk/plugins/ecosystem_bridge.py +1105 -0
- ksadk/plugins/ecosystem_probe.py +191 -0
- ksadk/plugins/host.py +724 -0
- ksadk/plugins/providers/__init__.py +85 -0
- ksadk/plugins/providers/bundles/ksadk-codex/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/ksadk-codex/index.mjs +18 -0
- ksadk/plugins/providers/bundles/ksadk-codex/package.json +19 -0
- ksadk/plugins/providers/bundles/ksadk-harness/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/ksadk-harness/index.mjs +19 -0
- ksadk/plugins/providers/bundles/ksadk-harness/package.json +19 -0
- ksadk/plugins/providers/codex.py +722 -0
- ksadk/plugins/providers/codex_dsh.py +83 -0
- ksadk/plugins/providers/dsh.py +951 -0
- ksadk/plugins/providers/dsh_descriptor_host.py +226 -0
- ksadk/plugins/providers/harness.py +627 -0
- ksadk/plugins/providers/harness_dsh.py +91 -0
- ksadk/plugins/providers/legacy.py +215 -0
- ksadk/plugins/providers/legacy_catalog.py +100 -0
- ksadk/plugins/providers/shipped_dsh.py +370 -0
- ksadk/plugins/resolver.py +331 -0
- ksadk/plugins/subagent_providers/__init__.py +12 -0
- ksadk/plugins/subagent_providers/codex.py +443 -0
- ksadk/plugins/subagents.py +333 -0
- ksadk/runtime/_runner_adapter/stream_mapping.py +51 -42
- ksadk/runtime/adapter.py +1 -0
- ksadk/runtime/factory.py +76 -2
- ksadk/runtime/framework_adapters.py +18 -0
- ksadk/runtime/runner_adapter.py +5 -0
- ksadk/sandbox/__init__.py +2 -1
- ksadk/sandbox/factory.py +72 -0
- ksadk/scheduler/__init__.py +18 -0
- ksadk/scheduler/calendar.py +102 -0
- ksadk/scheduler/contracts.py +254 -0
- ksadk/scheduler/dispatcher.py +129 -0
- ksadk/scheduler/engine.py +416 -0
- ksadk/scheduler/sqlite_store.py +563 -0
- ksadk/server/static/assets/{ArtifactsPanel-DSU1sWD_.js → ArtifactsPanel-10GPXIJ6.js} +1 -1
- ksadk/server/static/assets/{CodeBlock-DOH6MVcn.js → CodeBlock-BXk1l-G1.js} +1 -1
- ksadk/server/static/assets/{MathMessageMarkdown-BL8my1R2.js → MathMessageMarkdown-D9uB55Oh.js} +1 -1
- ksadk/server/static/assets/{MermaidBlock-Dz4IP-Tx.js → MermaidBlock--OEYoXIJ.js} +6 -6
- ksadk/server/static/assets/{NativeTerminalPanel-DUrK0JpZ.js → NativeTerminalPanel-DrEe1syq.js} +1 -1
- ksadk/server/static/assets/{abnfDiagram-VCTEODGH-g20pFzNV.js → abnfDiagram-VCTEODGH-eeTFJLvo.js} +1 -1
- ksadk/server/static/assets/{arc-C4FzinUA.js → arc-S3ZNbIAd.js} +1 -1
- ksadk/server/static/assets/architecture-7GRP2DOG-Dk9nRQ6-.js +1 -0
- ksadk/server/static/assets/{architectureDiagram-5GKGNRK7-DpIqL5h4.js → architectureDiagram-5GKGNRK7-CRjgEJDX.js} +1 -1
- ksadk/server/static/assets/{blockDiagram-NRAW4CY4-BjkljTNz.js → blockDiagram-NRAW4CY4-C7bsBoNH.js} +1 -1
- ksadk/server/static/assets/{c4Diagram-UCG6FXSJ-Dk_ieq2X.js → c4Diagram-UCG6FXSJ-BX3c33Ob.js} +1 -1
- ksadk/server/static/assets/channel-s354Yo6o.js +1 -0
- ksadk/server/static/assets/chunk-2Q5K7J3B-CDpPpKR5.js +1 -0
- ksadk/server/static/assets/{chunk-5VM5RSS4-BVHAchFb.js → chunk-5VM5RSS4-bZ7ws-8m.js} +1 -1
- ksadk/server/static/assets/{chunk-F27PBJKO-C-ipQzuS.js → chunk-F27PBJKO-D2r0pvhY.js} +1 -1
- ksadk/server/static/assets/{chunk-G27WJ6UU-C2EyJbIK.js → chunk-G27WJ6UU-CZwF3oUP.js} +1 -1
- ksadk/server/static/assets/{chunk-JWPE2WC7-vYvVJb_M.js → chunk-JWPE2WC7-DigFYCML.js} +1 -1
- ksadk/server/static/assets/{chunk-LCL6LL3I-BcI6ysPT.js → chunk-LCL6LL3I-CnorGV5q.js} +1 -1
- ksadk/server/static/assets/{chunk-POPQ4Y6H-C030x_Z1.js → chunk-POPQ4Y6H-CexntQA-.js} +1 -1
- ksadk/server/static/assets/{chunk-RHFEMEQ7-yyGpsz4n.js → chunk-RHFEMEQ7-CHTC7UxA.js} +1 -1
- ksadk/server/static/assets/{chunk-SVP7TREG-BLTlmMU7.js → chunk-SVP7TREG-BC1EWt_-.js} +1 -1
- ksadk/server/static/assets/{chunk-XXDRQBXY-C_32ArgP.js → chunk-XXDRQBXY-BTG24xN0.js} +1 -1
- ksadk/server/static/assets/classDiagram-DTDB5LWJ-Dd0nucl9.js +1 -0
- ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-Dd0nucl9.js +1 -0
- ksadk/server/static/assets/{cose-bilkent-JH36ORCC-9YQYwdl0.js → cose-bilkent-JH36ORCC-CkonRdAS.js} +1 -1
- ksadk/server/static/assets/cynefin-OW5HDTMX-BBlT4uMf.js +1 -0
- ksadk/server/static/assets/{cynefinDiagram-5FMLGOSQ-ErLF5N13.js → cynefinDiagram-5FMLGOSQ-DEOCBe15.js} +1 -1
- ksadk/server/static/assets/{dagre-3AP2YEHR-DFiQF-6f.js → dagre-3AP2YEHR-DBThPuLa.js} +1 -1
- ksadk/server/static/assets/{dagre-BuhGnRI1.js → dagre-ClTPrTht.js} +1 -1
- ksadk/server/static/assets/{diagram-S7CK7UJ4-Bt1v8-GC.js → diagram-S7CK7UJ4-DsZHrnpJ.js} +1 -1
- ksadk/server/static/assets/{diagram-UQ7AKVKN-DSCxJdBK.js → diagram-UQ7AKVKN-BJR4nhVr.js} +1 -1
- ksadk/server/static/assets/{diagram-VSXAHHWV-DHfYwp_9.js → diagram-VSXAHHWV-xzcERX0a.js} +1 -1
- ksadk/server/static/assets/{diagram-VX7I27RA-DdzD-4Le.js → diagram-VX7I27RA-tn2xM1xu.js} +1 -1
- ksadk/server/static/assets/{diagram-Z3DM3KII-Du-nAJ9F.js → diagram-Z3DM3KII-BZPGBkw5.js} +1 -1
- ksadk/server/static/assets/{dist-ClxGviKw.js → dist-4zN1_tq5.js} +1 -1
- ksadk/server/static/assets/{dist-BE7bu-DL.js → dist-8VWI-2bZ.js} +1 -1
- ksadk/server/static/assets/{dist-BX8z72IC.js → dist-B-46q3Hk.js} +1 -1
- ksadk/server/static/assets/{dist-BjU6y-A2.js → dist-B0gdk4rl.js} +1 -1
- ksadk/server/static/assets/{dist-C_wsv-Qd.js → dist-B1oWRmrH.js} +1 -1
- ksadk/server/static/assets/{dist-ngtN0DJB.js → dist-BI1qQH4n.js} +1 -1
- ksadk/server/static/assets/{dist-dydu68Fl.js → dist-BJLUwH6W.js} +1 -1
- ksadk/server/static/assets/{dist-Dq9gLD7L.js → dist-Bbwqa8TG.js} +1 -1
- ksadk/server/static/assets/{dist-CttYUqzS.js → dist-Btyo4vX4.js} +1 -1
- ksadk/server/static/assets/{dist-CXYYBw3_.js → dist-C71CsujO.js} +1 -1
- ksadk/server/static/assets/{dist-dDdADKFA.js → dist-CXpH_Rdd.js} +1 -1
- ksadk/server/static/assets/{dist-CSh_DE14.js → dist-Cq4OL6nO.js} +1 -1
- ksadk/server/static/assets/{dist-8hqcx0sU.js → dist-D1V3f1B4.js} +1 -1
- ksadk/server/static/assets/{dist-BVDffZ0U.js → dist-DJmCPRVB.js} +1 -1
- ksadk/server/static/assets/{dist-DnfXs8Vn.js → dist-DQAB0qn_.js} +1 -1
- ksadk/server/static/assets/{dist-DNp6rLRA.js → dist-DUwKa_l6.js} +1 -1
- ksadk/server/static/assets/{dist-B7seoj3d.js → dist-DdRBIceA.js} +1 -1
- ksadk/server/static/assets/{dist-BytlxIDT.js → dist-DfGFrb0v.js} +1 -1
- ksadk/server/static/assets/{dist-Bf2M8m3N.js → dist-DjhwGUgZ.js} +1 -1
- ksadk/server/static/assets/{dist-W-TIVntx.js → dist-DyqV4fL4.js} +1 -1
- ksadk/server/static/assets/{dist-DUX-id_F.js → dist-M67Hwu0n.js} +1 -1
- ksadk/server/static/assets/{dist-DmldrHd_.js → dist-u8tVH5Ak.js} +1 -1
- ksadk/server/static/assets/{ebnfDiagram-PWID7BFC-Da9C9NO1.js → ebnfDiagram-PWID7BFC-DVfMsLge.js} +1 -1
- ksadk/server/static/assets/{erDiagram-SSCWMZ5O-EAGqqR79.js → erDiagram-SSCWMZ5O-CWEZSYaw.js} +1 -1
- ksadk/server/static/assets/eventmodeling-NTZA5JFV-Dr5okd8V.js +1 -0
- ksadk/server/static/assets/flowDiagram-A5DVABFB-VEO-cVcO.js +1 -0
- ksadk/server/static/assets/{ganttDiagram-EL5Y4UJY-DqJsKb59.js → ganttDiagram-EL5Y4UJY-TWIxoyYL.js} +1 -1
- ksadk/server/static/assets/gitGraph-4MIJSDKK-CKiKY9Cf.js +1 -0
- ksadk/server/static/assets/{gitGraphDiagram-WWUBYQGX-B6g4dtDi.js → gitGraphDiagram-WWUBYQGX-DDh2hd2S.js} +1 -1
- ksadk/server/static/assets/index-B2k_urY8.js +241 -0
- ksadk/server/static/assets/index-ILPt6IWF.css +2 -0
- ksadk/server/static/assets/info-A6RAGUB7-ubA4Nm_p.js +1 -0
- ksadk/server/static/assets/{infoDiagram-RXCK75RN-Djm_nbd5.js → infoDiagram-RXCK75RN-CgoyWiDR.js} +1 -1
- ksadk/server/static/assets/{ishikawaDiagram-5VMMS53U-DWya9SG2.js → ishikawaDiagram-5VMMS53U-CyRIu_om.js} +1 -1
- ksadk/server/static/assets/{journeyDiagram-EYS64GPL-DutxhSNI.js → journeyDiagram-EYS64GPL-DN5JXHzN.js} +1 -1
- ksadk/server/static/assets/{kanban-definition-3QL26DDD-DJdOF8Fm.js → kanban-definition-3QL26DDD-CNP3ui66.js} +1 -1
- ksadk/server/static/assets/{katex-vFWytM5c.js → katex-DWEzQy1d.js} +1 -1
- ksadk/server/static/assets/{linear-BHjG8b-J.js → linear-C-qHp7d2.js} +1 -1
- ksadk/server/static/assets/{mermaid-parser.core-KGSy4jWT.js → mermaid-parser.core-Cl-K943T.js} +2 -2
- ksadk/server/static/assets/{mindmap-definition-FBJOCRG2-9-HC88D4.js → mindmap-definition-FBJOCRG2-D5DB5mYm.js} +1 -1
- ksadk/server/static/assets/packet-AYTQ26CC-C7R3NUlK.js +1 -0
- ksadk/server/static/assets/{pegDiagram-XKGWAZYB-DrEZd4fD.js → pegDiagram-XKGWAZYB-BQVc9ia_.js} +1 -1
- ksadk/server/static/assets/pie-WAS4IAKB-BIT6qjr3.js +1 -0
- ksadk/server/static/assets/{pieDiagram-E7YTZNPT-DpjqOFFp.js → pieDiagram-E7YTZNPT-B6UCAvtY.js} +1 -1
- ksadk/server/static/assets/{quadrantDiagram-AXDQQJYC-BiNhgOXX.js → quadrantDiagram-AXDQQJYC-mIr2d3eb.js} +1 -1
- ksadk/server/static/assets/radar-RG4KPBEZ-l5noef9X.js +1 -0
- ksadk/server/static/assets/railroad-74A4TZTK-Baea6ZvM.js +1 -0
- ksadk/server/static/assets/railroad-abnf-HS5TGJTU-OKn8zZnU.js +1 -0
- ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-DdKAKC4l.js +1 -0
- ksadk/server/static/assets/railroad-peg-WCYAUIDC-Bds8udlG.js +1 -0
- ksadk/server/static/assets/{railroadDiagram-O6MQD6OU-_Y3Ojg6G.js → railroadDiagram-O6MQD6OU-Dt3FFDmK.js} +1 -1
- ksadk/server/static/assets/{requirementDiagram-EFPCY7ZU-DeCFdg9K.js → requirementDiagram-EFPCY7ZU-Cz_vQtgF.js} +1 -1
- ksadk/server/static/assets/{sankeyDiagram-P5KCCOFB-CPH75rhw.js → sankeyDiagram-P5KCCOFB-wlO1TsH5.js} +1 -1
- ksadk/server/static/assets/{sequenceDiagram-WJ2MYXX4-OEpQQdr4.js → sequenceDiagram-WJ2MYXX4-CHRbElYq.js} +1 -1
- ksadk/server/static/assets/{sizeCapture-X5ZJPWSS-heUErzhI.js → sizeCapture-X5ZJPWSS-DXX54iyO.js} +1 -1
- ksadk/server/static/assets/{stateDiagram-HBIQ2CUA-DvA3jSMB.js → stateDiagram-HBIQ2CUA-Dp_lw9II.js} +1 -1
- ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-5b-8o2iS.js +1 -0
- ksadk/server/static/assets/{swimlanes-XN3QIQJK-CR-dfN_t.js → swimlanes-XN3QIQJK-D8O5UlLG.js} +1 -1
- ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-uf6WPRA4.js +8 -0
- ksadk/server/static/assets/{timeline-definition-24CTP7MA-DAVJf1TX.js → timeline-definition-24CTP7MA-8mfKcxvG.js} +1 -1
- ksadk/server/static/assets/treeView-Q6P3EWNA-CK5CSrSO.js +1 -0
- ksadk/server/static/assets/treemap-WGGIJYW6-Ch2BKgAM.js +1 -0
- ksadk/server/static/assets/{vennDiagram-4TSXK5OY-DwM8eDkh.js → vennDiagram-4TSXK5OY-EnNl0DE7.js} +1 -1
- ksadk/server/static/assets/wardley-WFR3VGLG-YW3Q0ml_.js +1 -0
- ksadk/server/static/assets/{wardleyDiagram-VM6X3IG4-DZL-Zz2t.js → wardleyDiagram-VM6X3IG4-DBu5fbYr.js} +1 -1
- ksadk/server/static/assets/{xychartDiagram-S5SC5T6Z-Dy6Yh_hu.js → xychartDiagram-S5SC5T6Z-BSVXs4IY.js} +1 -1
- ksadk/server/static/index.html +2 -2
- ksadk/skills/service_client.py +48 -0
- ksadk/studio/agent_lifecycle.py +26 -20
- ksadk/studio/api.py +513 -71
- ksadk/studio/api_catalog_routes.py +5 -0
- ksadk/studio/api_contracts.py +34 -1
- ksadk/studio/api_plugin_routes.py +480 -0
- ksadk/studio/attachment_store.py +187 -0
- ksadk/studio/authoring.py +10 -4
- ksadk/studio/authoring_coordinator.py +12 -2
- ksadk/studio/builder.py +123 -43
- ksadk/studio/cloud.py +4 -0
- ksadk/studio/codex_agent_service.py +12 -1
- ksadk/studio/codex_builder.py +54 -15
- ksadk/studio/codex_manifest.py +21 -1
- ksadk/studio/codex_run.py +16 -4
- ksadk/studio/compatibility_report.py +445 -0
- ksadk/studio/compiler.py +6 -1
- ksadk/studio/contracts.py +123 -4
- ksadk/studio/dsh_provider_registration.py +768 -0
- ksadk/studio/event_store.py +54 -6
- ksadk/studio/framework_run.py +37 -4
- ksadk/studio/hosted_kernel.py +1 -1
- ksadk/studio/operations.py +12 -2
- ksadk/studio/plugin_composition.py +301 -0
- ksadk/studio/plugin_kernel_adapter.py +128 -0
- ksadk/studio/plugin_runtime.py +652 -0
- ksadk/studio/resource_catalog.py +6 -1
- ksadk/studio/run_service.py +713 -75
- ksadk/studio/runtime_source.py +9 -1
- ksadk/studio/scheduler_runtime.py +374 -0
- ksadk/studio/scheduler_service.py +211 -0
- ksadk/studio/service.py +672 -82
- ksadk/studio/shared_web.py +167 -28
- ksadk/studio/soul.py +52 -0
- ksadk/studio/static/assets/index-DXs1Eq4M.css +1 -0
- ksadk/studio/static/assets/index-DeDqwTsD.js +283 -0
- ksadk/studio/static/index.html +2 -2
- ksadk/studio/validator.py +1 -1
- ksadk/studio/workspace.py +1 -0
- ksadk/version.py +1 -1
- ksadk_runtime_common/workspace_files/path_utils.py +41 -0
- ksadk/server/static/assets/architecture-7GRP2DOG-DjKratdf.js +0 -1
- ksadk/server/static/assets/channel-4cQHKtx1.js +0 -1
- ksadk/server/static/assets/chunk-2Q5K7J3B-DmgWkESh.js +0 -1
- ksadk/server/static/assets/classDiagram-DTDB5LWJ-DLFd9Xi0.js +0 -1
- ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-DLFd9Xi0.js +0 -1
- ksadk/server/static/assets/cynefin-OW5HDTMX-DjEM48Qp.js +0 -1
- ksadk/server/static/assets/eventmodeling-NTZA5JFV-CMgMaJrC.js +0 -1
- ksadk/server/static/assets/flowDiagram-A5DVABFB-BBKL0x3P.js +0 -1
- ksadk/server/static/assets/gitGraph-4MIJSDKK-Dw63mrhw.js +0 -1
- ksadk/server/static/assets/index-8ipRcQ-M.js +0 -240
- ksadk/server/static/assets/index-ByFCqjlh.css +0 -2
- ksadk/server/static/assets/info-A6RAGUB7-B6iAblII.js +0 -1
- ksadk/server/static/assets/packet-AYTQ26CC-BOYAipuq.js +0 -1
- ksadk/server/static/assets/pie-WAS4IAKB-Ctq0Kbku.js +0 -1
- ksadk/server/static/assets/radar-RG4KPBEZ-DN0-ul_0.js +0 -1
- ksadk/server/static/assets/railroad-74A4TZTK-C6cC_to0.js +0 -1
- ksadk/server/static/assets/railroad-abnf-HS5TGJTU-BimjIuRc.js +0 -1
- ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-CvY65ABq.js +0 -1
- ksadk/server/static/assets/railroad-peg-WCYAUIDC-D72Iq_Tv.js +0 -1
- ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BgQY03nz.js +0 -1
- ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-DLVw3q5Q.js +0 -8
- ksadk/server/static/assets/treeView-Q6P3EWNA-BMpO0wsB.js +0 -1
- ksadk/server/static/assets/treemap-WGGIJYW6-Et0aqzU3.js +0 -1
- ksadk/server/static/assets/wardley-WFR3VGLG-CbY0mhpl.js +0 -1
- ksadk/studio/static/assets/index-BLpcdrgW.css +0 -1
- ksadk/studio/static/assets/index-CYekR2Xv.js +0 -260
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/WHEEL +0 -0
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/entry_points.txt +0 -0
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/licenses/LICENSE +0 -0
- {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
|
+
]
|