agentengine-sdk-python 0.8.3__py3-none-any.whl → 0.8.4__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.3.dist-info → agentengine_sdk_python-0.8.4.dist-info}/METADATA +9 -8
- {agentengine_sdk_python-0.8.3.dist-info → agentengine_sdk_python-0.8.4.dist-info}/RECORD +482 -245
- ksadk/_build_provenance.json +1 -1
- ksadk/api/client.py +105 -91
- ksadk/builders/code_builder.py +36 -9
- ksadk/builders/framework_requirements.py +2 -1
- ksadk/builders/ks3_uploader.py +36 -7
- ksadk/builders/managed_runtime_builder.py +1 -0
- ksadk/cli/__init__.py +32 -3
- ksadk/cli/cmd_managed_runtime.py +13 -1
- ksadk/cli/cmd_plugin.py +6 -1
- ksadk/cli/cmd_web.py +2 -5
- ksadk/cli/runtime_bootstrap.py +23 -1
- ksadk/codex/client.py +424 -96
- ksadk/codex/projection.py +24 -0
- ksadk/codex/runtime.py +56 -298
- ksadk/common/auth.py +16 -10
- ksadk/configs/env_registry.py +29 -1
- ksadk/conversations/message_projection.py +135 -14
- ksadk/conversations/projector.py +4 -6
- ksadk/conversations/runtime_invocation.py +5 -0
- ksadk/conversations/runtime_payloads.py +1 -0
- ksadk/conversations/runtime_preparation.py +30 -27
- ksadk/conversations/runtime_resume.py +50 -2
- ksadk/conversations/runtime_stream_events.py +5 -0
- ksadk/conversations/runtime_streaming.py +25 -7
- ksadk/deployment/env_forward.py +2 -2
- ksadk/deployment/providers/serverless.py +1 -1
- ksadk/events/adapters/_codex_validators.py +18 -7
- ksadk/events/adapters/langgraph.py +15 -1
- ksadk/events/canonical_store.py +3 -1
- ksadk/events/projections.py +1 -1
- ksadk/harness/config.py +2 -2
- ksadk/harness/runtime.py +27 -27
- ksadk/harness/tools.py +38 -13
- ksadk/interaction/provider.py +1 -0
- ksadk/interaction/providers/langgraph.py +1 -0
- ksadk/kernel/bootstrap.py +28 -4
- ksadk/kernel/ingress.py +15 -2
- ksadk/kernel/sqlite_store.py +27 -0
- ksadk/kernel/store.py +3 -0
- ksadk/kernel/worker.py +65 -10
- ksadk/knowledge_base/client.py +57 -27
- ksadk/mcp_runtime/__init__.py +12 -4
- ksadk/memory/adk/backends/sdk_ltm_backend.py +161 -41
- ksadk/memory/adk/resilient_session_service.py +25 -17
- ksadk/memory/adk/short_term_memory.py +26 -9
- ksadk/memory/models.py +7 -4
- ksadk/memory/provider_resolver.py +3 -0
- ksadk/model_proxy/server.py +10 -5
- ksadk/plugins/__init__.py +4 -0
- ksadk/plugins/artifacts.py +275 -0
- ksadk/plugins/bridges/__init__.py +4 -2
- ksadk/plugins/bridges/codex.py +120 -13
- ksadk/plugins/bridges/dsh.py +340 -156
- ksadk/plugins/builtins.py +35 -36
- ksadk/plugins/bundle_security.py +28 -4
- ksadk/plugins/codex_manifest.py +983 -0
- ksadk/plugins/composition.py +8 -0
- ksadk/plugins/contracts.py +118 -0
- ksadk/plugins/delivery.py +153 -0
- ksadk/plugins/dsh_installation_digest.py +71 -0
- ksadk/plugins/dsh_toolchain.py +110 -21
- ksadk/plugins/ecosystem_probe.py +6 -6
- ksadk/plugins/host.py +102 -39
- ksadk/plugins/kernel_adapter.py +205 -0
- ksadk/plugins/providers/__init__.py +23 -0
- ksadk/plugins/providers/bundles/dsh-knowledge/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/dsh-knowledge/index.mjs +53 -0
- ksadk/plugins/providers/bundles/dsh-knowledge/package.json +16 -0
- ksadk/plugins/providers/bundles/dsh-memory/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/dsh-memory/index.mjs +175 -0
- ksadk/plugins/providers/bundles/dsh-memory/package.json +16 -0
- ksadk/plugins/providers/bundles/dsh-platform-resources/client.mjs +166 -0
- ksadk/plugins/providers/bundles/dsh-platform-resources/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/dsh-platform-resources/index.mjs +9 -0
- ksadk/plugins/providers/bundles/dsh-platform-resources/package.json +12 -0
- ksadk/plugins/providers/bundles/dsh-skill-center/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/dsh-skill-center/index.mjs +125 -0
- ksadk/plugins/providers/bundles/dsh-skill-center/package.json +16 -0
- ksadk/plugins/providers/bundles/ksadk-dsh-capability-host/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/ksadk-dsh-capability-host/index.mjs +870 -0
- ksadk/plugins/providers/bundles/ksadk-dsh-capability-host/package.json +27 -0
- ksadk/plugins/providers/bundles/ksadk-dsh-studio/client.js +92 -0
- ksadk/plugins/providers/bundles/ksadk-dsh-studio/index.mjs +15 -0
- ksadk/plugins/providers/bundles/ksadk-dsh-studio/package.json +7 -0
- ksadk/plugins/providers/codex.py +335 -69
- ksadk/plugins/providers/codex_native.py +368 -0
- ksadk/plugins/providers/codex_turn.py +301 -0
- ksadk/plugins/providers/dsh_capabilities.py +1389 -0
- ksadk/plugins/providers/dsh_mcp.py +467 -0
- ksadk/plugins/providers/harness.py +118 -113
- ksadk/plugins/providers/mcp_projection.py +150 -0
- ksadk/plugins/providers/platform_resources.py +657 -0
- ksadk/plugins/resolver.py +8 -1
- ksadk/resource_runtime/__init__.py +4 -0
- ksadk/resource_runtime/broker.py +616 -0
- ksadk/resource_runtime/build_artifacts.py +165 -0
- ksadk/resource_runtime/contracts.py +138 -0
- ksadk/resource_runtime/discovery_receipts.py +100 -0
- ksadk/resource_runtime/discovery_skills.py +172 -0
- ksadk/resource_runtime/errors.py +6 -0
- ksadk/resource_runtime/ipc.py +142 -0
- ksadk/resource_runtime/kernel_approval.py +165 -0
- ksadk/resource_runtime/knowledge.py +114 -0
- ksadk/resource_runtime/langgraph.py +94 -0
- ksadk/resource_runtime/leases.py +191 -0
- ksadk/resource_runtime/managed_projection.py +186 -0
- ksadk/resource_runtime/mcp_server.py +138 -0
- ksadk/resource_runtime/memory_provider.py +318 -0
- ksadk/resource_runtime/memory_receipts.py +34 -0
- ksadk/resource_runtime/operation_ledger.py +211 -0
- ksadk/resource_runtime/plugin_config.py +48 -0
- ksadk/resource_runtime/policy_authorization.py +54 -0
- ksadk/resource_runtime/process.py +110 -0
- ksadk/resource_runtime/skill_artifacts.py +153 -0
- ksadk/resource_runtime/skill_runtime.py +42 -0
- ksadk/resource_runtime/skill_workspace.py +118 -0
- ksadk/resource_runtime/skills.py +215 -0
- ksadk/resource_runtime/snapshots.py +180 -0
- ksadk/resource_runtime/socket_server.py +103 -0
- ksadk/resource_runtime/supervisor.py +380 -0
- ksadk/resource_runtime/worker.py +504 -0
- ksadk/runners/_langgraph_runner_streams.py +74 -6
- ksadk/runners/adk_runner.py +149 -11
- ksadk/runners/base_runner.py +29 -1
- ksadk/runners/langgraph_runner.py +475 -145
- ksadk/runtime/_runner_adapter/stream_mapping.py +142 -18
- ksadk/runtime/adapter.py +2 -0
- ksadk/runtime/conversation_execution.py +92 -16
- ksadk/runtime/executor.py +78 -2
- ksadk/runtime/factory.py +29 -273
- ksadk/runtime/framework_adapters.py +23 -0
- ksadk/runtime/preprocessing.py +10 -2
- ksadk/runtime/runner_adapter.py +73 -14
- ksadk/runtime_context.py +40 -2
- ksadk/sandbox/backends/e2b.py +52 -11
- ksadk/sandbox/backends/local_process.py +9 -0
- ksadk/sandbox/base.py +2 -0
- ksadk/sandbox/e2b_connection.py +66 -0
- ksadk/scheduler/sqlite_store.py +18 -0
- ksadk/server/composition.py +2 -0
- ksadk/server/factory.py +5 -0
- ksadk/server/persistence_capability.py +516 -0
- ksadk/server/routes/control.py +210 -46
- ksadk/server/routes/dependencies.py +15 -0
- ksadk/server/routes/models.py +47 -4
- ksadk/server/routes/projection.py +252 -12
- ksadk/server/routes/sessions.py +284 -107
- ksadk/server/routes/streaming.py +43 -9
- ksadk/server/static/assets/{ArtifactsPanel-10GPXIJ6.js → ArtifactsPanel-CGbWlMNh.js} +1 -1
- ksadk/server/static/assets/{CodeBlock-BXk1l-G1.js → CodeBlock-b4C7upmO.js} +3 -3
- ksadk/server/static/assets/{MathMessageMarkdown-D9uB55Oh.js → MathMessageMarkdown-DbdW5A4_.js} +4 -4
- ksadk/server/static/assets/{MermaidBlock--OEYoXIJ.js → MermaidBlock-BEBN4Ksz.js} +6 -6
- ksadk/server/static/assets/{NativeTerminalPanel-DrEe1syq.js → NativeTerminalPanel-zvL-WpAn.js} +1 -1
- ksadk/server/static/assets/{abnfDiagram-VCTEODGH-eeTFJLvo.js → abnfDiagram-VCTEODGH-BYafW7Za.js} +1 -1
- ksadk/server/static/assets/{arc-S3ZNbIAd.js → arc-bTohInsS.js} +1 -1
- ksadk/server/static/assets/architecture-7GRP2DOG-BW5UtSnx.js +1 -0
- ksadk/server/static/assets/{architectureDiagram-5GKGNRK7-CRjgEJDX.js → architectureDiagram-5GKGNRK7-Ch5Lbzsi.js} +1 -1
- ksadk/server/static/assets/{blockDiagram-NRAW4CY4-C7bsBoNH.js → blockDiagram-NRAW4CY4-kqF4MDOG.js} +1 -1
- ksadk/server/static/assets/{c4Diagram-UCG6FXSJ-BX3c33Ob.js → c4Diagram-UCG6FXSJ-8uSRIODv.js} +1 -1
- ksadk/server/static/assets/channel-CF5Up3hQ.js +1 -0
- ksadk/server/static/assets/chunk-2Q5K7J3B-KkXX47WI.js +1 -0
- ksadk/server/static/assets/{chunk-5VM5RSS4-bZ7ws-8m.js → chunk-5VM5RSS4-DkhehZDr.js} +1 -1
- ksadk/server/static/assets/{chunk-F27PBJKO-D2r0pvhY.js → chunk-F27PBJKO-BdXhtPe9.js} +1 -1
- ksadk/server/static/assets/{chunk-G27WJ6UU-CZwF3oUP.js → chunk-G27WJ6UU-DPTpvpwi.js} +1 -1
- ksadk/server/static/assets/{chunk-JWPE2WC7-DigFYCML.js → chunk-JWPE2WC7-Pqoh38el.js} +1 -1
- ksadk/server/static/assets/{chunk-LCL6LL3I-CnorGV5q.js → chunk-LCL6LL3I-DeU00qC-.js} +1 -1
- ksadk/server/static/assets/{chunk-POPQ4Y6H-CexntQA-.js → chunk-POPQ4Y6H-CzxScFkr.js} +1 -1
- ksadk/server/static/assets/{chunk-RHFEMEQ7-CHTC7UxA.js → chunk-RHFEMEQ7-DpqMx-ux.js} +1 -1
- ksadk/server/static/assets/{chunk-SVP7TREG-BC1EWt_-.js → chunk-SVP7TREG-C6t0SypQ.js} +1 -1
- ksadk/server/static/assets/{chunk-XXDRQBXY-BTG24xN0.js → chunk-XXDRQBXY-DVaeafZk.js} +1 -1
- ksadk/server/static/assets/classDiagram-DTDB5LWJ-Cz81acRQ.js +1 -0
- ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-Cz81acRQ.js +1 -0
- ksadk/server/static/assets/{cose-bilkent-JH36ORCC-CkonRdAS.js → cose-bilkent-JH36ORCC-Bf9D3tzJ.js} +1 -1
- ksadk/server/static/assets/cynefin-OW5HDTMX-DVWs8a2t.js +1 -0
- ksadk/server/static/assets/{cynefinDiagram-5FMLGOSQ-DEOCBe15.js → cynefinDiagram-5FMLGOSQ-B9cY3iTz.js} +1 -1
- ksadk/server/static/assets/{dagre-3AP2YEHR-DBThPuLa.js → dagre-3AP2YEHR-CSwlx3CB.js} +1 -1
- ksadk/server/static/assets/{dagre-ClTPrTht.js → dagre-UBpgon5q.js} +1 -1
- ksadk/server/static/assets/{diagram-S7CK7UJ4-DsZHrnpJ.js → diagram-S7CK7UJ4-C9akhN9a.js} +1 -1
- ksadk/server/static/assets/{diagram-UQ7AKVKN-BJR4nhVr.js → diagram-UQ7AKVKN-CgedhTPI.js} +1 -1
- ksadk/server/static/assets/{diagram-VSXAHHWV-xzcERX0a.js → diagram-VSXAHHWV-Cjucz5BY.js} +1 -1
- ksadk/server/static/assets/{diagram-VX7I27RA-tn2xM1xu.js → diagram-VX7I27RA-Dd4Vleyn.js} +1 -1
- ksadk/server/static/assets/{diagram-Z3DM3KII-BZPGBkw5.js → diagram-Z3DM3KII-CZ2jGtYW.js} +1 -1
- ksadk/server/static/assets/{dist-DdRBIceA.js → dist-B2_CVgKv.js} +1 -1
- ksadk/server/static/assets/{dist-Cq4OL6nO.js → dist-B3FR1SKK.js} +1 -1
- ksadk/server/static/assets/{dist-M67Hwu0n.js → dist-BIr4DSRq.js} +1 -1
- ksadk/server/static/assets/{dist-B1oWRmrH.js → dist-BgV_owcB.js} +1 -1
- ksadk/server/static/assets/{dist-DyqV4fL4.js → dist-Bj730Ib4.js} +1 -1
- ksadk/server/static/assets/{dist-DfGFrb0v.js → dist-BnaTiyzN.js} +1 -1
- ksadk/server/static/assets/{dist-C71CsujO.js → dist-C1NMAY5a.js} +1 -1
- ksadk/server/static/assets/{dist-CXpH_Rdd.js → dist-C5_ZAZsG.js} +1 -1
- ksadk/server/static/assets/{dist-DJmCPRVB.js → dist-CB9iJrvg.js} +1 -1
- ksadk/server/static/assets/{dist-DjhwGUgZ.js → dist-CFzsn6BG.js} +1 -1
- ksadk/server/static/assets/{dist-BI1qQH4n.js → dist-Cf125P_m.js} +1 -1
- ksadk/server/static/assets/{dist-8VWI-2bZ.js → dist-ClYUxfeK.js} +1 -1
- ksadk/server/static/assets/{dist-DUwKa_l6.js → dist-DFD6Qa6T.js} +1 -1
- ksadk/server/static/assets/{dist-B-46q3Hk.js → dist-DVCOWtha.js} +1 -1
- ksadk/server/static/assets/{dist-BJLUwH6W.js → dist-D_ctSS2i.js} +1 -1
- ksadk/server/static/assets/{dist-4zN1_tq5.js → dist-DiixqL0J.js} +1 -1
- ksadk/server/static/assets/{dist-B0gdk4rl.js → dist-DoVSwoDb.js} +1 -1
- ksadk/server/static/assets/{dist-Bbwqa8TG.js → dist-DvFK7p5S.js} +1 -1
- ksadk/server/static/assets/{dist-D1V3f1B4.js → dist-QfkEHOlb.js} +1 -1
- ksadk/server/static/assets/{dist-Btyo4vX4.js → dist-btbmmX5O.js} +1 -1
- ksadk/server/static/assets/{dist-u8tVH5Ak.js → dist-fBO2v5Kt.js} +1 -1
- ksadk/server/static/assets/{dist-DQAB0qn_.js → dist-ufY3F5p6.js} +1 -1
- ksadk/server/static/assets/{ebnfDiagram-PWID7BFC-DVfMsLge.js → ebnfDiagram-PWID7BFC-Ci8NqqZu.js} +1 -1
- ksadk/server/static/assets/{erDiagram-SSCWMZ5O-CWEZSYaw.js → erDiagram-SSCWMZ5O-CCIOhLZG.js} +1 -1
- ksadk/server/static/assets/eventmodeling-NTZA5JFV-CQqR2hg3.js +1 -0
- ksadk/server/static/assets/flowDiagram-A5DVABFB-DDRRbGPm.js +1 -0
- ksadk/server/static/assets/{ganttDiagram-EL5Y4UJY-TWIxoyYL.js → ganttDiagram-EL5Y4UJY-Cp_r9Hb6.js} +1 -1
- ksadk/server/static/assets/gitGraph-4MIJSDKK-fgWEf7UD.js +1 -0
- ksadk/server/static/assets/{gitGraphDiagram-WWUBYQGX-DDh2hd2S.js → gitGraphDiagram-WWUBYQGX-Dlhc8Wr8.js} +1 -1
- ksadk/server/static/assets/index-DcTML1Up.css +2 -0
- ksadk/server/static/assets/index-Dh2XHqPK.js +244 -0
- ksadk/server/static/assets/info-A6RAGUB7-BUecLTdt.js +1 -0
- ksadk/server/static/assets/{infoDiagram-RXCK75RN-CgoyWiDR.js → infoDiagram-RXCK75RN-CO0rRTRA.js} +1 -1
- ksadk/server/static/assets/{ishikawaDiagram-5VMMS53U-CyRIu_om.js → ishikawaDiagram-5VMMS53U-CHiaedSS.js} +1 -1
- ksadk/server/static/assets/{journeyDiagram-EYS64GPL-DN5JXHzN.js → journeyDiagram-EYS64GPL-CHcCIB09.js} +1 -1
- ksadk/server/static/assets/{kanban-definition-3QL26DDD-CNP3ui66.js → kanban-definition-3QL26DDD-CJ_ebBZf.js} +1 -1
- ksadk/server/static/assets/{katex-DWEzQy1d.js → katex-B4weu383.js} +1 -1
- ksadk/server/static/assets/{linear-C-qHp7d2.js → linear-V-MgYHW1.js} +1 -1
- ksadk/server/static/assets/{mermaid-parser.core-Cl-K943T.js → mermaid-parser.core-CBijEX_c.js} +2 -2
- ksadk/server/static/assets/{mindmap-definition-FBJOCRG2-D5DB5mYm.js → mindmap-definition-FBJOCRG2-Bc-pQvpp.js} +1 -1
- ksadk/server/static/assets/packet-AYTQ26CC-BeW-btFv.js +1 -0
- ksadk/server/static/assets/{pegDiagram-XKGWAZYB-BQVc9ia_.js → pegDiagram-XKGWAZYB-D7genGLl.js} +1 -1
- ksadk/server/static/assets/pie-WAS4IAKB-X0xh4SaT.js +1 -0
- ksadk/server/static/assets/{pieDiagram-E7YTZNPT-B6UCAvtY.js → pieDiagram-E7YTZNPT-Bxj32zwD.js} +1 -1
- ksadk/server/static/assets/{quadrantDiagram-AXDQQJYC-mIr2d3eb.js → quadrantDiagram-AXDQQJYC-BURv2mGh.js} +1 -1
- ksadk/server/static/assets/radar-RG4KPBEZ-DzRMSqjd.js +1 -0
- ksadk/server/static/assets/railroad-74A4TZTK-Cc44icX-.js +1 -0
- ksadk/server/static/assets/railroad-abnf-HS5TGJTU-D6S4BdV-.js +1 -0
- ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-vD3aVE2o.js +1 -0
- ksadk/server/static/assets/railroad-peg-WCYAUIDC-CYbW_b4T.js +1 -0
- ksadk/server/static/assets/{railroadDiagram-O6MQD6OU-Dt3FFDmK.js → railroadDiagram-O6MQD6OU-CCsh3avW.js} +1 -1
- ksadk/server/static/assets/{requirementDiagram-EFPCY7ZU-Cz_vQtgF.js → requirementDiagram-EFPCY7ZU-_FRRBKEd.js} +1 -1
- ksadk/server/static/assets/{sankeyDiagram-P5KCCOFB-wlO1TsH5.js → sankeyDiagram-P5KCCOFB-C-gr2o9M.js} +1 -1
- ksadk/server/static/assets/{sequenceDiagram-WJ2MYXX4-CHRbElYq.js → sequenceDiagram-WJ2MYXX4-D97NmfKg.js} +1 -1
- ksadk/server/static/assets/{sizeCapture-X5ZJPWSS-DXX54iyO.js → sizeCapture-X5ZJPWSS-DjrPC5FX.js} +1 -1
- ksadk/server/static/assets/{stateDiagram-HBIQ2CUA-Dp_lw9II.js → stateDiagram-HBIQ2CUA-DzMNg1hh.js} +1 -1
- ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BIcjKFhF.js +1 -0
- ksadk/server/static/assets/{swimlanes-XN3QIQJK-D8O5UlLG.js → swimlanes-XN3QIQJK-Cssf_CsU.js} +1 -1
- ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-tq3K3Ybl.js +8 -0
- ksadk/server/static/assets/{timeline-definition-24CTP7MA-8mfKcxvG.js → timeline-definition-24CTP7MA-BWTHzyS1.js} +1 -1
- ksadk/server/static/assets/treeView-Q6P3EWNA-DBXt3GdU.js +1 -0
- ksadk/server/static/assets/treemap-WGGIJYW6-DrCy4E_l.js +1 -0
- ksadk/server/static/assets/{vennDiagram-4TSXK5OY-EnNl0DE7.js → vennDiagram-4TSXK5OY-BSKgJsmR.js} +1 -1
- ksadk/server/static/assets/wardley-WFR3VGLG-CefhOliM.js +1 -0
- ksadk/server/static/assets/{wardleyDiagram-VM6X3IG4-DBu5fbYr.js → wardleyDiagram-VM6X3IG4-eflHNoaZ.js} +1 -1
- ksadk/server/static/assets/{xychartDiagram-S5SC5T6Z-BSVXs4IY.js → xychartDiagram-S5SC5T6Z-eYqrQ-v4.js} +1 -1
- ksadk/server/static/index.html +2 -2
- ksadk/session_context.py +66 -0
- ksadk/sessions/__init__.py +11 -21
- ksadk/sessions/_local_service_sync.py +263 -1
- ksadk/sessions/base.py +176 -0
- ksadk/sessions/errors.py +4 -0
- ksadk/sessions/in_memory.py +309 -19
- ksadk/sessions/local_service.py +83 -1
- ksadk/sessions/persistence.py +275 -0
- ksadk/sessions/postgres_service.py +358 -0
- ksadk/sessions/resilience.py +8 -0
- ksadk/sessions/resilient.py +362 -4
- ksadk/sessions/topology.py +135 -0
- ksadk/skills/models.py +26 -4
- ksadk/skills/package_store.py +186 -38
- ksadk/skills/runtime/agent.py +31 -6
- ksadk/skills/runtime/artifact_delivery.py +139 -0
- ksadk/skills/runtime/backends/e2b.py +98 -6
- ksadk/skills/runtime/backends/local.py +73 -8
- ksadk/skills/runtime/base.py +3 -0
- ksadk/skills/runtime/executor.py +2 -0
- ksadk/skills/runtime/loader.py +10 -2
- ksadk/skills/runtime/pinned.py +114 -0
- ksadk/skills/runtime/request.py +31 -0
- ksadk/skills/service_client.py +167 -28
- ksadk/studio/api.py +224 -42
- ksadk/studio/api_catalog_routes.py +18 -0
- ksadk/studio/api_plugin_routes.py +526 -95
- ksadk/studio/api_resource_connections.py +183 -0
- ksadk/studio/authoring_coordinator.py +19 -14
- ksadk/studio/builder.py +76 -0
- ksadk/studio/cloud.py +342 -104
- ksadk/studio/cloud_shared_web.py +565 -0
- ksadk/studio/codex_agent_service.py +128 -26
- ksadk/studio/codex_builder.py +241 -2
- ksadk/studio/codex_credentials.py +90 -0
- ksadk/studio/codex_manifest.py +31 -4
- ksadk/studio/codex_plugin_store.py +746 -0
- ksadk/studio/codex_provider_build.py +231 -0
- ksadk/studio/codex_run.py +184 -25
- ksadk/studio/compatibility_report.py +12 -1
- ksadk/studio/compiler.py +78 -14
- ksadk/studio/contracts.py +132 -6
- ksadk/studio/dsh_application.py +137 -0
- ksadk/studio/dsh_capability_service.py +1107 -0
- ksadk/studio/dsh_models.py +59 -0
- ksadk/studio/dsh_provider_registration.py +320 -13
- ksadk/studio/hosted_kernel.py +88 -13
- ksadk/studio/mcp_runtime.py +11 -3
- ksadk/studio/model_client.py +2 -2
- ksadk/studio/plugin_composition.py +190 -23
- ksadk/studio/plugin_kernel_adapter.py +12 -112
- ksadk/studio/plugin_runtime.py +224 -45
- ksadk/studio/resource_authority.py +637 -0
- ksadk/studio/resource_binding_validation.py +77 -0
- ksadk/studio/resource_build_admission.py +196 -0
- ksadk/studio/resource_build_materializer.py +149 -0
- ksadk/studio/resource_catalog.py +143 -2
- ksadk/studio/resource_connections.py +220 -0
- ksadk/studio/run_service.py +462 -52
- ksadk/studio/runtime_source.py +15 -7
- ksadk/studio/scheduler_assistant.py +317 -0
- ksadk/studio/scheduler_runtime.py +9 -0
- ksadk/studio/scheduler_service.py +3 -0
- ksadk/studio/service.py +490 -44
- ksadk/studio/shared_web.py +446 -62
- ksadk/studio/static/assets/CodeBlock-DCUUY0PL-BLjA7kbS.js +41 -0
- ksadk/studio/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- ksadk/studio/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- ksadk/studio/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- ksadk/studio/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- ksadk/studio/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- ksadk/studio/static/assets/MathMessageMarkdown-CBcDrAw--BEOyPC3w.css +1 -0
- ksadk/studio/static/assets/MathMessageMarkdown-CBcDrAw--CUYIXwgI.js +8 -0
- ksadk/studio/static/assets/MermaidBlock-DBFTztfQ-BTYKWEbD.js +44 -0
- ksadk/studio/static/assets/PrismRenderer-wiXRHURw.js +3 -0
- ksadk/studio/static/assets/abnfDiagram-VCTEODGH-CLETGjKq.js +1 -0
- ksadk/studio/static/assets/arc-Pry-7G1_.js +1 -0
- ksadk/studio/static/assets/architecture-7GRP2DOG-hXMiO_AU.js +1 -0
- ksadk/studio/static/assets/architectureDiagram-5GKGNRK7-B5EQysk6.js +36 -0
- ksadk/studio/static/assets/array-BifhSqXX.js +1 -0
- ksadk/studio/static/assets/blockDiagram-I7D4REHJ-CFmPl1de.js +129 -0
- ksadk/studio/static/assets/c4Diagram-7LVT6UL2-BRrGu8qt.js +38 -0
- ksadk/studio/static/assets/channel-BqO-gtEw.js +1 -0
- ksadk/studio/static/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
- ksadk/studio/static/assets/chunk-4HAMMTFA-D220vUtq.js +62 -0
- ksadk/studio/static/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
- ksadk/studio/static/assets/chunk-75Z2AOVW-C8lLv_qc.js +2 -0
- ksadk/studio/static/assets/chunk-DU6HZSFF-C4DtVpCu.js +128 -0
- ksadk/studio/static/assets/chunk-F27PBJKO-Cpw14SNG.js +1 -0
- ksadk/studio/static/assets/chunk-FOHPRMQF-DHwB1DNv.js +161 -0
- ksadk/studio/static/assets/chunk-GMAD6QVW-DiQA-u2N.js +72 -0
- ksadk/studio/static/assets/chunk-GVQU2GXP-DUdNBUCi.js +1 -0
- ksadk/studio/static/assets/chunk-IMKFNOWR-BSlbjT_b.js +231 -0
- ksadk/studio/static/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
- ksadk/studio/static/assets/chunk-L3NEJ4N5-Db8Ab22W.js +1 -0
- ksadk/studio/static/assets/chunk-OSK3NFVY-M2m6G3j4.js +10 -0
- ksadk/studio/static/assets/chunk-P2QGCYS3-DezaQxiK.js +1 -0
- ksadk/studio/static/assets/chunk-POPQ4Y6H-DOpufJ3t.js +1 -0
- ksadk/studio/static/assets/chunk-PWAF6VOD-CfLb1gSc.js +1 -0
- ksadk/studio/static/assets/chunk-SHT3W25Y-DAyIVp5H.js +168 -0
- ksadk/studio/static/assets/chunk-SVP7TREG-DulomOrS.js +88 -0
- ksadk/studio/static/assets/chunk-TICWLB2K-bHJYoflj.js +206 -0
- ksadk/studio/static/assets/chunk-XXDRQBXY-7PcdR2B1.js +1 -0
- ksadk/studio/static/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
- ksadk/studio/static/assets/classDiagram-ZZMXUADV-D_bW8JjA.js +1 -0
- ksadk/studio/static/assets/classDiagram-v2-VYDZK3BY-D_bW8JjA.js +1 -0
- ksadk/studio/static/assets/cose-bilkent-JH36ORCC-snL88wkX.js +1 -0
- ksadk/studio/static/assets/cynefin-OW5HDTMX-Bd1YhRo1.js +1 -0
- ksadk/studio/static/assets/cynefinDiagram-5FMLGOSQ-BBrIG_mI.js +62 -0
- ksadk/studio/static/assets/cytoscape.esm-CGd-uY3x.js +321 -0
- ksadk/studio/static/assets/dagre-GXQ25YYZ-DtF1nGdY.js +4 -0
- ksadk/studio/static/assets/dagre-PrKaheQc.js +1 -0
- ksadk/studio/static/assets/defaultLocale-BFoDCU3G.js +1 -0
- ksadk/studio/static/assets/diagram-S7CK7UJ4-0BZ98mya.js +30 -0
- ksadk/studio/static/assets/diagram-UQ7AKVKN-6mLKEo6L.js +41 -0
- ksadk/studio/static/assets/diagram-VSXAHHWV-CJBIaiZ2.js +3 -0
- ksadk/studio/static/assets/diagram-VX7I27RA-BohLEE1C.js +24 -0
- ksadk/studio/static/assets/diagram-Z3DM3KII-D8KIGonG.js +24 -0
- ksadk/studio/static/assets/dist-v5Q1xZ2K.js +1 -0
- ksadk/studio/static/assets/ebnfDiagram-PWID7BFC-C99HU61t.js +1 -0
- ksadk/studio/static/assets/erDiagram-RLTQ6QDP-DLGry2Ap.js +99 -0
- ksadk/studio/static/assets/eventmodeling-NTZA5JFV-B1_5Ac8j.js +1 -0
- ksadk/studio/static/assets/flowDiagram-HODETNUW-DD5hmicn.js +1 -0
- ksadk/studio/static/assets/ganttDiagram-EL5Y4UJY-BPJrVLSG.js +292 -0
- ksadk/studio/static/assets/gitGraph-4MIJSDKK-Cy_ZQBGi.js +1 -0
- ksadk/studio/static/assets/gitGraphDiagram-WWUBYQGX-CZCUz4MT.js +106 -0
- ksadk/studio/static/assets/graphlib-DS17s2tU.js +1 -0
- ksadk/studio/static/assets/index-CeHL9ARV.css +2 -0
- ksadk/studio/static/assets/index-D9QEcVpL.js +320 -0
- ksadk/studio/static/assets/info-A6RAGUB7-DBzDQB_I.js +1 -0
- ksadk/studio/static/assets/infoDiagram-27XIBGKW-B-sq07Hi.js +2 -0
- ksadk/studio/static/assets/init-C-OQMol4.js +1 -0
- ksadk/studio/static/assets/ishikawaDiagram-5VMMS53U-CljI2wyf.js +70 -0
- ksadk/studio/static/assets/journeyDiagram-3NMN7TZE-D50Fo2sL.js +139 -0
- ksadk/studio/static/assets/kanban-definition-UXKFOSKX-DimfOTam.js +89 -0
- ksadk/studio/static/assets/katex-DhON-x74.js +257 -0
- ksadk/studio/static/assets/line-CX5BOOUP.js +1 -0
- ksadk/studio/static/assets/linear-BCxN9JLv.js +1 -0
- ksadk/studio/static/assets/mermaid-parser.core-BdCz8fkS.js +7 -0
- ksadk/studio/static/assets/mindmap-definition-YA3MSWOX-Bizzqy5R.js +96 -0
- ksadk/studio/static/assets/ordinal-BDEzSJ7C.js +1 -0
- ksadk/studio/static/assets/packet-AYTQ26CC-gPln-_kG.js +1 -0
- ksadk/studio/static/assets/path-fybaL0A-.js +1 -0
- ksadk/studio/static/assets/pegDiagram-XKGWAZYB-Dy3fB-gW.js +1 -0
- ksadk/studio/static/assets/pie-WAS4IAKB-qZdHMr7B.js +1 -0
- ksadk/studio/static/assets/pieDiagram-E7YTZNPT-B9mrFSDF.js +39 -0
- ksadk/studio/static/assets/quadrantDiagram-AXDQQJYC-NzUfCbMR.js +7 -0
- ksadk/studio/static/assets/radar-RG4KPBEZ-BZlnaea9.js +1 -0
- ksadk/studio/static/assets/railroad-74A4TZTK-Ce3__H9c.js +1 -0
- ksadk/studio/static/assets/railroad-abnf-HS5TGJTU-DqQn7EzD.js +1 -0
- ksadk/studio/static/assets/railroad-ebnf-LZEXJU2U-B55kCA8F.js +1 -0
- ksadk/studio/static/assets/railroad-peg-WCYAUIDC-DfQnvkHd.js +1 -0
- ksadk/studio/static/assets/railroadDiagram-O6MQD6OU-DnhMjZCk.js +1 -0
- ksadk/studio/static/assets/requirementDiagram-BXWQKSXE-BF3d103U.js +84 -0
- ksadk/studio/static/assets/rolldown-runtime-Dy4uBu1J-DK3Fl9T5.js +1 -0
- ksadk/studio/static/assets/rough.esm-Dy-Kn_BL.js +1 -0
- ksadk/studio/static/assets/sankeyDiagram-P5KCCOFB-Bo-JTwt0.js +40 -0
- ksadk/studio/static/assets/sequenceDiagram-WJ2MYXX4-Dh4wZtUm.js +162 -0
- ksadk/studio/static/assets/sizeCapture-INFHLROL-B0uUizjq.js +1 -0
- ksadk/studio/static/assets/src-SFn9St9H.js +1 -0
- ksadk/studio/static/assets/stateDiagram-D77RDMKH-DH774G6d.js +1 -0
- ksadk/studio/static/assets/stateDiagram-v2-MP3YSRHH-DZvVWPBo.js +1 -0
- ksadk/studio/static/assets/swimlanes-42K2YHIH-Bl13OvTX.js +1 -0
- ksadk/studio/static/assets/swimlanesDiagram-VR7AAH4N-BnFOTALJ.js +8 -0
- ksadk/studio/static/assets/timeline-definition-24CTP7MA-DxmSSjRB.js +120 -0
- ksadk/studio/static/assets/treeView-Q6P3EWNA-D5Nn9Swz.js +1 -0
- ksadk/studio/static/assets/treemap-WGGIJYW6-CgHgnQhE.js +1 -0
- ksadk/studio/static/assets/vennDiagram-4TSXK5OY-B9QEXcJq.js +34 -0
- ksadk/studio/static/assets/wardley-WFR3VGLG-DXrK2RRY.js +1 -0
- ksadk/studio/static/assets/wardleyDiagram-VM6X3IG4-Ba1AOWoe.js +78 -0
- ksadk/studio/static/assets/xychartDiagram-S5SC5T6Z-Cr5pt5Y-.js +7 -0
- ksadk/studio/static/index.html +2 -2
- ksadk/version.py +1 -1
- ksadk/server/static/assets/architecture-7GRP2DOG-Dk9nRQ6-.js +0 -1
- ksadk/server/static/assets/channel-s354Yo6o.js +0 -1
- ksadk/server/static/assets/chunk-2Q5K7J3B-CDpPpKR5.js +0 -1
- ksadk/server/static/assets/classDiagram-DTDB5LWJ-Dd0nucl9.js +0 -1
- ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-Dd0nucl9.js +0 -1
- ksadk/server/static/assets/cynefin-OW5HDTMX-BBlT4uMf.js +0 -1
- ksadk/server/static/assets/eventmodeling-NTZA5JFV-Dr5okd8V.js +0 -1
- ksadk/server/static/assets/flowDiagram-A5DVABFB-VEO-cVcO.js +0 -1
- ksadk/server/static/assets/gitGraph-4MIJSDKK-CKiKY9Cf.js +0 -1
- ksadk/server/static/assets/index-B2k_urY8.js +0 -241
- ksadk/server/static/assets/index-ILPt6IWF.css +0 -2
- ksadk/server/static/assets/info-A6RAGUB7-ubA4Nm_p.js +0 -1
- ksadk/server/static/assets/packet-AYTQ26CC-C7R3NUlK.js +0 -1
- ksadk/server/static/assets/pie-WAS4IAKB-BIT6qjr3.js +0 -1
- ksadk/server/static/assets/radar-RG4KPBEZ-l5noef9X.js +0 -1
- ksadk/server/static/assets/railroad-74A4TZTK-Baea6ZvM.js +0 -1
- ksadk/server/static/assets/railroad-abnf-HS5TGJTU-OKn8zZnU.js +0 -1
- ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-DdKAKC4l.js +0 -1
- ksadk/server/static/assets/railroad-peg-WCYAUIDC-Bds8udlG.js +0 -1
- ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-5b-8o2iS.js +0 -1
- ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-uf6WPRA4.js +0 -8
- ksadk/server/static/assets/treeView-Q6P3EWNA-CK5CSrSO.js +0 -1
- ksadk/server/static/assets/treemap-WGGIJYW6-Ch2BKgAM.js +0 -1
- ksadk/server/static/assets/wardley-WFR3VGLG-YW3Q0ml_.js +0 -1
- ksadk/studio/static/assets/PrismRenderer-IYN-ffww.js +0 -3
- ksadk/studio/static/assets/index-DXs1Eq4M.css +0 -1
- ksadk/studio/static/assets/index-DeDqwTsD.js +0 -283
- {agentengine_sdk_python-0.8.3.dist-info → agentengine_sdk_python-0.8.4.dist-info}/WHEEL +0 -0
- {agentengine_sdk_python-0.8.3.dist-info → agentengine_sdk_python-0.8.4.dist-info}/entry_points.txt +0 -0
- {agentengine_sdk_python-0.8.3.dist-info → agentengine_sdk_python-0.8.4.dist-info}/licenses/LICENSE +0 -0
- {agentengine_sdk_python-0.8.3.dist-info → agentengine_sdk_python-0.8.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1107 @@
|
|
|
1
|
+
"""Studio-owned lifecycle for one profile-level DSH MCP capability host.
|
|
2
|
+
|
|
3
|
+
The service is intentionally ephemeral. It caches one host for the current
|
|
4
|
+
immutable Profile projection, keeps its loopback endpoint and bearer lease in
|
|
5
|
+
memory only, and exposes only descriptor/tool/result projections to routes.
|
|
6
|
+
Profile refresh and shutdown cancel all tracked calls before disposing the
|
|
7
|
+
sidecar.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import asyncio
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import re
|
|
16
|
+
import secrets
|
|
17
|
+
from collections.abc import Callable, Coroutine, Mapping, Sequence
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from dataclasses import field as dataclass_field
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any, Literal, cast
|
|
22
|
+
|
|
23
|
+
import httpx
|
|
24
|
+
from pydantic import ValidationError
|
|
25
|
+
|
|
26
|
+
from ksadk.plugins.bridges.dsh import (
|
|
27
|
+
DshProfileBuildSnapshot,
|
|
28
|
+
DshProfilePluginBridge,
|
|
29
|
+
DshProfileProjection,
|
|
30
|
+
)
|
|
31
|
+
from ksadk.plugins.dsh_toolchain import DshToolchainManager
|
|
32
|
+
from ksadk.plugins.host import PluginHostError
|
|
33
|
+
from ksadk.plugins.providers.dsh_capabilities import (
|
|
34
|
+
DSH_CAPABILITY_BUNDLE_PACKAGE,
|
|
35
|
+
DSH_CAPABILITY_HOST_VERSION,
|
|
36
|
+
DSH_CAPABILITY_MCP_PROTOCOL,
|
|
37
|
+
DshCapabilityTool,
|
|
38
|
+
DshMcpConnectorLease,
|
|
39
|
+
DshProfileCapabilityDescriptor,
|
|
40
|
+
DshProfileCapabilityHost,
|
|
41
|
+
DshProfileCapabilityInventory,
|
|
42
|
+
)
|
|
43
|
+
from ksadk.resource_runtime.broker import ResourceWriteAuthorizer
|
|
44
|
+
from ksadk.resource_runtime.operation_ledger import OperationLedger
|
|
45
|
+
from ksadk.resource_runtime.supervisor import (
|
|
46
|
+
ActiveResources,
|
|
47
|
+
ResourceRevalidator,
|
|
48
|
+
ResourceSupervisor,
|
|
49
|
+
)
|
|
50
|
+
from ksadk.resource_runtime.worker import WorkerInitialization
|
|
51
|
+
from ksadk.studio.errors import StudioError
|
|
52
|
+
|
|
53
|
+
_CALL_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$")
|
|
54
|
+
_TOOL_NAME = re.compile(r"^[A-Za-z0-9_.:-]{1,128}$")
|
|
55
|
+
_DIGEST = re.compile(r"^sha256:[0-9a-f]{64}$")
|
|
56
|
+
_GENERATION_ID = re.compile(r"^dshgen_[A-Za-z0-9_-]{24,96}$")
|
|
57
|
+
_MAX_ARGUMENT_BYTES = 256 * 1024
|
|
58
|
+
_MAX_RESPONSE_BYTES = 1024 * 1024
|
|
59
|
+
_JSON_RPC_ENVELOPE_BYTES = 16 * 1024
|
|
60
|
+
|
|
61
|
+
BridgeFactory = Callable[..., DshProfilePluginBridge]
|
|
62
|
+
HostFactory = Callable[..., DshProfileCapabilityHost]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass(frozen=True)
|
|
66
|
+
class _ActiveCall:
|
|
67
|
+
lease: DshMcpConnectorLease
|
|
68
|
+
tool_name: str
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass(frozen=True)
|
|
72
|
+
class DshCapabilityRuntimeSnapshot:
|
|
73
|
+
"""One atomically fenced descriptor/lease pair for PluginHost."""
|
|
74
|
+
|
|
75
|
+
descriptor: DshProfileCapabilityDescriptor
|
|
76
|
+
lease: DshMcpConnectorLease = dataclass_field(repr=False)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass(frozen=True)
|
|
80
|
+
class DshCapabilitySnapshot:
|
|
81
|
+
"""One generation-consistent public capability inventory."""
|
|
82
|
+
|
|
83
|
+
descriptor: DshProfileCapabilityDescriptor
|
|
84
|
+
tools: tuple[DshCapabilityTool, ...]
|
|
85
|
+
inventory: DshProfileCapabilityInventory
|
|
86
|
+
generation_id: str
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _strict_json_size(value: Any) -> int:
|
|
90
|
+
try:
|
|
91
|
+
encoded = json.dumps(
|
|
92
|
+
value,
|
|
93
|
+
allow_nan=False,
|
|
94
|
+
ensure_ascii=False,
|
|
95
|
+
separators=(",", ":"),
|
|
96
|
+
).encode("utf-8")
|
|
97
|
+
except (TypeError, ValueError, RecursionError) as error:
|
|
98
|
+
raise StudioError(
|
|
99
|
+
"DSH_CAPABILITY_ARGUMENTS_INVALID",
|
|
100
|
+
"DSH 工具参数必须是严格 JSON 对象",
|
|
101
|
+
status_code=422,
|
|
102
|
+
) from error
|
|
103
|
+
return len(encoded)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _duplicate_rejecting_object(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
|
|
107
|
+
value: dict[str, Any] = {}
|
|
108
|
+
for key, item in pairs:
|
|
109
|
+
if key in value:
|
|
110
|
+
raise ValueError("duplicate JSON member")
|
|
111
|
+
value[key] = item
|
|
112
|
+
return value
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _reject_non_finite(value: str) -> None:
|
|
116
|
+
raise ValueError(f"non-finite JSON number: {value}")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class StudioDshCapabilityService:
|
|
120
|
+
"""Lazy, generation-fenced access to a DSH Profile's real MCP lease."""
|
|
121
|
+
|
|
122
|
+
def __init__(
|
|
123
|
+
self,
|
|
124
|
+
workspace: Path,
|
|
125
|
+
*,
|
|
126
|
+
dsh_home: Path,
|
|
127
|
+
profile: str = "web",
|
|
128
|
+
dsh_command: Sequence[str] | None = None,
|
|
129
|
+
bridge_factory: BridgeFactory = DshProfilePluginBridge,
|
|
130
|
+
host_factory: HostFactory = DshProfileCapabilityHost,
|
|
131
|
+
mount_studio_app: bool = True,
|
|
132
|
+
max_argument_bytes: int = _MAX_ARGUMENT_BYTES,
|
|
133
|
+
max_response_bytes: int = _MAX_RESPONSE_BYTES,
|
|
134
|
+
) -> None:
|
|
135
|
+
self._workspace = workspace.resolve()
|
|
136
|
+
self._dsh_home = dsh_home.expanduser().resolve()
|
|
137
|
+
self._profile = profile
|
|
138
|
+
if isinstance(dsh_command, (str, bytes)):
|
|
139
|
+
raise ValueError("dsh_command must be a one-item argv sequence")
|
|
140
|
+
normalized_command = tuple(dsh_command) if dsh_command is not None else ()
|
|
141
|
+
if len(normalized_command) > 1:
|
|
142
|
+
raise ValueError("dsh_command must contain only the DSH executable")
|
|
143
|
+
self._explicit_dsh_executable = normalized_command[0] if normalized_command else None
|
|
144
|
+
self._bridge_factory = bridge_factory
|
|
145
|
+
self._host_factory = host_factory
|
|
146
|
+
self._mount_studio_app = mount_studio_app
|
|
147
|
+
if (
|
|
148
|
+
isinstance(max_argument_bytes, bool)
|
|
149
|
+
or max_argument_bytes < 1
|
|
150
|
+
or max_argument_bytes > 8 * 1024 * 1024
|
|
151
|
+
or isinstance(max_response_bytes, bool)
|
|
152
|
+
or max_response_bytes < 1
|
|
153
|
+
or max_response_bytes > 8 * 1024 * 1024
|
|
154
|
+
):
|
|
155
|
+
raise ValueError("DSH capability size limits are invalid")
|
|
156
|
+
self._max_argument_bytes = max_argument_bytes
|
|
157
|
+
self._max_result_bytes = max_response_bytes
|
|
158
|
+
self._max_wire_response_bytes = max_response_bytes + _JSON_RPC_ENVELOPE_BYTES
|
|
159
|
+
self._lock = asyncio.Lock()
|
|
160
|
+
self._host: DshProfileCapabilityHost | None = None
|
|
161
|
+
self._lease: DshMcpConnectorLease | None = None
|
|
162
|
+
self._projection: DshProfileProjection | None = None
|
|
163
|
+
self._generation_id: str | None = None
|
|
164
|
+
self._resource_supervisor: ResourceSupervisor | None = None
|
|
165
|
+
self._resource_generation_snapshot: DshProfileBuildSnapshot | None = None
|
|
166
|
+
self._active_calls: dict[str, _ActiveCall] = {}
|
|
167
|
+
self._closed = False
|
|
168
|
+
self.model_projection = None
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
def discover_or_create_workspace_default(cls, workspace: Path) -> "StudioDshCapabilityService":
|
|
172
|
+
root = workspace.resolve()
|
|
173
|
+
configured_home = os.environ.get("KSADK_DSH_HOME", "").strip()
|
|
174
|
+
dsh_home = (
|
|
175
|
+
Path(configured_home).expanduser()
|
|
176
|
+
if configured_home
|
|
177
|
+
else root / ".agentkit" / "dsh-home"
|
|
178
|
+
)
|
|
179
|
+
profile = os.environ.get("KSADK_DSH_PROFILE", "").strip() or "web"
|
|
180
|
+
configured_bin = os.environ.get("KSADK_DSH_BIN", "").strip()
|
|
181
|
+
command = (str(Path(configured_bin).expanduser()),) if configured_bin else None
|
|
182
|
+
return cls(
|
|
183
|
+
root,
|
|
184
|
+
dsh_home=dsh_home,
|
|
185
|
+
profile=profile,
|
|
186
|
+
dsh_command=command,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
@classmethod
|
|
190
|
+
def create_workspace_resource_default(cls, workspace: Path) -> "StudioDshCapabilityService":
|
|
191
|
+
"""Create the closed execution Profile used by platform resources."""
|
|
192
|
+
|
|
193
|
+
root = workspace.resolve()
|
|
194
|
+
configured_bin = os.environ.get("KSADK_DSH_BIN", "").strip()
|
|
195
|
+
command = (str(Path(configured_bin).expanduser()),) if configured_bin else None
|
|
196
|
+
return cls(
|
|
197
|
+
root,
|
|
198
|
+
dsh_home=root / ".agentkit" / "dsh-home",
|
|
199
|
+
profile="agentkit-resources",
|
|
200
|
+
dsh_command=command,
|
|
201
|
+
mount_studio_app=False,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
@property
|
|
205
|
+
def profile(self) -> str:
|
|
206
|
+
return self._profile
|
|
207
|
+
|
|
208
|
+
async def application_lease(self) -> DshMcpConnectorLease:
|
|
209
|
+
"""Reuse the live generation for browser assets and long-lived streams.
|
|
210
|
+
|
|
211
|
+
Management mutations dispose the generation explicitly. Avoid running
|
|
212
|
+
Profile projection and health checks for every CSS/JS/RPC request.
|
|
213
|
+
"""
|
|
214
|
+
if self._lease is not None and self._host is not None and self._host.pid is not None:
|
|
215
|
+
return self._lease
|
|
216
|
+
return await self.connector_lease()
|
|
217
|
+
|
|
218
|
+
async def has_enabled_profile_plugins(self) -> bool:
|
|
219
|
+
"""Inspect Profile metadata without starting the capability sidecar."""
|
|
220
|
+
|
|
221
|
+
async with self._lock:
|
|
222
|
+
if self._closed:
|
|
223
|
+
raise StudioError(
|
|
224
|
+
"DSH_CAPABILITY_SERVICE_CLOSED",
|
|
225
|
+
"DSH capability service 已关闭",
|
|
226
|
+
status_code=503,
|
|
227
|
+
)
|
|
228
|
+
command = await asyncio.to_thread(self._resolve_command)
|
|
229
|
+
return await asyncio.to_thread(self._profile_has_enabled_plugins, command)
|
|
230
|
+
|
|
231
|
+
def capture_resource_build_snapshot(self) -> DshProfileBuildSnapshot:
|
|
232
|
+
"""Capture immutable Profile inputs for a caller-owned Build staging area.
|
|
233
|
+
|
|
234
|
+
The bridge uses the cross-process Profile transaction lock, so this
|
|
235
|
+
synchronous method is safe to call from Studio's existing Build worker
|
|
236
|
+
thread without starting or mutating the live Core generation.
|
|
237
|
+
"""
|
|
238
|
+
|
|
239
|
+
if self._closed:
|
|
240
|
+
raise StudioError(
|
|
241
|
+
"DSH_CAPABILITY_SERVICE_CLOSED",
|
|
242
|
+
"DSH capability service 已关闭",
|
|
243
|
+
status_code=503,
|
|
244
|
+
)
|
|
245
|
+
command = self._resolve_command()
|
|
246
|
+
try:
|
|
247
|
+
with self._bridge_factory(
|
|
248
|
+
dsh_home=self._dsh_home,
|
|
249
|
+
profile=self._profile,
|
|
250
|
+
dsh_command=command,
|
|
251
|
+
cwd=self._workspace,
|
|
252
|
+
) as bridge:
|
|
253
|
+
return bridge.snapshot_for_build()
|
|
254
|
+
except StudioError:
|
|
255
|
+
raise
|
|
256
|
+
except Exception as error:
|
|
257
|
+
raise self._unavailable(error) from error
|
|
258
|
+
|
|
259
|
+
async def describe(self) -> DshProfileCapabilityDescriptor:
|
|
260
|
+
host, _lease = await self._ready_generation()
|
|
261
|
+
return host.descriptor
|
|
262
|
+
|
|
263
|
+
async def runtime_snapshot(self) -> DshCapabilityRuntimeSnapshot:
|
|
264
|
+
"""Fence descriptor and credential to the same supervised generation."""
|
|
265
|
+
|
|
266
|
+
async with self._lock:
|
|
267
|
+
host, lease = await self._ensure_ready_locked()
|
|
268
|
+
descriptor = host.descriptor
|
|
269
|
+
self._validate_lease(descriptor, lease)
|
|
270
|
+
return DshCapabilityRuntimeSnapshot(descriptor=descriptor, lease=lease)
|
|
271
|
+
|
|
272
|
+
async def capability_snapshot(self) -> DshCapabilitySnapshot:
|
|
273
|
+
"""Read descriptor, tools and health without crossing a refresh."""
|
|
274
|
+
|
|
275
|
+
async with self._lock:
|
|
276
|
+
host, lease = await self._ensure_ready_locked()
|
|
277
|
+
descriptor = host.descriptor
|
|
278
|
+
self._validate_lease(descriptor, lease)
|
|
279
|
+
tools = await self._list_tools_for_generation(host, lease)
|
|
280
|
+
inventory = await host.inventory()
|
|
281
|
+
if (
|
|
282
|
+
inventory.state != "ready"
|
|
283
|
+
or inventory.profile != descriptor.profile
|
|
284
|
+
or inventory.profile_digest != descriptor.profile_digest
|
|
285
|
+
or inventory.descriptor_digest != descriptor.descriptor_digest
|
|
286
|
+
or inventory.inventory_digest != descriptor.inventory_digest
|
|
287
|
+
or inventory.tool_count != len(descriptor.tools)
|
|
288
|
+
):
|
|
289
|
+
raise self._protocol_error()
|
|
290
|
+
return DshCapabilitySnapshot(
|
|
291
|
+
descriptor=descriptor,
|
|
292
|
+
tools=tools,
|
|
293
|
+
inventory=inventory,
|
|
294
|
+
generation_id=self._require_generation_id(),
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
async def descriptor_generation(self) -> tuple[DshProfileCapabilityDescriptor, str]:
|
|
298
|
+
"""Return a descriptor and non-secret lease epoch under one lock."""
|
|
299
|
+
|
|
300
|
+
async with self._lock:
|
|
301
|
+
host, _lease = await self._ensure_ready_locked()
|
|
302
|
+
return host.descriptor, self._require_generation_id()
|
|
303
|
+
|
|
304
|
+
async def connector_lease(self) -> DshMcpConnectorLease:
|
|
305
|
+
"""Return the current in-memory lease to a trusted PluginHost adapter."""
|
|
306
|
+
|
|
307
|
+
host, lease = await self._ready_generation()
|
|
308
|
+
descriptor = host.descriptor
|
|
309
|
+
self._validate_lease(descriptor, lease)
|
|
310
|
+
return lease
|
|
311
|
+
|
|
312
|
+
async def inventory(self) -> DshProfileCapabilityInventory:
|
|
313
|
+
host, _lease = await self._ready_generation()
|
|
314
|
+
return await host.inventory()
|
|
315
|
+
|
|
316
|
+
async def list_tools(
|
|
317
|
+
self,
|
|
318
|
+
*,
|
|
319
|
+
expected_descriptor_digest: str | None = None,
|
|
320
|
+
expected_generation_id: str | None = None,
|
|
321
|
+
) -> tuple[DshCapabilityTool, ...]:
|
|
322
|
+
self._validate_expected_generation(expected_descriptor_digest, expected_generation_id)
|
|
323
|
+
async with self._lock:
|
|
324
|
+
host, lease = await self._ensure_ready_locked()
|
|
325
|
+
self._require_expected_generation(
|
|
326
|
+
host.descriptor,
|
|
327
|
+
expected_descriptor_digest,
|
|
328
|
+
expected_generation_id,
|
|
329
|
+
)
|
|
330
|
+
return await self._list_tools_for_generation(host, lease)
|
|
331
|
+
|
|
332
|
+
async def _list_tools_for_generation(
|
|
333
|
+
self,
|
|
334
|
+
host: DshProfileCapabilityHost,
|
|
335
|
+
lease: DshMcpConnectorLease,
|
|
336
|
+
) -> tuple[DshCapabilityTool, ...]:
|
|
337
|
+
request_id = f"list-{os.urandom(12).hex()}"
|
|
338
|
+
result = await self._mcp_request(
|
|
339
|
+
lease,
|
|
340
|
+
request_id=request_id,
|
|
341
|
+
method="tools/list",
|
|
342
|
+
params={},
|
|
343
|
+
timeout_seconds=5.0,
|
|
344
|
+
)
|
|
345
|
+
raw_tools = result.get("tools")
|
|
346
|
+
if not isinstance(raw_tools, list):
|
|
347
|
+
raise self._protocol_error()
|
|
348
|
+
try:
|
|
349
|
+
tools = tuple(DshCapabilityTool.model_validate(item) for item in raw_tools)
|
|
350
|
+
except (TypeError, ValidationError, ValueError) as error:
|
|
351
|
+
raise self._protocol_error() from error
|
|
352
|
+
if tools != host.descriptor.tools:
|
|
353
|
+
raise StudioError(
|
|
354
|
+
"DSH_CAPABILITY_INVENTORY_CHANGED",
|
|
355
|
+
"DSH 工具清单已变化,请刷新插件 Profile 后重试",
|
|
356
|
+
status_code=409,
|
|
357
|
+
)
|
|
358
|
+
return tools
|
|
359
|
+
|
|
360
|
+
@staticmethod
|
|
361
|
+
def _validate_lease(
|
|
362
|
+
descriptor: DshProfileCapabilityDescriptor,
|
|
363
|
+
lease: DshMcpConnectorLease,
|
|
364
|
+
) -> None:
|
|
365
|
+
if (
|
|
366
|
+
lease.profile != descriptor.profile
|
|
367
|
+
or lease.profile_digest != descriptor.profile_digest
|
|
368
|
+
or lease.descriptor_digest != descriptor.descriptor_digest
|
|
369
|
+
):
|
|
370
|
+
raise StudioDshCapabilityService._protocol_error()
|
|
371
|
+
|
|
372
|
+
async def call_tool(
|
|
373
|
+
self,
|
|
374
|
+
*,
|
|
375
|
+
call_id: str,
|
|
376
|
+
tool_name: str,
|
|
377
|
+
arguments: Mapping[str, Any],
|
|
378
|
+
deadline_ms: int,
|
|
379
|
+
expected_descriptor_digest: str | None = None,
|
|
380
|
+
expected_generation_id: str | None = None,
|
|
381
|
+
) -> dict[str, Any]:
|
|
382
|
+
self._validate_expected_generation(expected_descriptor_digest, expected_generation_id)
|
|
383
|
+
if not _CALL_ID.fullmatch(call_id):
|
|
384
|
+
raise StudioError(
|
|
385
|
+
"DSH_CAPABILITY_CALL_ID_INVALID",
|
|
386
|
+
"DSH 工具调用标识无效",
|
|
387
|
+
status_code=422,
|
|
388
|
+
)
|
|
389
|
+
if not isinstance(tool_name, str) or not _TOOL_NAME.fullmatch(tool_name):
|
|
390
|
+
raise StudioError(
|
|
391
|
+
"DSH_CAPABILITY_TOOL_INVALID",
|
|
392
|
+
"DSH 工具名称无效",
|
|
393
|
+
status_code=422,
|
|
394
|
+
)
|
|
395
|
+
if not isinstance(arguments, Mapping):
|
|
396
|
+
raise StudioError(
|
|
397
|
+
"DSH_CAPABILITY_ARGUMENTS_INVALID",
|
|
398
|
+
"DSH 工具参数必须是 JSON 对象",
|
|
399
|
+
status_code=422,
|
|
400
|
+
)
|
|
401
|
+
detached_arguments = dict(arguments)
|
|
402
|
+
if _strict_json_size(detached_arguments) > self._max_argument_bytes:
|
|
403
|
+
raise StudioError(
|
|
404
|
+
"DSH_CAPABILITY_ARGUMENTS_TOO_LARGE",
|
|
405
|
+
"DSH 工具参数超过大小限制",
|
|
406
|
+
status_code=413,
|
|
407
|
+
)
|
|
408
|
+
if (
|
|
409
|
+
isinstance(deadline_ms, bool)
|
|
410
|
+
or not isinstance(deadline_ms, int)
|
|
411
|
+
or deadline_ms < 1
|
|
412
|
+
or deadline_ms > 120_000
|
|
413
|
+
):
|
|
414
|
+
raise StudioError(
|
|
415
|
+
"DSH_CAPABILITY_DEADLINE_INVALID",
|
|
416
|
+
"DSH 工具调用超时值无效",
|
|
417
|
+
status_code=422,
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
async with self._lock:
|
|
421
|
+
host, lease = await self._ensure_ready_locked()
|
|
422
|
+
self._require_expected_generation(
|
|
423
|
+
host.descriptor,
|
|
424
|
+
expected_descriptor_digest,
|
|
425
|
+
expected_generation_id,
|
|
426
|
+
)
|
|
427
|
+
if tool_name not in {tool.name for tool in host.descriptor.tools}:
|
|
428
|
+
raise StudioError(
|
|
429
|
+
"DSH_CAPABILITY_TOOL_FORBIDDEN",
|
|
430
|
+
"请求的 DSH 工具不在当前能力清单中",
|
|
431
|
+
status_code=403,
|
|
432
|
+
)
|
|
433
|
+
if call_id in self._active_calls:
|
|
434
|
+
raise StudioError(
|
|
435
|
+
"DSH_CAPABILITY_CALL_DUPLICATE",
|
|
436
|
+
"DSH 工具调用标识已在使用",
|
|
437
|
+
status_code=409,
|
|
438
|
+
)
|
|
439
|
+
self._active_calls[call_id] = _ActiveCall(lease=lease, tool_name=tool_name)
|
|
440
|
+
|
|
441
|
+
try:
|
|
442
|
+
try:
|
|
443
|
+
return await asyncio.wait_for(
|
|
444
|
+
self._mcp_request(
|
|
445
|
+
lease,
|
|
446
|
+
request_id=call_id,
|
|
447
|
+
method="tools/call",
|
|
448
|
+
params={"name": tool_name, "arguments": detached_arguments},
|
|
449
|
+
timeout_seconds=(deadline_ms / 1000.0) + 2.0,
|
|
450
|
+
),
|
|
451
|
+
timeout=(deadline_ms / 1000.0) + 2.0,
|
|
452
|
+
)
|
|
453
|
+
except asyncio.TimeoutError as error:
|
|
454
|
+
await self._send_cancel(lease, call_id)
|
|
455
|
+
raise StudioError(
|
|
456
|
+
"DSH_CAPABILITY_CALL_TIMEOUT",
|
|
457
|
+
"DSH 工具调用超时",
|
|
458
|
+
status_code=504,
|
|
459
|
+
) from error
|
|
460
|
+
except asyncio.CancelledError:
|
|
461
|
+
await self._send_cancel(lease, call_id)
|
|
462
|
+
raise
|
|
463
|
+
finally:
|
|
464
|
+
async with self._lock:
|
|
465
|
+
current = self._active_calls.get(call_id)
|
|
466
|
+
if current is not None and current.lease is lease:
|
|
467
|
+
self._active_calls.pop(call_id, None)
|
|
468
|
+
|
|
469
|
+
async def cancel(self, call_id: str) -> bool:
|
|
470
|
+
async with self._lock:
|
|
471
|
+
active = self._active_calls.get(call_id)
|
|
472
|
+
if active is None:
|
|
473
|
+
return False
|
|
474
|
+
await self._send_cancel(active.lease, call_id)
|
|
475
|
+
return True
|
|
476
|
+
|
|
477
|
+
async def revoke_runtime_token(
|
|
478
|
+
self,
|
|
479
|
+
lease: DshMcpConnectorLease,
|
|
480
|
+
token: str,
|
|
481
|
+
) -> None:
|
|
482
|
+
"""Revoke one activation-scoped token or kill its generation on failure."""
|
|
483
|
+
|
|
484
|
+
if not isinstance(token, str) or not token.startswith("ks1.") or len(token) > 12 * 1024:
|
|
485
|
+
raise StudioError(
|
|
486
|
+
"DSH_CAPABILITY_SCOPE_INVALID",
|
|
487
|
+
"DSH runtime credential 无效",
|
|
488
|
+
status_code=422,
|
|
489
|
+
)
|
|
490
|
+
async with self._lock:
|
|
491
|
+
if self._closed or self._lease is not lease:
|
|
492
|
+
# A replaced/disposed process has already invalidated its HMAC
|
|
493
|
+
# root, so no live credential remains to revoke.
|
|
494
|
+
return
|
|
495
|
+
try:
|
|
496
|
+
result = await self._mcp_request(
|
|
497
|
+
lease,
|
|
498
|
+
request_id=f"revoke-{os.urandom(12).hex()}",
|
|
499
|
+
method="io.ksadk/scopes/revoke",
|
|
500
|
+
params={"token": token},
|
|
501
|
+
timeout_seconds=2.0,
|
|
502
|
+
)
|
|
503
|
+
if result.get("revoked") is not True:
|
|
504
|
+
raise self._protocol_error()
|
|
505
|
+
except BaseException:
|
|
506
|
+
# Revocation is a security boundary. If it cannot be proven,
|
|
507
|
+
# terminating the generation invalidates every derived token.
|
|
508
|
+
await self._dispose_generation_locked()
|
|
509
|
+
raise
|
|
510
|
+
|
|
511
|
+
async def refresh(self) -> None:
|
|
512
|
+
"""Cancel the cached generation and allow the next call to start afresh."""
|
|
513
|
+
|
|
514
|
+
async with self._lock:
|
|
515
|
+
if self._closed:
|
|
516
|
+
return
|
|
517
|
+
await self._dispose_generation_locked()
|
|
518
|
+
|
|
519
|
+
async def aclose(self) -> None:
|
|
520
|
+
async with self._lock:
|
|
521
|
+
if self._closed:
|
|
522
|
+
return
|
|
523
|
+
self._closed = True
|
|
524
|
+
await self._dispose_generation_locked()
|
|
525
|
+
|
|
526
|
+
async def _ready_generation(
|
|
527
|
+
self,
|
|
528
|
+
) -> tuple[DshProfileCapabilityHost, DshMcpConnectorLease]:
|
|
529
|
+
async with self._lock:
|
|
530
|
+
return await self._ensure_ready_locked()
|
|
531
|
+
|
|
532
|
+
async def _ensure_ready_locked(
|
|
533
|
+
self,
|
|
534
|
+
) -> tuple[DshProfileCapabilityHost, DshMcpConnectorLease]:
|
|
535
|
+
if self._closed:
|
|
536
|
+
raise StudioError(
|
|
537
|
+
"DSH_CAPABILITY_SERVICE_CLOSED",
|
|
538
|
+
"DSH capability service 已关闭",
|
|
539
|
+
status_code=503,
|
|
540
|
+
)
|
|
541
|
+
host = self._host
|
|
542
|
+
projection = self._projection
|
|
543
|
+
if host is not None:
|
|
544
|
+
if projection is None: # pragma: no cover - internal invariant guard
|
|
545
|
+
await self._dispose_generation_locked()
|
|
546
|
+
raise self._protocol_error()
|
|
547
|
+
if self._lease is not None:
|
|
548
|
+
try:
|
|
549
|
+
if await host.health():
|
|
550
|
+
return host, self._lease
|
|
551
|
+
except (PluginHostError, OSError):
|
|
552
|
+
pass
|
|
553
|
+
self._lease = None
|
|
554
|
+
return await self._start_host_locked(host, projection)
|
|
555
|
+
|
|
556
|
+
try:
|
|
557
|
+
command = await asyncio.to_thread(self._resolve_command)
|
|
558
|
+
projection = await asyncio.to_thread(self._project_profile, command)
|
|
559
|
+
host = self._host_factory(
|
|
560
|
+
command,
|
|
561
|
+
projection=projection,
|
|
562
|
+
dsh_home=self._dsh_home,
|
|
563
|
+
cwd=self._workspace,
|
|
564
|
+
studio_index=(
|
|
565
|
+
Path(__file__).with_name("static") / "index.html"
|
|
566
|
+
if self._mount_studio_app
|
|
567
|
+
else None
|
|
568
|
+
),
|
|
569
|
+
studio_models=self.model_projection() if self.model_projection else None,
|
|
570
|
+
max_argument_bytes=self._max_argument_bytes,
|
|
571
|
+
max_result_bytes=self._max_result_bytes,
|
|
572
|
+
max_request_bytes=self._max_argument_bytes + 16 * 1024,
|
|
573
|
+
)
|
|
574
|
+
except StudioError:
|
|
575
|
+
raise
|
|
576
|
+
except (PluginHostError, OSError, ValueError) as error:
|
|
577
|
+
raise self._unavailable(error) from error
|
|
578
|
+
|
|
579
|
+
# Retain one Host across transient start failures. Its circuit breaker
|
|
580
|
+
# is generation state; replacing it here would make repeated crashes
|
|
581
|
+
# look like unrelated first failures and prevent the breaker opening.
|
|
582
|
+
self._projection = projection
|
|
583
|
+
self._host = host
|
|
584
|
+
return await self._start_host_locked(host, projection)
|
|
585
|
+
|
|
586
|
+
async def _start_host_locked(
|
|
587
|
+
self,
|
|
588
|
+
host: DshProfileCapabilityHost,
|
|
589
|
+
projection: DshProfileProjection,
|
|
590
|
+
) -> tuple[DshProfileCapabilityHost, DshMcpConnectorLease]:
|
|
591
|
+
# A restarted Core must never inherit workers or resource handles from
|
|
592
|
+
# the previous generation, including when the restart subsequently fails.
|
|
593
|
+
self._resource_generation_snapshot = None
|
|
594
|
+
await self._close_resource_supervisor_locked()
|
|
595
|
+
generation_id = f"dshgen_{secrets.token_urlsafe(24)}"
|
|
596
|
+
try:
|
|
597
|
+
if "@kingsoftcloud/dsh-platform-resources" in projection.bundles:
|
|
598
|
+
ledger = await asyncio.to_thread(
|
|
599
|
+
OperationLedger, self._workspace / ".agentkit" / "resource-operations"
|
|
600
|
+
)
|
|
601
|
+
self._resource_supervisor = ResourceSupervisor(
|
|
602
|
+
generation_id, operation_ledger=ledger,
|
|
603
|
+
)
|
|
604
|
+
socket_path = await self._resource_supervisor.start_broker()
|
|
605
|
+
await host.configure_resource_socket(socket_path)
|
|
606
|
+
lease = await host.lease()
|
|
607
|
+
descriptor = host.descriptor
|
|
608
|
+
if (
|
|
609
|
+
lease.profile != projection.profile
|
|
610
|
+
or lease.profile_digest != projection.config_digest
|
|
611
|
+
or lease.descriptor_digest != descriptor.descriptor_digest
|
|
612
|
+
):
|
|
613
|
+
raise self._protocol_error()
|
|
614
|
+
await self._initialize_lease(lease)
|
|
615
|
+
except StudioError as error:
|
|
616
|
+
self._lease = None
|
|
617
|
+
await self._close_resource_supervisor_locked()
|
|
618
|
+
if error.code == "DSH_CAPABILITY_PROTOCOL_INVALID":
|
|
619
|
+
await self._dispose_generation_locked()
|
|
620
|
+
raise
|
|
621
|
+
except (PluginHostError, OSError, ValueError) as error:
|
|
622
|
+
self._lease = None
|
|
623
|
+
await self._close_resource_supervisor_locked()
|
|
624
|
+
raise self._unavailable(error) from error
|
|
625
|
+
except BaseException:
|
|
626
|
+
self._lease = None
|
|
627
|
+
await self._close_resource_supervisor_locked()
|
|
628
|
+
raise
|
|
629
|
+
self._lease = lease
|
|
630
|
+
self._generation_id = generation_id
|
|
631
|
+
return host, lease
|
|
632
|
+
|
|
633
|
+
async def prepare_resource_generation(
|
|
634
|
+
self, expected: DshProfileBuildSnapshot
|
|
635
|
+
) -> tuple[DshProfileCapabilityDescriptor, str]:
|
|
636
|
+
"""Admit a cold Core or share an already attested, identical installation.
|
|
637
|
+
|
|
638
|
+
A same-Profile Core started for Studio UI contributions has no resource
|
|
639
|
+
Build owner yet. Restart it under the frozen snapshot so loading a client
|
|
640
|
+
plugin cannot make platform-resource Agents impossible to run. A generation
|
|
641
|
+
already owned by another resource Build is never replaced implicitly.
|
|
642
|
+
"""
|
|
643
|
+
expected = DshProfileBuildSnapshot.model_validate_json(expected.model_dump_json())
|
|
644
|
+
async with self._lock:
|
|
645
|
+
if (
|
|
646
|
+
self._host is not None and self._resource_generation_snapshot != expected
|
|
647
|
+
):
|
|
648
|
+
if (
|
|
649
|
+
self._resource_generation_snapshot is not None
|
|
650
|
+
or self._projection != expected.projection
|
|
651
|
+
):
|
|
652
|
+
raise StudioError(
|
|
653
|
+
"RESOURCE_PROFILE_IN_USE",
|
|
654
|
+
"当前 DSH generation 已由其他配置占用;请停止对应运行或选择独立 profile",
|
|
655
|
+
status_code=409,
|
|
656
|
+
)
|
|
657
|
+
if self._lease is not None:
|
|
658
|
+
# The live Core only serves the exact same Profile projection
|
|
659
|
+
# and has not activated resources. Replace it so the process
|
|
660
|
+
# itself starts between the two immutable-installation checks
|
|
661
|
+
# below. A Host retained after a failed start has no live
|
|
662
|
+
# generation and remains the circuit-breaker owner for retry.
|
|
663
|
+
await self._dispose_generation_locked()
|
|
664
|
+
command = await asyncio.to_thread(self._resolve_command)
|
|
665
|
+
try:
|
|
666
|
+
await asyncio.to_thread(self._verify_resource_build_snapshot, command, expected)
|
|
667
|
+
except BaseException:
|
|
668
|
+
await self._dispose_generation_locked()
|
|
669
|
+
raise
|
|
670
|
+
host, _ = await self._ensure_ready_locked()
|
|
671
|
+
try:
|
|
672
|
+
if self._projection != expected.projection:
|
|
673
|
+
raise StudioError(
|
|
674
|
+
"RESOURCE_BUILD_PROFILE_MISMATCH",
|
|
675
|
+
"DSH Core 与 Build 配置不匹配",
|
|
676
|
+
status_code=409,
|
|
677
|
+
)
|
|
678
|
+
await asyncio.to_thread(self._verify_resource_build_snapshot, command, expected)
|
|
679
|
+
except BaseException:
|
|
680
|
+
await self._dispose_generation_locked()
|
|
681
|
+
raise
|
|
682
|
+
self._resource_generation_snapshot = expected
|
|
683
|
+
return host.descriptor, self._require_generation_id()
|
|
684
|
+
|
|
685
|
+
async def activate_resources(
|
|
686
|
+
self, initialization: WorkerInitialization, *, expected: DshProfileBuildSnapshot,
|
|
687
|
+
write_authorizer: ResourceWriteAuthorizer | None = None,
|
|
688
|
+
) -> ActiveResources:
|
|
689
|
+
"""Trusted execution adapter entry after Build and upstream admission.
|
|
690
|
+
|
|
691
|
+
This does not install bundles or expose a resource MCP token. It owns
|
|
692
|
+
worker lifetime alongside the matching existing Core generation.
|
|
693
|
+
"""
|
|
694
|
+
initialization = WorkerInitialization.model_validate(initialization.pipe_payload())
|
|
695
|
+
async with self._lock:
|
|
696
|
+
if self._resource_generation_snapshot != expected:
|
|
697
|
+
raise StudioError(
|
|
698
|
+
"RESOURCE_BUILD_NOT_PREPARED",
|
|
699
|
+
"资源 Build 尚未准备对应的 DSH generation",
|
|
700
|
+
status_code=409,
|
|
701
|
+
)
|
|
702
|
+
if initialization.resource_snapshot.dsh_profile != expected:
|
|
703
|
+
raise StudioError(
|
|
704
|
+
"RESOURCE_BUILD_PROFILE_MISMATCH",
|
|
705
|
+
"资源 Build 未锁定当前 DSH 安装快照,请重新构建",
|
|
706
|
+
status_code=409,
|
|
707
|
+
)
|
|
708
|
+
command = await asyncio.to_thread(self._resolve_command)
|
|
709
|
+
try:
|
|
710
|
+
await asyncio.to_thread(self._verify_resource_build_snapshot, command, expected)
|
|
711
|
+
except BaseException:
|
|
712
|
+
await self._dispose_generation_locked()
|
|
713
|
+
raise
|
|
714
|
+
_, lease = await self._ensure_ready_locked()
|
|
715
|
+
if self._resource_generation_snapshot != expected:
|
|
716
|
+
raise StudioError(
|
|
717
|
+
"RESOURCE_BUILD_NOT_PREPARED",
|
|
718
|
+
"DSH 重启后需要重新准备资源 Build",
|
|
719
|
+
status_code=409,
|
|
720
|
+
)
|
|
721
|
+
first = initialization.scopes[0]
|
|
722
|
+
if (
|
|
723
|
+
first.generation_id != self._generation_id
|
|
724
|
+
or first.profile_digest != lease.profile_digest
|
|
725
|
+
):
|
|
726
|
+
raise StudioError(
|
|
727
|
+
"RESOURCE_BUILD_PROFILE_MISMATCH",
|
|
728
|
+
"资源运行实例与当前 DSH profile/generation 不匹配",
|
|
729
|
+
status_code=409,
|
|
730
|
+
)
|
|
731
|
+
if self._resource_supervisor is None:
|
|
732
|
+
ledger = await asyncio.to_thread(
|
|
733
|
+
OperationLedger, self._workspace / ".agentkit" / "resource-operations"
|
|
734
|
+
)
|
|
735
|
+
self._resource_supervisor = ResourceSupervisor(
|
|
736
|
+
first.generation_id, operation_ledger=ledger,
|
|
737
|
+
)
|
|
738
|
+
return await self._resource_supervisor.activate(
|
|
739
|
+
initialization, write_authorizer=write_authorizer,
|
|
740
|
+
)
|
|
741
|
+
|
|
742
|
+
async def deactivate_resources(self, activation_id: str) -> None:
|
|
743
|
+
async with self._lock:
|
|
744
|
+
if self._resource_supervisor is not None:
|
|
745
|
+
await self._resource_supervisor.deactivate(activation_id)
|
|
746
|
+
|
|
747
|
+
async def resource_connector_lease(
|
|
748
|
+
self,
|
|
749
|
+
current: ActiveResources,
|
|
750
|
+
) -> DshMcpConnectorLease:
|
|
751
|
+
"""Return the matching Core lease only while the worker is still live."""
|
|
752
|
+
|
|
753
|
+
async with self._lock:
|
|
754
|
+
_host, lease = await self._ensure_ready_locked()
|
|
755
|
+
supervisor = self._resource_supervisor
|
|
756
|
+
if (
|
|
757
|
+
supervisor is None
|
|
758
|
+
or not current.leases
|
|
759
|
+
or current.leases[0].scope.generation_id != self._generation_id
|
|
760
|
+
or current.leases[0].scope.profile_digest != lease.profile_digest
|
|
761
|
+
or not await supervisor.owns(current)
|
|
762
|
+
):
|
|
763
|
+
raise StudioError(
|
|
764
|
+
"RESOURCE_ACTIVATION_STALE",
|
|
765
|
+
"资源 worker 已失效,需要重新激活",
|
|
766
|
+
status_code=409,
|
|
767
|
+
)
|
|
768
|
+
return lease
|
|
769
|
+
|
|
770
|
+
async def renew_resources(
|
|
771
|
+
self, current: ActiveResources, *, expected: DshProfileBuildSnapshot,
|
|
772
|
+
revalidate: ResourceRevalidator,
|
|
773
|
+
) -> ActiveResources:
|
|
774
|
+
async with self._lock:
|
|
775
|
+
if self._resource_supervisor is None or self._resource_generation_snapshot != expected:
|
|
776
|
+
raise StudioError(
|
|
777
|
+
"RESOURCE_BUILD_NOT_PREPARED", "资源 Build 尚未准备", status_code=409,
|
|
778
|
+
)
|
|
779
|
+
supervisor = self._resource_supervisor
|
|
780
|
+
try:
|
|
781
|
+
command = await asyncio.to_thread(self._resolve_command)
|
|
782
|
+
await asyncio.to_thread(self._verify_resource_build_snapshot, command, expected)
|
|
783
|
+
except BaseException:
|
|
784
|
+
async with self._lock:
|
|
785
|
+
if self._resource_supervisor is supervisor:
|
|
786
|
+
await self._dispose_generation_locked()
|
|
787
|
+
raise
|
|
788
|
+
# Platform checks cannot hold the Studio lock and delay stop/revocation.
|
|
789
|
+
return await supervisor.renew(current, revalidate=revalidate)
|
|
790
|
+
|
|
791
|
+
async def resource_runtime_status(self, *, agent_id: str, activation_id: str) -> dict | None:
|
|
792
|
+
# Observations never start a Core or replace a generation.
|
|
793
|
+
async with self._lock:
|
|
794
|
+
if self._resource_supervisor is None:
|
|
795
|
+
return None
|
|
796
|
+
return await self._resource_supervisor.runtime_status(
|
|
797
|
+
agent_id=agent_id, activation_id=activation_id,
|
|
798
|
+
)
|
|
799
|
+
|
|
800
|
+
async def _close_resource_supervisor_locked(self) -> None:
|
|
801
|
+
supervisor = self._resource_supervisor
|
|
802
|
+
self._resource_supervisor = None
|
|
803
|
+
if supervisor is not None:
|
|
804
|
+
await self._finish_cleanup(supervisor.aclose())
|
|
805
|
+
|
|
806
|
+
def _resolve_command(self) -> tuple[str, ...]:
|
|
807
|
+
try:
|
|
808
|
+
return tuple(DshToolchainManager().require_command(self._explicit_dsh_executable))
|
|
809
|
+
except Exception as error:
|
|
810
|
+
raise StudioError(
|
|
811
|
+
"DSH_CAPABILITY_HOST_UNAVAILABLE",
|
|
812
|
+
"DSH capability host 未安装、版本不匹配或不可用",
|
|
813
|
+
status_code=503,
|
|
814
|
+
) from error
|
|
815
|
+
|
|
816
|
+
def _project_profile(self, command: Sequence[str]) -> DshProfileProjection:
|
|
817
|
+
try:
|
|
818
|
+
with self._bridge_factory(
|
|
819
|
+
dsh_home=self._dsh_home,
|
|
820
|
+
profile=self._profile,
|
|
821
|
+
dsh_command=command,
|
|
822
|
+
cwd=self._workspace,
|
|
823
|
+
) as bridge:
|
|
824
|
+
return bridge.project_profile()
|
|
825
|
+
except Exception as error:
|
|
826
|
+
raise self._unavailable(error) from error
|
|
827
|
+
|
|
828
|
+
def _profile_has_enabled_plugins(self, command: Sequence[str]) -> bool:
|
|
829
|
+
try:
|
|
830
|
+
with self._bridge_factory(
|
|
831
|
+
dsh_home=self._dsh_home,
|
|
832
|
+
profile=self._profile,
|
|
833
|
+
dsh_command=command,
|
|
834
|
+
cwd=self._workspace,
|
|
835
|
+
) as bridge:
|
|
836
|
+
return any(item.enabled for item in bridge.list_plugins())
|
|
837
|
+
except Exception as error:
|
|
838
|
+
raise self._unavailable(error) from error
|
|
839
|
+
|
|
840
|
+
def _verify_resource_build_snapshot(
|
|
841
|
+
self, command: Sequence[str], expected: DshProfileBuildSnapshot
|
|
842
|
+
) -> None:
|
|
843
|
+
try:
|
|
844
|
+
with self._bridge_factory(
|
|
845
|
+
dsh_home=self._dsh_home,
|
|
846
|
+
profile=self._profile,
|
|
847
|
+
dsh_command=command,
|
|
848
|
+
cwd=self._workspace,
|
|
849
|
+
) as bridge:
|
|
850
|
+
bridge.verify_build_snapshot(expected)
|
|
851
|
+
except Exception as error:
|
|
852
|
+
raise StudioError(
|
|
853
|
+
"RESOURCE_BUILD_INSTALLATION_MISMATCH",
|
|
854
|
+
"DSH 已安装内容与资源 Build 不一致",
|
|
855
|
+
status_code=409,
|
|
856
|
+
) from error
|
|
857
|
+
|
|
858
|
+
@staticmethod
|
|
859
|
+
def _validate_expected_generation(
|
|
860
|
+
descriptor_digest: str | None,
|
|
861
|
+
generation_id: str | None,
|
|
862
|
+
) -> None:
|
|
863
|
+
if descriptor_digest is not None and (
|
|
864
|
+
not isinstance(descriptor_digest, str) or not _DIGEST.fullmatch(descriptor_digest)
|
|
865
|
+
):
|
|
866
|
+
raise StudioError(
|
|
867
|
+
"DSH_CAPABILITY_DESCRIPTOR_INVALID",
|
|
868
|
+
"DSH capability descriptor 摘要无效",
|
|
869
|
+
status_code=422,
|
|
870
|
+
)
|
|
871
|
+
if generation_id is not None and (
|
|
872
|
+
not isinstance(generation_id, str) or not _GENERATION_ID.fullmatch(generation_id)
|
|
873
|
+
):
|
|
874
|
+
raise StudioError(
|
|
875
|
+
"DSH_CAPABILITY_GENERATION_INVALID",
|
|
876
|
+
"DSH capability generation 标识无效",
|
|
877
|
+
status_code=422,
|
|
878
|
+
)
|
|
879
|
+
|
|
880
|
+
def _require_expected_generation(
|
|
881
|
+
self,
|
|
882
|
+
descriptor: DshProfileCapabilityDescriptor,
|
|
883
|
+
expected_descriptor_digest: str | None,
|
|
884
|
+
expected_generation_id: str | None,
|
|
885
|
+
) -> None:
|
|
886
|
+
if (
|
|
887
|
+
expected_descriptor_digest is not None
|
|
888
|
+
and descriptor.descriptor_digest != expected_descriptor_digest
|
|
889
|
+
) or (expected_generation_id is not None and expected_generation_id != self._generation_id):
|
|
890
|
+
raise StudioError(
|
|
891
|
+
"DSH_CAPABILITY_GENERATION_CHANGED",
|
|
892
|
+
"DSH capability generation 已变化,请刷新后重试",
|
|
893
|
+
status_code=409,
|
|
894
|
+
)
|
|
895
|
+
|
|
896
|
+
def _require_generation_id(self) -> str:
|
|
897
|
+
generation_id = self._generation_id
|
|
898
|
+
if generation_id is None: # pragma: no cover - internal invariant guard
|
|
899
|
+
raise self._protocol_error()
|
|
900
|
+
return generation_id
|
|
901
|
+
|
|
902
|
+
async def _dispose_generation_locked(self) -> None:
|
|
903
|
+
await self._finish_cleanup(self._dispose_generation_owned_locked())
|
|
904
|
+
|
|
905
|
+
async def _dispose_generation_owned_locked(self) -> None:
|
|
906
|
+
"""Finish generation teardown before propagating caller cancellation."""
|
|
907
|
+
|
|
908
|
+
host = self._host
|
|
909
|
+
active = tuple(self._active_calls.items())
|
|
910
|
+
self._host = None
|
|
911
|
+
self._lease = None
|
|
912
|
+
self._projection = None
|
|
913
|
+
self._resource_generation_snapshot = None
|
|
914
|
+
self._generation_id = None
|
|
915
|
+
self._active_calls.clear()
|
|
916
|
+
try:
|
|
917
|
+
await self._close_resource_supervisor_locked()
|
|
918
|
+
finally:
|
|
919
|
+
if active:
|
|
920
|
+
await asyncio.gather(
|
|
921
|
+
*(self._send_cancel(call.lease, call_id) for call_id, call in active),
|
|
922
|
+
return_exceptions=True,
|
|
923
|
+
)
|
|
924
|
+
if host is not None:
|
|
925
|
+
await host.dispose()
|
|
926
|
+
|
|
927
|
+
@staticmethod
|
|
928
|
+
async def _finish_cleanup(cleanup: Coroutine[Any, Any, None]) -> None:
|
|
929
|
+
"""Defer caller cancellation until an owned-effect cleanup finishes."""
|
|
930
|
+
|
|
931
|
+
cleanup_task = asyncio.create_task(cleanup)
|
|
932
|
+
interrupted = False
|
|
933
|
+
while not cleanup_task.done():
|
|
934
|
+
try:
|
|
935
|
+
await asyncio.shield(cleanup_task)
|
|
936
|
+
except asyncio.CancelledError:
|
|
937
|
+
interrupted = True
|
|
938
|
+
cleanup_task.result()
|
|
939
|
+
if interrupted:
|
|
940
|
+
raise asyncio.CancelledError
|
|
941
|
+
|
|
942
|
+
async def _mcp_request(
|
|
943
|
+
self,
|
|
944
|
+
lease: DshMcpConnectorLease,
|
|
945
|
+
*,
|
|
946
|
+
request_id: str,
|
|
947
|
+
method: Literal[
|
|
948
|
+
"initialize",
|
|
949
|
+
"io.ksadk/scopes/revoke",
|
|
950
|
+
"tools/list",
|
|
951
|
+
"tools/call",
|
|
952
|
+
],
|
|
953
|
+
params: Mapping[str, Any],
|
|
954
|
+
timeout_seconds: float,
|
|
955
|
+
) -> dict[str, Any]:
|
|
956
|
+
payload = {
|
|
957
|
+
"jsonrpc": "2.0",
|
|
958
|
+
"id": request_id,
|
|
959
|
+
"method": method,
|
|
960
|
+
"params": dict(params),
|
|
961
|
+
}
|
|
962
|
+
headers = {**lease.headers(), "Content-Type": "application/json"}
|
|
963
|
+
try:
|
|
964
|
+
async with httpx.AsyncClient(
|
|
965
|
+
headers=headers,
|
|
966
|
+
timeout=timeout_seconds,
|
|
967
|
+
trust_env=False,
|
|
968
|
+
) as client:
|
|
969
|
+
response = await client.post(lease.endpoint, json=payload)
|
|
970
|
+
except httpx.TimeoutException as error:
|
|
971
|
+
raise asyncio.TimeoutError from error
|
|
972
|
+
except httpx.HTTPError as error:
|
|
973
|
+
raise StudioError(
|
|
974
|
+
"DSH_CAPABILITY_HOST_UNAVAILABLE",
|
|
975
|
+
"DSH capability MCP 连接失败",
|
|
976
|
+
status_code=503,
|
|
977
|
+
details={"errorType": type(error).__name__},
|
|
978
|
+
) from error
|
|
979
|
+
if response.status_code != 200 or len(response.content) > self._max_wire_response_bytes:
|
|
980
|
+
raise self._protocol_error()
|
|
981
|
+
try:
|
|
982
|
+
envelope = json.loads(
|
|
983
|
+
response.content,
|
|
984
|
+
object_pairs_hook=_duplicate_rejecting_object,
|
|
985
|
+
parse_constant=_reject_non_finite,
|
|
986
|
+
)
|
|
987
|
+
except (UnicodeError, ValueError, TypeError, RecursionError) as error:
|
|
988
|
+
raise self._protocol_error() from error
|
|
989
|
+
if (
|
|
990
|
+
not isinstance(envelope, dict)
|
|
991
|
+
or envelope.get("jsonrpc") != "2.0"
|
|
992
|
+
or envelope.get("id") != request_id
|
|
993
|
+
):
|
|
994
|
+
raise self._protocol_error()
|
|
995
|
+
if "error" in envelope:
|
|
996
|
+
rpc_error = envelope.get("error")
|
|
997
|
+
code = rpc_error.get("code") if isinstance(rpc_error, dict) else None
|
|
998
|
+
raise StudioError(
|
|
999
|
+
"DSH_CAPABILITY_REQUEST_FAILED",
|
|
1000
|
+
"DSH capability MCP 拒绝了请求",
|
|
1001
|
+
status_code=502,
|
|
1002
|
+
details={"rpcCode": code if isinstance(code, int) else None},
|
|
1003
|
+
)
|
|
1004
|
+
result = envelope.get("result")
|
|
1005
|
+
if not isinstance(result, dict):
|
|
1006
|
+
raise self._protocol_error()
|
|
1007
|
+
try:
|
|
1008
|
+
result_bytes = len(
|
|
1009
|
+
json.dumps(
|
|
1010
|
+
result,
|
|
1011
|
+
allow_nan=False,
|
|
1012
|
+
ensure_ascii=False,
|
|
1013
|
+
separators=(",", ":"),
|
|
1014
|
+
).encode("utf-8")
|
|
1015
|
+
)
|
|
1016
|
+
except (TypeError, ValueError, RecursionError) as error:
|
|
1017
|
+
raise self._protocol_error() from error
|
|
1018
|
+
if result_bytes > self._max_result_bytes:
|
|
1019
|
+
raise self._protocol_error()
|
|
1020
|
+
return cast(dict[str, Any], result)
|
|
1021
|
+
|
|
1022
|
+
async def _initialize_lease(self, lease: DshMcpConnectorLease) -> None:
|
|
1023
|
+
request_id = f"initialize-{os.urandom(12).hex()}"
|
|
1024
|
+
result = await self._mcp_request(
|
|
1025
|
+
lease,
|
|
1026
|
+
request_id=request_id,
|
|
1027
|
+
method="initialize",
|
|
1028
|
+
params={
|
|
1029
|
+
"protocolVersion": DSH_CAPABILITY_MCP_PROTOCOL,
|
|
1030
|
+
"capabilities": {},
|
|
1031
|
+
"clientInfo": {"name": "ksadk-studio", "version": "1.0.0"},
|
|
1032
|
+
},
|
|
1033
|
+
timeout_seconds=5.0,
|
|
1034
|
+
)
|
|
1035
|
+
server_info = result.get("serverInfo")
|
|
1036
|
+
capabilities = result.get("capabilities")
|
|
1037
|
+
if (
|
|
1038
|
+
result.get("protocolVersion") != DSH_CAPABILITY_MCP_PROTOCOL
|
|
1039
|
+
or not isinstance(server_info, dict)
|
|
1040
|
+
or server_info.get("name") != DSH_CAPABILITY_BUNDLE_PACKAGE
|
|
1041
|
+
or server_info.get("version") != DSH_CAPABILITY_HOST_VERSION
|
|
1042
|
+
or not isinstance(capabilities, dict)
|
|
1043
|
+
or not isinstance(capabilities.get("tools"), dict)
|
|
1044
|
+
):
|
|
1045
|
+
raise self._protocol_error()
|
|
1046
|
+
await self._send_notification(
|
|
1047
|
+
lease,
|
|
1048
|
+
method="notifications/initialized",
|
|
1049
|
+
params={},
|
|
1050
|
+
tolerate_failure=False,
|
|
1051
|
+
)
|
|
1052
|
+
|
|
1053
|
+
async def _send_cancel(self, lease: DshMcpConnectorLease, call_id: str) -> None:
|
|
1054
|
+
await self._send_notification(
|
|
1055
|
+
lease,
|
|
1056
|
+
method="notifications/cancelled",
|
|
1057
|
+
params={"requestId": call_id, "reason": "Studio UI cancelled the call"},
|
|
1058
|
+
tolerate_failure=True,
|
|
1059
|
+
)
|
|
1060
|
+
|
|
1061
|
+
async def _send_notification(
|
|
1062
|
+
self,
|
|
1063
|
+
lease: DshMcpConnectorLease,
|
|
1064
|
+
*,
|
|
1065
|
+
method: Literal["notifications/initialized", "notifications/cancelled"],
|
|
1066
|
+
params: Mapping[str, Any],
|
|
1067
|
+
tolerate_failure: bool,
|
|
1068
|
+
) -> None:
|
|
1069
|
+
headers = {**lease.headers(), "Content-Type": "application/json"}
|
|
1070
|
+
payload = {"jsonrpc": "2.0", "method": method, "params": dict(params)}
|
|
1071
|
+
try:
|
|
1072
|
+
async with httpx.AsyncClient(headers=headers, timeout=2.0, trust_env=False) as client:
|
|
1073
|
+
response = await client.post(lease.endpoint, json=payload)
|
|
1074
|
+
if response.status_code not in {200, 202, 204}:
|
|
1075
|
+
raise self._protocol_error()
|
|
1076
|
+
except (httpx.HTTPError, StudioError):
|
|
1077
|
+
# Disposal still kills the process and aborts all work. Cancellation
|
|
1078
|
+
# is best effort here so refresh/close cannot be held hostage by a
|
|
1079
|
+
# broken loopback listener.
|
|
1080
|
+
if tolerate_failure:
|
|
1081
|
+
return
|
|
1082
|
+
raise self._protocol_error() from None
|
|
1083
|
+
|
|
1084
|
+
@staticmethod
|
|
1085
|
+
def _protocol_error() -> StudioError:
|
|
1086
|
+
return StudioError(
|
|
1087
|
+
"DSH_CAPABILITY_PROTOCOL_INVALID",
|
|
1088
|
+
"DSH capability MCP 返回了无效响应",
|
|
1089
|
+
status_code=502,
|
|
1090
|
+
)
|
|
1091
|
+
|
|
1092
|
+
@staticmethod
|
|
1093
|
+
def _unavailable(error: BaseException) -> StudioError:
|
|
1094
|
+
reason = getattr(error, "code", None)
|
|
1095
|
+
return StudioError(
|
|
1096
|
+
"DSH_CAPABILITY_HOST_UNAVAILABLE",
|
|
1097
|
+
"DSH capability host 当前不可用",
|
|
1098
|
+
status_code=503,
|
|
1099
|
+
details={"reason": reason} if isinstance(reason, str) else {},
|
|
1100
|
+
)
|
|
1101
|
+
|
|
1102
|
+
|
|
1103
|
+
__all__ = [
|
|
1104
|
+
"DshCapabilityRuntimeSnapshot",
|
|
1105
|
+
"DshCapabilitySnapshot",
|
|
1106
|
+
"StudioDshCapabilityService",
|
|
1107
|
+
]
|