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,333 @@
|
|
|
1
|
+
"""Frozen SubagentProvider/v1 seam with explicit lineage and policy bounds.
|
|
2
|
+
|
|
3
|
+
The Kernel does not know whether a child is implemented by Codex, DSH,
|
|
4
|
+
Claude, another KsADK AgentProvider, or a remote service. It only routes a
|
|
5
|
+
bounded request to a named provider and retains the returned child identity.
|
|
6
|
+
Provider-private state and event logs remain with that provider; projected
|
|
7
|
+
child events keep their own ids and sequence numbers.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from collections.abc import AsyncIterator, Mapping, Sequence
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from typing import Any, Literal, Protocol, cast, runtime_checkable
|
|
15
|
+
|
|
16
|
+
from pydantic import Field, field_validator, model_validator
|
|
17
|
+
|
|
18
|
+
from ksadk.plugins.contracts import PluginContractModel, PluginReference
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SubagentPolicy(PluginContractModel):
|
|
22
|
+
"""Parent-owned limits that a child provider may only narrow."""
|
|
23
|
+
|
|
24
|
+
max_depth: int = Field(default=1, ge=1, le=16)
|
|
25
|
+
timeout_seconds: int = Field(default=120, ge=1, le=86_400)
|
|
26
|
+
max_steps: int = Field(default=12, ge=1, le=10_000)
|
|
27
|
+
background: bool = False
|
|
28
|
+
allowed_tools: tuple[str, ...] = ()
|
|
29
|
+
allowed_permissions: tuple[str, ...] = ()
|
|
30
|
+
|
|
31
|
+
@field_validator("allowed_tools", "allowed_permissions")
|
|
32
|
+
@classmethod
|
|
33
|
+
def validate_unique_values(cls, value: tuple[str, ...]) -> tuple[str, ...]:
|
|
34
|
+
if any(not item.strip() for item in value):
|
|
35
|
+
raise ValueError("subagent policy entries must be non-empty")
|
|
36
|
+
if len(value) != len(set(value)):
|
|
37
|
+
raise ValueError("subagent policy entries must be unique")
|
|
38
|
+
return tuple(sorted(value))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class SpawnSubagentRequest(PluginContractModel):
|
|
42
|
+
"""One bounded child task requested by an authenticated parent Run."""
|
|
43
|
+
|
|
44
|
+
request_format: Literal["ksadk.subagent-spawn/v1"] = "ksadk.subagent-spawn/v1"
|
|
45
|
+
provider_ref: str
|
|
46
|
+
parent_session_id: str = Field(min_length=1, max_length=256)
|
|
47
|
+
parent_run_id: str = Field(min_length=1, max_length=256)
|
|
48
|
+
task: str = Field(min_length=1, max_length=131_072)
|
|
49
|
+
depth: int = Field(default=1, ge=1, le=16)
|
|
50
|
+
policy: SubagentPolicy = Field(default_factory=SubagentPolicy)
|
|
51
|
+
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
52
|
+
|
|
53
|
+
@field_validator("provider_ref")
|
|
54
|
+
@classmethod
|
|
55
|
+
def validate_provider_ref(cls, value: str) -> str:
|
|
56
|
+
return cast(str, PluginReference(ref=value).ref)
|
|
57
|
+
|
|
58
|
+
@field_validator("metadata")
|
|
59
|
+
@classmethod
|
|
60
|
+
def validate_metadata(cls, value: dict[str, Any]) -> dict[str, Any]:
|
|
61
|
+
_reject_secret_values(value)
|
|
62
|
+
return value
|
|
63
|
+
|
|
64
|
+
@model_validator(mode="after")
|
|
65
|
+
def validate_depth(self) -> "SpawnSubagentRequest":
|
|
66
|
+
if self.depth > self.policy.max_depth:
|
|
67
|
+
raise ValueError("subagent depth exceeds the parent policy maxDepth")
|
|
68
|
+
return self
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class ChildHandle(PluginContractModel):
|
|
72
|
+
"""Stable lineage and recovery descriptor for one provider-owned child."""
|
|
73
|
+
|
|
74
|
+
handle_format: Literal["ksadk.child-handle/v1"] = "ksadk.child-handle/v1"
|
|
75
|
+
handle_id: str = Field(min_length=1, max_length=256)
|
|
76
|
+
provider_ref: str
|
|
77
|
+
parent_session_id: str = Field(min_length=1, max_length=256)
|
|
78
|
+
parent_run_id: str = Field(min_length=1, max_length=256)
|
|
79
|
+
child_session_id: str | None = Field(default=None, max_length=256)
|
|
80
|
+
child_run_id: str | None = Field(default=None, max_length=256)
|
|
81
|
+
depth: int = Field(ge=1, le=16)
|
|
82
|
+
capabilities: tuple[str, ...] = ()
|
|
83
|
+
capability_digest: str = Field(pattern=r"^sha256:[0-9a-f]{64}$")
|
|
84
|
+
created_at: datetime
|
|
85
|
+
resumable: bool = False
|
|
86
|
+
resume_descriptor: dict[str, Any] | None = None
|
|
87
|
+
|
|
88
|
+
@field_validator("provider_ref")
|
|
89
|
+
@classmethod
|
|
90
|
+
def validate_provider_ref(cls, value: str) -> str:
|
|
91
|
+
return cast(str, PluginReference(ref=value).ref)
|
|
92
|
+
|
|
93
|
+
@field_validator("capabilities")
|
|
94
|
+
@classmethod
|
|
95
|
+
def validate_capabilities(cls, value: tuple[str, ...]) -> tuple[str, ...]:
|
|
96
|
+
if any(not item.strip() for item in value):
|
|
97
|
+
raise ValueError("child capabilities must be non-empty")
|
|
98
|
+
if len(value) != len(set(value)):
|
|
99
|
+
raise ValueError("child capabilities must be unique")
|
|
100
|
+
return tuple(sorted(value))
|
|
101
|
+
|
|
102
|
+
@field_validator("created_at")
|
|
103
|
+
@classmethod
|
|
104
|
+
def validate_created_at(cls, value: datetime) -> datetime:
|
|
105
|
+
if value.tzinfo is None or value.utcoffset() is None:
|
|
106
|
+
raise ValueError("child handle createdAt must include a timezone")
|
|
107
|
+
return value
|
|
108
|
+
|
|
109
|
+
@field_validator("resume_descriptor")
|
|
110
|
+
@classmethod
|
|
111
|
+
def validate_resume_descriptor(
|
|
112
|
+
cls, value: dict[str, Any] | None
|
|
113
|
+
) -> dict[str, Any] | None:
|
|
114
|
+
if value is not None:
|
|
115
|
+
_reject_secret_values(value)
|
|
116
|
+
return value
|
|
117
|
+
|
|
118
|
+
@model_validator(mode="after")
|
|
119
|
+
def validate_recovery_shape(self) -> "ChildHandle":
|
|
120
|
+
if self.resumable and not self.resume_descriptor:
|
|
121
|
+
raise ValueError("resumable child handle requires a resumeDescriptor")
|
|
122
|
+
if not self.resumable and self.resume_descriptor is not None:
|
|
123
|
+
raise ValueError("non-resumable child handle cannot carry a resumeDescriptor")
|
|
124
|
+
return self
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class SubagentStatus(PluginContractModel):
|
|
128
|
+
status_format: Literal["ksadk.subagent-status/v1"] = "ksadk.subagent-status/v1"
|
|
129
|
+
handle_id: str = Field(min_length=1, max_length=256)
|
|
130
|
+
state: Literal[
|
|
131
|
+
"accepted",
|
|
132
|
+
"running",
|
|
133
|
+
"waiting_input",
|
|
134
|
+
"succeeded",
|
|
135
|
+
"failed",
|
|
136
|
+
"cancelled",
|
|
137
|
+
"interrupted",
|
|
138
|
+
"disposed",
|
|
139
|
+
]
|
|
140
|
+
last_seq: int = Field(default=0, ge=0)
|
|
141
|
+
updated_at: datetime
|
|
142
|
+
reason: str | None = Field(default=None, max_length=2048)
|
|
143
|
+
|
|
144
|
+
@field_validator("updated_at")
|
|
145
|
+
@classmethod
|
|
146
|
+
def validate_updated_at(cls, value: datetime) -> datetime:
|
|
147
|
+
if value.tzinfo is None or value.utcoffset() is None:
|
|
148
|
+
raise ValueError("subagent status updatedAt must include a timezone")
|
|
149
|
+
return value
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class SubagentEvent(PluginContractModel):
|
|
153
|
+
"""Identity-preserving child event; parent projection must not merge by text."""
|
|
154
|
+
|
|
155
|
+
event_format: Literal["ksadk.subagent-event/v1"] = "ksadk.subagent-event/v1"
|
|
156
|
+
handle_id: str = Field(min_length=1, max_length=256)
|
|
157
|
+
event_id: str = Field(min_length=1, max_length=256)
|
|
158
|
+
seq: int = Field(ge=1)
|
|
159
|
+
kind: Literal["progress", "item", "interaction", "terminal"]
|
|
160
|
+
payload: dict[str, Any] = Field(default_factory=dict)
|
|
161
|
+
native_ref: dict[str, str] = Field(default_factory=dict)
|
|
162
|
+
|
|
163
|
+
@field_validator("payload")
|
|
164
|
+
@classmethod
|
|
165
|
+
def validate_payload(cls, value: dict[str, Any]) -> dict[str, Any]:
|
|
166
|
+
_reject_secret_values(value)
|
|
167
|
+
return value
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class SubagentResult(PluginContractModel):
|
|
171
|
+
result_format: Literal["ksadk.subagent-result/v1"] = "ksadk.subagent-result/v1"
|
|
172
|
+
handle_id: str = Field(min_length=1, max_length=256)
|
|
173
|
+
state: Literal["succeeded", "failed", "cancelled", "interrupted"]
|
|
174
|
+
output: Any = None
|
|
175
|
+
output_refs: tuple[str, ...] = ()
|
|
176
|
+
error_code: str | None = Field(default=None, max_length=256)
|
|
177
|
+
error_message: str | None = Field(default=None, max_length=2048)
|
|
178
|
+
|
|
179
|
+
@field_validator("output_refs")
|
|
180
|
+
@classmethod
|
|
181
|
+
def validate_output_refs(cls, value: tuple[str, ...]) -> tuple[str, ...]:
|
|
182
|
+
if any(not item.strip() for item in value):
|
|
183
|
+
raise ValueError("subagent output references must be non-empty")
|
|
184
|
+
if len(value) != len(set(value)):
|
|
185
|
+
raise ValueError("subagent output references must be unique")
|
|
186
|
+
return tuple(sorted(value))
|
|
187
|
+
|
|
188
|
+
@model_validator(mode="after")
|
|
189
|
+
def validate_terminal_shape(self) -> "SubagentResult":
|
|
190
|
+
if self.state == "succeeded" and (self.error_code or self.error_message):
|
|
191
|
+
raise ValueError("successful subagent result cannot carry an error")
|
|
192
|
+
if self.state == "failed" and not self.error_code:
|
|
193
|
+
raise ValueError("failed subagent result requires errorCode")
|
|
194
|
+
return self
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@runtime_checkable
|
|
198
|
+
class SubagentProvider(Protocol):
|
|
199
|
+
"""Named child execution provider; every method is provider-owned."""
|
|
200
|
+
|
|
201
|
+
async def describe(self) -> Mapping[str, Any]: ...
|
|
202
|
+
|
|
203
|
+
async def available(self) -> bool: ...
|
|
204
|
+
|
|
205
|
+
async def spawn(self, request: SpawnSubagentRequest) -> ChildHandle: ...
|
|
206
|
+
|
|
207
|
+
async def followup(self, handle: ChildHandle, input: Any) -> None: ...
|
|
208
|
+
|
|
209
|
+
async def status(self, handle: ChildHandle) -> SubagentStatus: ...
|
|
210
|
+
|
|
211
|
+
async def interrupt(self, handle: ChildHandle) -> None: ...
|
|
212
|
+
|
|
213
|
+
async def cancel(self, handle: ChildHandle) -> None: ...
|
|
214
|
+
|
|
215
|
+
def subscribe(
|
|
216
|
+
self, handle: ChildHandle, *, after_seq: int = 0
|
|
217
|
+
) -> AsyncIterator[SubagentEvent]: ...
|
|
218
|
+
|
|
219
|
+
async def result(self, handle: ChildHandle) -> SubagentResult: ...
|
|
220
|
+
|
|
221
|
+
async def dispose(self, handle: ChildHandle) -> None: ...
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class SubagentProviderError(RuntimeError):
|
|
225
|
+
"""Stable typed failure at the provider routing boundary."""
|
|
226
|
+
|
|
227
|
+
def __init__(self, code: str, message: str) -> None:
|
|
228
|
+
super().__init__(message)
|
|
229
|
+
self.code = code
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class SubagentProviderRouter:
|
|
233
|
+
"""Route child handles to exact providers without interpreting their state."""
|
|
234
|
+
|
|
235
|
+
def __init__(self, providers: Mapping[str, SubagentProvider]) -> None:
|
|
236
|
+
self._providers = dict(providers)
|
|
237
|
+
|
|
238
|
+
async def spawn(self, request: SpawnSubagentRequest) -> ChildHandle:
|
|
239
|
+
provider = self._provider(request.provider_ref)
|
|
240
|
+
if not await provider.available():
|
|
241
|
+
raise SubagentProviderError(
|
|
242
|
+
"subagent_provider_unavailable",
|
|
243
|
+
f"SubagentProvider {request.provider_ref} is unavailable",
|
|
244
|
+
)
|
|
245
|
+
handle = await provider.spawn(request)
|
|
246
|
+
self._validate_handle(request, handle)
|
|
247
|
+
return handle
|
|
248
|
+
|
|
249
|
+
async def status(self, handle: ChildHandle) -> SubagentStatus:
|
|
250
|
+
return await self._provider_for_handle(handle).status(handle)
|
|
251
|
+
|
|
252
|
+
async def followup(self, handle: ChildHandle, input: Any) -> None:
|
|
253
|
+
await self._provider_for_handle(handle).followup(handle, input)
|
|
254
|
+
|
|
255
|
+
async def interrupt(self, handle: ChildHandle) -> None:
|
|
256
|
+
await self._provider_for_handle(handle).interrupt(handle)
|
|
257
|
+
|
|
258
|
+
async def cancel(self, handle: ChildHandle) -> None:
|
|
259
|
+
await self._provider_for_handle(handle).cancel(handle)
|
|
260
|
+
|
|
261
|
+
def subscribe(
|
|
262
|
+
self, handle: ChildHandle, *, after_seq: int = 0
|
|
263
|
+
) -> AsyncIterator[SubagentEvent]:
|
|
264
|
+
if after_seq < 0:
|
|
265
|
+
raise SubagentProviderError(
|
|
266
|
+
"subagent_cursor_invalid", "subagent after_seq cannot be negative"
|
|
267
|
+
)
|
|
268
|
+
return self._provider_for_handle(handle).subscribe(handle, after_seq=after_seq)
|
|
269
|
+
|
|
270
|
+
async def result(self, handle: ChildHandle) -> SubagentResult:
|
|
271
|
+
return await self._provider_for_handle(handle).result(handle)
|
|
272
|
+
|
|
273
|
+
async def dispose(self, handle: ChildHandle) -> None:
|
|
274
|
+
await self._provider_for_handle(handle).dispose(handle)
|
|
275
|
+
|
|
276
|
+
def _provider_for_handle(self, handle: ChildHandle) -> SubagentProvider:
|
|
277
|
+
return self._provider(handle.provider_ref)
|
|
278
|
+
|
|
279
|
+
def _provider(self, provider_ref: str) -> SubagentProvider:
|
|
280
|
+
provider = self._providers.get(provider_ref)
|
|
281
|
+
if provider is None:
|
|
282
|
+
raise SubagentProviderError(
|
|
283
|
+
"subagent_provider_not_found",
|
|
284
|
+
f"SubagentProvider {provider_ref} is not registered",
|
|
285
|
+
)
|
|
286
|
+
return provider
|
|
287
|
+
|
|
288
|
+
@staticmethod
|
|
289
|
+
def _validate_handle(
|
|
290
|
+
request: SpawnSubagentRequest,
|
|
291
|
+
handle: ChildHandle,
|
|
292
|
+
) -> None:
|
|
293
|
+
if (
|
|
294
|
+
handle.provider_ref != request.provider_ref
|
|
295
|
+
or handle.parent_session_id != request.parent_session_id
|
|
296
|
+
or handle.parent_run_id != request.parent_run_id
|
|
297
|
+
or handle.depth != request.depth
|
|
298
|
+
):
|
|
299
|
+
raise SubagentProviderError(
|
|
300
|
+
"subagent_handle_mismatch",
|
|
301
|
+
"SubagentProvider returned a child handle outside the requested lineage",
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
_SECRET_KEY_PARTS = ("secret", "password", "token", "apikey", "api_key")
|
|
306
|
+
_SECRET_REF_PREFIXES = ("secret://", "env://", "credential://", "vault://")
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _reject_secret_values(value: Any, *, path: str = "payload") -> None:
|
|
310
|
+
if isinstance(value, Mapping):
|
|
311
|
+
for key, child in value.items():
|
|
312
|
+
key_text = str(key)
|
|
313
|
+
normalized = key_text.replace("-", "").lower()
|
|
314
|
+
if any(part in normalized for part in _SECRET_KEY_PARTS) and child is not None:
|
|
315
|
+
if not isinstance(child, str) or not child.startswith(_SECRET_REF_PREFIXES):
|
|
316
|
+
raise ValueError(f"{path}.{key_text} must contain a secret reference")
|
|
317
|
+
_reject_secret_values(child, path=f"{path}.{key_text}")
|
|
318
|
+
elif isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)):
|
|
319
|
+
for index, child in enumerate(value):
|
|
320
|
+
_reject_secret_values(child, path=f"{path}[{index}]")
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
__all__ = [
|
|
324
|
+
"ChildHandle",
|
|
325
|
+
"SpawnSubagentRequest",
|
|
326
|
+
"SubagentEvent",
|
|
327
|
+
"SubagentPolicy",
|
|
328
|
+
"SubagentProvider",
|
|
329
|
+
"SubagentProviderError",
|
|
330
|
+
"SubagentProviderRouter",
|
|
331
|
+
"SubagentResult",
|
|
332
|
+
"SubagentStatus",
|
|
333
|
+
]
|
|
@@ -69,7 +69,7 @@ def _a2ui_surface_event(
|
|
|
69
69
|
self: Any,
|
|
70
70
|
handle: RunHandle,
|
|
71
71
|
chunk: Any,
|
|
72
|
-
) -> RuntimeEvent
|
|
72
|
+
) -> tuple[RuntimeEvent, ...]:
|
|
73
73
|
"""Recognize a validated A2UI tool envelope and emit a canonical data-item event.
|
|
74
74
|
|
|
75
75
|
The dynamic ``generate_a2ui`` tool returns official v0.9 operations as a
|
|
@@ -83,7 +83,7 @@ def _a2ui_surface_event(
|
|
|
83
83
|
"""
|
|
84
84
|
|
|
85
85
|
if not isinstance(chunk, dict):
|
|
86
|
-
return
|
|
86
|
+
return ()
|
|
87
87
|
value = chunk.get("tool_output", chunk.get("output"))
|
|
88
88
|
if value is not None and hasattr(value, "content"):
|
|
89
89
|
value = value.content
|
|
@@ -91,15 +91,15 @@ def _a2ui_surface_event(
|
|
|
91
91
|
try:
|
|
92
92
|
value = json.loads(value)
|
|
93
93
|
except (TypeError, ValueError):
|
|
94
|
-
return
|
|
94
|
+
return ()
|
|
95
95
|
if not isinstance(value, Mapping):
|
|
96
|
-
return
|
|
96
|
+
return ()
|
|
97
97
|
operations_raw = value.get("a2ui_operations")
|
|
98
98
|
if not isinstance(operations_raw, list) or not operations_raw:
|
|
99
|
-
return
|
|
99
|
+
return ()
|
|
100
100
|
operations = [dict(operation) for operation in operations_raw if isinstance(operation, Mapping)]
|
|
101
101
|
if not operations:
|
|
102
|
-
return
|
|
102
|
+
return ()
|
|
103
103
|
|
|
104
104
|
known: list[tuple[str, str]] = [] # (surface_id, lifecycle)
|
|
105
105
|
for operation in operations:
|
|
@@ -116,71 +116,75 @@ def _a2ui_surface_event(
|
|
|
116
116
|
known.append((surface_id, lifecycle))
|
|
117
117
|
break
|
|
118
118
|
if not known:
|
|
119
|
-
return
|
|
119
|
+
return ()
|
|
120
120
|
surface_ids = {surface_id for surface_id, _lifecycle in known}
|
|
121
121
|
if len(surface_ids) != 1:
|
|
122
122
|
logger.warning("ignoring A2UI tool result with multiple surfaces")
|
|
123
|
-
return
|
|
123
|
+
return ()
|
|
124
124
|
surface_id = known[0][0]
|
|
125
125
|
lifecycle = "begin" if any(lc == "begin" for _, lc in known) else known[0][1]
|
|
126
126
|
|
|
127
127
|
framework = self._runtime_type
|
|
128
128
|
run_id = handle.run_id
|
|
129
129
|
scope_id = stable_scope_id(framework, run_id)
|
|
130
|
-
|
|
130
|
+
# A dynamic A2UI tool result is a complete, immutable operation batch. It
|
|
131
|
+
# must therefore be represented by a closed canonical item. Reusing one
|
|
132
|
+
# long-lived item per surface leaves a create/update-only batch open at the
|
|
133
|
+
# run terminal and makes strict reducers reject the stream.
|
|
134
|
+
start_seq = self._next_seq()
|
|
135
|
+
batch_ref = str(chunk.get("call_id") or chunk.get("run_id") or chunk.get("id") or "")
|
|
136
|
+
if not batch_ref:
|
|
137
|
+
batch_ref = json.dumps(
|
|
138
|
+
operations,
|
|
139
|
+
ensure_ascii=False,
|
|
140
|
+
sort_keys=True,
|
|
141
|
+
separators=(",", ":"),
|
|
142
|
+
)
|
|
143
|
+
item_id = stable_item_id(framework, run_id, "a2ui-batch", surface_id, batch_ref)
|
|
131
144
|
source = SourceRef(
|
|
132
145
|
framework=framework,
|
|
133
146
|
protocol="a2ui",
|
|
134
147
|
native_run_id=run_id,
|
|
135
|
-
metadata={
|
|
148
|
+
metadata={
|
|
149
|
+
"surface_id": surface_id,
|
|
150
|
+
"operation_batch": True,
|
|
151
|
+
"surface_lifecycle": lifecycle,
|
|
152
|
+
},
|
|
136
153
|
)
|
|
137
154
|
# TODO(runtime-event-v2): dict chunk 退化路径,chunk_ordinal 用 seq counter;
|
|
138
155
|
# LangGraph/Codex 切 stream_canonical_events 后清理
|
|
139
|
-
n = self._next_seq()
|
|
140
156
|
timestamp = time.time()
|
|
141
|
-
|
|
142
|
-
|
|
157
|
+
snapshot = ContentSnapshot(parts=(DataContent(part_id="a2ui-ops", data=operations),))
|
|
158
|
+
completed_seq = self._next_seq()
|
|
159
|
+
return (
|
|
160
|
+
ItemStarted(
|
|
143
161
|
schema_version=2,
|
|
144
162
|
event_id=stable_event_id(
|
|
145
|
-
framework, scope_id, item_id, "item.started", "a2ui", run_id,
|
|
163
|
+
framework, scope_id, item_id, "item.started", "a2ui", run_id, start_seq
|
|
146
164
|
),
|
|
147
|
-
seq=
|
|
165
|
+
seq=start_seq,
|
|
148
166
|
timestamp=timestamp,
|
|
149
167
|
run_id=run_id,
|
|
150
168
|
scope_id=scope_id,
|
|
151
169
|
source=source,
|
|
152
170
|
item_id=item_id,
|
|
153
171
|
item_kind="data",
|
|
154
|
-
initial=
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
return ItemUpdated(
|
|
172
|
+
initial=snapshot,
|
|
173
|
+
),
|
|
174
|
+
ItemCompleted(
|
|
158
175
|
schema_version=2,
|
|
159
176
|
event_id=stable_event_id(
|
|
160
|
-
framework, scope_id, item_id, "item.
|
|
177
|
+
framework, scope_id, item_id, "item.completed", "a2ui", run_id, completed_seq
|
|
161
178
|
),
|
|
162
|
-
seq=
|
|
179
|
+
seq=completed_seq,
|
|
163
180
|
timestamp=timestamp,
|
|
164
181
|
run_id=run_id,
|
|
165
182
|
scope_id=scope_id,
|
|
166
183
|
source=source,
|
|
167
184
|
item_id=item_id,
|
|
168
185
|
item_kind="data",
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
)
|
|
172
|
-
# lifecycle == "end"
|
|
173
|
-
return ItemCompleted(
|
|
174
|
-
schema_version=2,
|
|
175
|
-
event_id=stable_event_id(framework, scope_id, item_id, "item.completed", "a2ui", run_id, n),
|
|
176
|
-
seq=n,
|
|
177
|
-
timestamp=timestamp,
|
|
178
|
-
run_id=run_id,
|
|
179
|
-
scope_id=scope_id,
|
|
180
|
-
source=source,
|
|
181
|
-
item_id=item_id,
|
|
182
|
-
item_kind="data",
|
|
183
|
-
snapshot=ContentSnapshot(parts=()),
|
|
186
|
+
snapshot=snapshot,
|
|
187
|
+
),
|
|
184
188
|
)
|
|
185
189
|
|
|
186
190
|
|
|
@@ -323,8 +327,7 @@ class _RunnerStreamMappingMixin:
|
|
|
323
327
|
usage.update(raw_usage)
|
|
324
328
|
for event in self._chunk_to_event(handle, run, chunk): # type: ignore[attr-defined]
|
|
325
329
|
yield event
|
|
326
|
-
a2ui_surface
|
|
327
|
-
if a2ui_surface is not None:
|
|
330
|
+
for a2ui_surface in _a2ui_surface_event(self, handle, chunk):
|
|
328
331
|
yield a2ui_surface
|
|
329
332
|
finally:
|
|
330
333
|
if accumulated_output:
|
|
@@ -708,8 +711,10 @@ class _RunnerStreamMappingMixin:
|
|
|
708
711
|
if run is not None:
|
|
709
712
|
run.final_answer_item_id = item_id
|
|
710
713
|
text_content = TextContent(part_id="text-0", text=output)
|
|
711
|
-
# Auto-close any open commentary/reasoning item before
|
|
712
|
-
# Text
|
|
714
|
+
# Auto-close any open explicit commentary/reasoning item before completing
|
|
715
|
+
# final_answer. Text deltas already belong to the final-answer item and must
|
|
716
|
+
# keep that identity through completion.
|
|
717
|
+
# Commentary/thinking deltas create items that are never ItemCompleted;
|
|
713
718
|
# without this, RunCompleted fails _ensure_no_open_items.
|
|
714
719
|
#
|
|
715
720
|
# Close them *before* allocating the final-answer item. Event
|
|
@@ -764,9 +769,13 @@ class _RunnerStreamMappingMixin:
|
|
|
764
769
|
text = self._coerce(chunk.get("delta") or chunk.get("output") or chunk.get("data")) # type: ignore[attr-defined]
|
|
765
770
|
if not text:
|
|
766
771
|
return []
|
|
767
|
-
|
|
772
|
+
is_commentary = chunk_type in {"commentary", "commentary_delta"}
|
|
773
|
+
phase = "commentary" if is_commentary else "final_answer"
|
|
774
|
+
item_id = stable_item_id(framework, run_id, "message", phase)
|
|
775
|
+
if run is not None and not is_commentary:
|
|
776
|
+
run.final_answer_item_id = item_id
|
|
768
777
|
op: str = "replace" if chunk.get("replace") else "append"
|
|
769
|
-
events = ensure_started(item_id=item_id, item_kind="message", phase=
|
|
778
|
+
events = ensure_started(item_id=item_id, item_kind="message", phase=phase)
|
|
770
779
|
events.append(
|
|
771
780
|
ItemUpdated(
|
|
772
781
|
**self._canonical_kwargs( # type: ignore[attr-defined]
|
ksadk/runtime/adapter.py
CHANGED
ksadk/runtime/factory.py
CHANGED
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import dataclasses
|
|
6
|
+
import hashlib
|
|
6
7
|
import os
|
|
8
|
+
import re
|
|
9
|
+
import shutil
|
|
7
10
|
import tempfile
|
|
8
11
|
from pathlib import Path
|
|
9
12
|
from typing import Any
|
|
@@ -11,8 +14,7 @@ from typing import Any
|
|
|
11
14
|
from ksadk.codex.client import AsyncCodexClient
|
|
12
15
|
from ksadk.codex.runtime import CodexRuntimeAdapter
|
|
13
16
|
from ksadk.runners.base_runner import BaseRunner
|
|
14
|
-
from ksadk.runtime.adapter import StartRequest
|
|
15
|
-
from ksadk.runtime.adapter import RuntimeAdapter, RuntimeRegistry
|
|
17
|
+
from ksadk.runtime.adapter import RuntimeAdapter, RuntimeRegistry, StartRequest
|
|
16
18
|
from ksadk.runtime.framework_adapters import ADKRuntimeAdapter, LangGraphRuntimeAdapter
|
|
17
19
|
from ksadk.runtime.launch import RuntimeLaunchContext
|
|
18
20
|
|
|
@@ -88,6 +90,19 @@ def kernel_start_request_defaults(context: RuntimeLaunchContext) -> dict[str, An
|
|
|
88
90
|
}
|
|
89
91
|
if base_instructions:
|
|
90
92
|
request_config["base_instructions"] = base_instructions
|
|
93
|
+
collaboration_mode = str(config.get("collaboration_mode") or "").strip().lower()
|
|
94
|
+
if collaboration_mode:
|
|
95
|
+
if collaboration_mode not in {"default", "plan"}:
|
|
96
|
+
raise ValueError("Codex collaboration_mode must be default or plan")
|
|
97
|
+
request_config["collaboration_mode"] = collaboration_mode
|
|
98
|
+
goal_objective = str(config.get("goal_objective") or "").strip()
|
|
99
|
+
if goal_objective:
|
|
100
|
+
request_config["goal_objective"] = goal_objective
|
|
101
|
+
raw_skills = config.get("skills")
|
|
102
|
+
if isinstance(raw_skills, (list, tuple)):
|
|
103
|
+
request_config["skills"] = [
|
|
104
|
+
dict(item) for item in raw_skills if isinstance(item, dict)
|
|
105
|
+
]
|
|
91
106
|
defaults["config"] = request_config
|
|
92
107
|
return defaults
|
|
93
108
|
|
|
@@ -158,6 +173,7 @@ def _manifest_mcp_overrides(config: dict[str, Any]) -> list[str]:
|
|
|
158
173
|
def _create_codex(context: RuntimeLaunchContext) -> RuntimeAdapter:
|
|
159
174
|
client_factory = context.services.codex_client_factory or AsyncCodexClient
|
|
160
175
|
config = dict(context.config)
|
|
176
|
+
bound_skill_paths: dict[str, str] = {}
|
|
161
177
|
overrides = list(config.get("codex_overrides") or [])
|
|
162
178
|
manifest_mcp_overrides = _manifest_mcp_overrides(config)
|
|
163
179
|
overrides.extend(item for item in manifest_mcp_overrides if item not in overrides)
|
|
@@ -185,6 +201,9 @@ def _create_codex(context: RuntimeLaunchContext) -> RuntimeAdapter:
|
|
|
185
201
|
)
|
|
186
202
|
env = dict(getattr(base_cfg, "env", None) or {})
|
|
187
203
|
isolated_home = _isolated_codex_home(context.project_dir)
|
|
204
|
+
bound_skill_paths = _materialize_bound_codex_skills(
|
|
205
|
+
isolated_home, config.get("skills")
|
|
206
|
+
)
|
|
188
207
|
env.setdefault("CODEX_HOME", str(isolated_home))
|
|
189
208
|
# HOME 级隔离:codex app-server 还会按约定扫 ~/.agents/skills、
|
|
190
209
|
# ~/.claude/skills 等宿主目录,仅设 CODEX_HOME 挡不住。隔离 HOME 后这些
|
|
@@ -219,6 +238,7 @@ def _create_codex(context: RuntimeLaunchContext) -> RuntimeAdapter:
|
|
|
219
238
|
client,
|
|
220
239
|
sandbox_read_only=sandbox_read_only,
|
|
221
240
|
turn_timeout_seconds=float(timeout) if timeout is not None else None,
|
|
241
|
+
bound_skill_paths=bound_skill_paths,
|
|
222
242
|
)
|
|
223
243
|
|
|
224
244
|
|
|
@@ -263,6 +283,60 @@ def _isolated_codex_home(project_dir: Any) -> Path:
|
|
|
263
283
|
return fallback_home
|
|
264
284
|
|
|
265
285
|
|
|
286
|
+
def _materialize_bound_codex_skills(
|
|
287
|
+
codex_home: Path, value: Any
|
|
288
|
+
) -> dict[str, str]:
|
|
289
|
+
"""Expose immutable Bundle Skills through Codex's native skill catalog.
|
|
290
|
+
|
|
291
|
+
Passing an arbitrary ``SkillInput`` path is accepted by the Python SDK but
|
|
292
|
+
ignored by the real App Server unless the Skill is discoverable by the
|
|
293
|
+
native host. Copy each content-addressed Bundle Skill into the isolated
|
|
294
|
+
CODEX_HOME so the host owns discovery while the Bundle remains read-only.
|
|
295
|
+
Content-addressed directory names avoid overwriting user-managed Skills
|
|
296
|
+
when an explicit KSADK_CODEX_HOME is used.
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
if not isinstance(value, (list, tuple)):
|
|
300
|
+
return {}
|
|
301
|
+
skills_root = codex_home / "skills"
|
|
302
|
+
skills_root.mkdir(parents=True, exist_ok=True)
|
|
303
|
+
installed: dict[str, str] = {}
|
|
304
|
+
for item in value:
|
|
305
|
+
if not isinstance(item, dict):
|
|
306
|
+
continue
|
|
307
|
+
name = str(item.get("name") or "").strip()
|
|
308
|
+
raw_path = str(item.get("path") or "").strip()
|
|
309
|
+
if not name or not raw_path:
|
|
310
|
+
continue
|
|
311
|
+
skill_file = Path(raw_path).resolve()
|
|
312
|
+
if skill_file.name != "SKILL.md" or not skill_file.is_file():
|
|
313
|
+
continue
|
|
314
|
+
source_dir = skill_file.parent
|
|
315
|
+
digest = hashlib.sha256()
|
|
316
|
+
for source in sorted(path for path in source_dir.rglob("*") if path.is_file()):
|
|
317
|
+
digest.update(source.relative_to(source_dir).as_posix().encode("utf-8"))
|
|
318
|
+
digest.update(b"\0")
|
|
319
|
+
digest.update(source.read_bytes())
|
|
320
|
+
digest.update(b"\0")
|
|
321
|
+
safe_name = re.sub(r"[^A-Za-z0-9._-]+", "-", name).strip(".-") or "skill"
|
|
322
|
+
target = skills_root / f"ksadk-{digest.hexdigest()[:16]}-{safe_name}"
|
|
323
|
+
if target.is_dir():
|
|
324
|
+
installed[name] = str(target / "SKILL.md")
|
|
325
|
+
continue
|
|
326
|
+
staging = Path(tempfile.mkdtemp(prefix=".ksadk-skill-", dir=skills_root))
|
|
327
|
+
try:
|
|
328
|
+
shutil.copytree(source_dir, staging, dirs_exist_ok=True)
|
|
329
|
+
try:
|
|
330
|
+
staging.replace(target)
|
|
331
|
+
except FileExistsError:
|
|
332
|
+
pass
|
|
333
|
+
finally:
|
|
334
|
+
if staging.exists():
|
|
335
|
+
shutil.rmtree(staging)
|
|
336
|
+
installed[name] = str(target / "SKILL.md")
|
|
337
|
+
return installed
|
|
338
|
+
|
|
339
|
+
|
|
266
340
|
def _apply_codex_overrides(client: Any, overrides: Any) -> None:
|
|
267
341
|
"""Append MCP server --config overrides to the client's CodexConfig."""
|
|
268
342
|
try:
|
|
@@ -63,6 +63,24 @@ class LangGraphRuntimeAdapter(RunnerRuntimeAdapter):
|
|
|
63
63
|
def __init__(self, runner: BaseRunner) -> None:
|
|
64
64
|
super().__init__(runner, runtime_type="langgraph")
|
|
65
65
|
|
|
66
|
+
def capabilities(self): # noqa: ANN201
|
|
67
|
+
"""Advertise checkpoint interaction only when LangGraph can resume it.
|
|
68
|
+
|
|
69
|
+
``RunnerRuntimeAdapter`` is intentionally conservative because a
|
|
70
|
+
generic checkpoint is not proof of original-interrupt delivery. The
|
|
71
|
+
LangGraph adapter owns that mapping, so it is the only runner-family
|
|
72
|
+
adapter that may publish ``durable_resume``.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
matrix = super().capabilities()
|
|
76
|
+
return matrix.model_copy(
|
|
77
|
+
update={
|
|
78
|
+
"interaction_mode": (
|
|
79
|
+
"durable_resume" if matrix.resume.supported else "unavailable"
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
|
|
66
84
|
async def _resume_native(
|
|
67
85
|
self,
|
|
68
86
|
handle: RunHandle,
|