agentengine-sdk-python 0.8.2__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.2.dist-info → agentengine_sdk_python-0.8.4.dist-info}/METADATA +22 -28
- agentengine_sdk_python-0.8.4.dist-info/RECORD +1070 -0
- ksadk/_build_provenance.json +1 -0
- ksadk/agui/_agent_helpers.py +4 -0
- ksadk/api/client.py +108 -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 +4 -0
- ksadk/cli/__init__.py +38 -3
- ksadk/cli/cmd_managed_runtime.py +13 -1
- ksadk/cli/cmd_plugin.py +662 -0
- 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 +83 -283
- ksadk/common/auth.py +16 -10
- ksadk/configs/env_registry.py +59 -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 +154 -15
- ksadk/conversations/projector.py +614 -0
- ksadk/conversations/reducer.py +98 -0
- 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/_v1_compat/projection.py +2 -0
- ksadk/events/adapters/_codex_validators.py +18 -7
- ksadk/events/adapters/codex.py +190 -0
- ksadk/events/adapters/langgraph.py +15 -1
- ksadk/events/canonical_store.py +3 -1
- ksadk/events/projections.py +1 -1
- ksadk/harness/__init__.py +45 -35
- ksadk/harness/config.py +2 -2
- ksadk/harness/runtime.py +75 -16
- 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/contract_fingerprints.py +5 -4
- ksadk/kernel/contracts.py +8 -0
- ksadk/kernel/control.py +1 -0
- ksadk/kernel/ingress.py +36 -2
- ksadk/kernel/memory_store.py +1 -0
- ksadk/kernel/postgres_store.py +1 -0
- ksadk/kernel/sqlite_store.py +28 -0
- ksadk/kernel/store.py +3 -0
- ksadk/kernel/worker.py +76 -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/memory/providers/local_sqlite.py +7 -0
- ksadk/model_proxy/server.py +10 -5
- ksadk/plugins/__init__.py +171 -0
- ksadk/plugins/artifacts.py +275 -0
- ksadk/plugins/bridges/__init__.py +47 -0
- ksadk/plugins/bridges/codex.py +659 -0
- ksadk/plugins/bridges/dsh.py +1300 -0
- ksadk/plugins/builtins.py +746 -0
- ksadk/plugins/bundle.py +246 -0
- ksadk/plugins/bundle_security.py +191 -0
- ksadk/plugins/codex_manifest.py +983 -0
- ksadk/plugins/composition.py +537 -0
- ksadk/plugins/context_contributor.py +437 -0
- ksadk/plugins/contracts.py +569 -0
- ksadk/plugins/delivery.py +153 -0
- ksadk/plugins/dsh_installation_digest.py +71 -0
- ksadk/plugins/dsh_toolchain.py +1024 -0
- ksadk/plugins/ecosystem_bridge.py +1105 -0
- ksadk/plugins/ecosystem_probe.py +191 -0
- ksadk/plugins/host.py +787 -0
- ksadk/plugins/kernel_adapter.py +205 -0
- ksadk/plugins/providers/__init__.py +108 -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-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-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/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 +988 -0
- ksadk/plugins/providers/codex_dsh.py +83 -0
- ksadk/plugins/providers/codex_native.py +368 -0
- ksadk/plugins/providers/codex_turn.py +301 -0
- ksadk/plugins/providers/dsh.py +951 -0
- ksadk/plugins/providers/dsh_capabilities.py +1389 -0
- ksadk/plugins/providers/dsh_descriptor_host.py +226 -0
- ksadk/plugins/providers/dsh_mcp.py +467 -0
- ksadk/plugins/providers/harness.py +632 -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/mcp_projection.py +150 -0
- ksadk/plugins/providers/platform_resources.py +657 -0
- ksadk/plugins/providers/shipped_dsh.py +370 -0
- ksadk/plugins/resolver.py +338 -0
- ksadk/plugins/subagent_providers/__init__.py +12 -0
- ksadk/plugins/subagent_providers/codex.py +443 -0
- ksadk/plugins/subagents.py +333 -0
- 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 +193 -60
- ksadk/runtime/adapter.py +3 -0
- ksadk/runtime/conversation_execution.py +92 -16
- ksadk/runtime/executor.py +78 -2
- ksadk/runtime/factory.py +30 -200
- ksadk/runtime/framework_adapters.py +41 -0
- ksadk/runtime/preprocessing.py +10 -2
- ksadk/runtime/runner_adapter.py +78 -14
- ksadk/runtime_context.py +40 -2
- ksadk/sandbox/__init__.py +2 -1
- 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/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 +581 -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-DSU1sWD_.js → ArtifactsPanel-CGbWlMNh.js} +1 -1
- ksadk/server/static/assets/{CodeBlock-DOH6MVcn.js → CodeBlock-b4C7upmO.js} +3 -3
- ksadk/server/static/assets/{MathMessageMarkdown-BL8my1R2.js → MathMessageMarkdown-DbdW5A4_.js} +4 -4
- ksadk/server/static/assets/{MermaidBlock-Dz4IP-Tx.js → MermaidBlock-BEBN4Ksz.js} +6 -6
- ksadk/server/static/assets/{NativeTerminalPanel-DUrK0JpZ.js → NativeTerminalPanel-zvL-WpAn.js} +1 -1
- ksadk/server/static/assets/{abnfDiagram-VCTEODGH-g20pFzNV.js → abnfDiagram-VCTEODGH-BYafW7Za.js} +1 -1
- ksadk/server/static/assets/{arc-C4FzinUA.js → arc-bTohInsS.js} +1 -1
- ksadk/server/static/assets/architecture-7GRP2DOG-BW5UtSnx.js +1 -0
- ksadk/server/static/assets/{architectureDiagram-5GKGNRK7-DpIqL5h4.js → architectureDiagram-5GKGNRK7-Ch5Lbzsi.js} +1 -1
- ksadk/server/static/assets/{blockDiagram-NRAW4CY4-BjkljTNz.js → blockDiagram-NRAW4CY4-kqF4MDOG.js} +1 -1
- ksadk/server/static/assets/{c4Diagram-UCG6FXSJ-Dk_ieq2X.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-BVHAchFb.js → chunk-5VM5RSS4-DkhehZDr.js} +1 -1
- ksadk/server/static/assets/{chunk-F27PBJKO-C-ipQzuS.js → chunk-F27PBJKO-BdXhtPe9.js} +1 -1
- ksadk/server/static/assets/{chunk-G27WJ6UU-C2EyJbIK.js → chunk-G27WJ6UU-DPTpvpwi.js} +1 -1
- ksadk/server/static/assets/{chunk-JWPE2WC7-vYvVJb_M.js → chunk-JWPE2WC7-Pqoh38el.js} +1 -1
- ksadk/server/static/assets/{chunk-LCL6LL3I-BcI6ysPT.js → chunk-LCL6LL3I-DeU00qC-.js} +1 -1
- ksadk/server/static/assets/{chunk-POPQ4Y6H-C030x_Z1.js → chunk-POPQ4Y6H-CzxScFkr.js} +1 -1
- ksadk/server/static/assets/{chunk-RHFEMEQ7-yyGpsz4n.js → chunk-RHFEMEQ7-DpqMx-ux.js} +1 -1
- ksadk/server/static/assets/{chunk-SVP7TREG-BLTlmMU7.js → chunk-SVP7TREG-C6t0SypQ.js} +1 -1
- ksadk/server/static/assets/{chunk-XXDRQBXY-C_32ArgP.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-9YQYwdl0.js → cose-bilkent-JH36ORCC-Bf9D3tzJ.js} +1 -1
- ksadk/server/static/assets/cynefin-OW5HDTMX-DVWs8a2t.js +1 -0
- ksadk/server/static/assets/{cynefinDiagram-5FMLGOSQ-ErLF5N13.js → cynefinDiagram-5FMLGOSQ-B9cY3iTz.js} +1 -1
- ksadk/server/static/assets/{dagre-3AP2YEHR-DFiQF-6f.js → dagre-3AP2YEHR-CSwlx3CB.js} +1 -1
- ksadk/server/static/assets/{dagre-BuhGnRI1.js → dagre-UBpgon5q.js} +1 -1
- ksadk/server/static/assets/{diagram-S7CK7UJ4-Bt1v8-GC.js → diagram-S7CK7UJ4-C9akhN9a.js} +1 -1
- ksadk/server/static/assets/{diagram-UQ7AKVKN-DSCxJdBK.js → diagram-UQ7AKVKN-CgedhTPI.js} +1 -1
- ksadk/server/static/assets/{diagram-VSXAHHWV-DHfYwp_9.js → diagram-VSXAHHWV-Cjucz5BY.js} +1 -1
- ksadk/server/static/assets/{diagram-VX7I27RA-DdzD-4Le.js → diagram-VX7I27RA-Dd4Vleyn.js} +1 -1
- ksadk/server/static/assets/{diagram-Z3DM3KII-Du-nAJ9F.js → diagram-Z3DM3KII-CZ2jGtYW.js} +1 -1
- ksadk/server/static/assets/{dist-B7seoj3d.js → dist-B2_CVgKv.js} +1 -1
- ksadk/server/static/assets/{dist-CSh_DE14.js → dist-B3FR1SKK.js} +1 -1
- ksadk/server/static/assets/{dist-DUX-id_F.js → dist-BIr4DSRq.js} +1 -1
- ksadk/server/static/assets/{dist-C_wsv-Qd.js → dist-BgV_owcB.js} +1 -1
- ksadk/server/static/assets/{dist-W-TIVntx.js → dist-Bj730Ib4.js} +1 -1
- ksadk/server/static/assets/{dist-BytlxIDT.js → dist-BnaTiyzN.js} +1 -1
- ksadk/server/static/assets/{dist-CXYYBw3_.js → dist-C1NMAY5a.js} +1 -1
- ksadk/server/static/assets/{dist-dDdADKFA.js → dist-C5_ZAZsG.js} +1 -1
- ksadk/server/static/assets/{dist-BVDffZ0U.js → dist-CB9iJrvg.js} +1 -1
- ksadk/server/static/assets/{dist-Bf2M8m3N.js → dist-CFzsn6BG.js} +1 -1
- ksadk/server/static/assets/{dist-ngtN0DJB.js → dist-Cf125P_m.js} +1 -1
- ksadk/server/static/assets/{dist-BE7bu-DL.js → dist-ClYUxfeK.js} +1 -1
- ksadk/server/static/assets/{dist-DNp6rLRA.js → dist-DFD6Qa6T.js} +1 -1
- ksadk/server/static/assets/{dist-BX8z72IC.js → dist-DVCOWtha.js} +1 -1
- ksadk/server/static/assets/{dist-dydu68Fl.js → dist-D_ctSS2i.js} +1 -1
- ksadk/server/static/assets/{dist-ClxGviKw.js → dist-DiixqL0J.js} +1 -1
- ksadk/server/static/assets/{dist-BjU6y-A2.js → dist-DoVSwoDb.js} +1 -1
- ksadk/server/static/assets/{dist-Dq9gLD7L.js → dist-DvFK7p5S.js} +1 -1
- ksadk/server/static/assets/{dist-8hqcx0sU.js → dist-QfkEHOlb.js} +1 -1
- ksadk/server/static/assets/{dist-CttYUqzS.js → dist-btbmmX5O.js} +1 -1
- ksadk/server/static/assets/{dist-DmldrHd_.js → dist-fBO2v5Kt.js} +1 -1
- ksadk/server/static/assets/{dist-DnfXs8Vn.js → dist-ufY3F5p6.js} +1 -1
- ksadk/server/static/assets/{ebnfDiagram-PWID7BFC-Da9C9NO1.js → ebnfDiagram-PWID7BFC-Ci8NqqZu.js} +1 -1
- ksadk/server/static/assets/{erDiagram-SSCWMZ5O-EAGqqR79.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-DqJsKb59.js → ganttDiagram-EL5Y4UJY-Cp_r9Hb6.js} +1 -1
- ksadk/server/static/assets/gitGraph-4MIJSDKK-fgWEf7UD.js +1 -0
- ksadk/server/static/assets/{gitGraphDiagram-WWUBYQGX-B6g4dtDi.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-Djm_nbd5.js → infoDiagram-RXCK75RN-CO0rRTRA.js} +1 -1
- ksadk/server/static/assets/{ishikawaDiagram-5VMMS53U-DWya9SG2.js → ishikawaDiagram-5VMMS53U-CHiaedSS.js} +1 -1
- ksadk/server/static/assets/{journeyDiagram-EYS64GPL-DutxhSNI.js → journeyDiagram-EYS64GPL-CHcCIB09.js} +1 -1
- ksadk/server/static/assets/{kanban-definition-3QL26DDD-DJdOF8Fm.js → kanban-definition-3QL26DDD-CJ_ebBZf.js} +1 -1
- ksadk/server/static/assets/{katex-vFWytM5c.js → katex-B4weu383.js} +1 -1
- ksadk/server/static/assets/{linear-BHjG8b-J.js → linear-V-MgYHW1.js} +1 -1
- ksadk/server/static/assets/{mermaid-parser.core-KGSy4jWT.js → mermaid-parser.core-CBijEX_c.js} +2 -2
- ksadk/server/static/assets/{mindmap-definition-FBJOCRG2-9-HC88D4.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-DrEZd4fD.js → pegDiagram-XKGWAZYB-D7genGLl.js} +1 -1
- ksadk/server/static/assets/pie-WAS4IAKB-X0xh4SaT.js +1 -0
- ksadk/server/static/assets/{pieDiagram-E7YTZNPT-DpjqOFFp.js → pieDiagram-E7YTZNPT-Bxj32zwD.js} +1 -1
- ksadk/server/static/assets/{quadrantDiagram-AXDQQJYC-BiNhgOXX.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-_Y3Ojg6G.js → railroadDiagram-O6MQD6OU-CCsh3avW.js} +1 -1
- ksadk/server/static/assets/{requirementDiagram-EFPCY7ZU-DeCFdg9K.js → requirementDiagram-EFPCY7ZU-_FRRBKEd.js} +1 -1
- ksadk/server/static/assets/{sankeyDiagram-P5KCCOFB-CPH75rhw.js → sankeyDiagram-P5KCCOFB-C-gr2o9M.js} +1 -1
- ksadk/server/static/assets/{sequenceDiagram-WJ2MYXX4-OEpQQdr4.js → sequenceDiagram-WJ2MYXX4-D97NmfKg.js} +1 -1
- ksadk/server/static/assets/{sizeCapture-X5ZJPWSS-heUErzhI.js → sizeCapture-X5ZJPWSS-DjrPC5FX.js} +1 -1
- ksadk/server/static/assets/{stateDiagram-HBIQ2CUA-DvA3jSMB.js → stateDiagram-HBIQ2CUA-DzMNg1hh.js} +1 -1
- ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BIcjKFhF.js +1 -0
- ksadk/server/static/assets/{swimlanes-XN3QIQJK-CR-dfN_t.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-DAVJf1TX.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-DwM8eDkh.js → vennDiagram-4TSXK5OY-BSKgJsmR.js} +1 -1
- ksadk/server/static/assets/wardley-WFR3VGLG-CefhOliM.js +1 -0
- ksadk/server/static/assets/{wardleyDiagram-VM6X3IG4-DZL-Zz2t.js → wardleyDiagram-VM6X3IG4-eflHNoaZ.js} +1 -1
- ksadk/server/static/assets/{xychartDiagram-S5SC5T6Z-Dy6Yh_hu.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 +211 -24
- ksadk/studio/agent_lifecycle.py +26 -20
- ksadk/studio/api.py +714 -90
- ksadk/studio/api_catalog_routes.py +23 -0
- ksadk/studio/api_contracts.py +34 -1
- ksadk/studio/api_plugin_routes.py +911 -0
- ksadk/studio/api_resource_connections.py +183 -0
- ksadk/studio/attachment_store.py +187 -0
- ksadk/studio/authoring.py +10 -4
- ksadk/studio/authoring_coordinator.py +31 -16
- ksadk/studio/builder.py +199 -43
- ksadk/studio/cloud.py +346 -104
- ksadk/studio/cloud_shared_web.py +565 -0
- ksadk/studio/codex_agent_service.py +140 -27
- ksadk/studio/codex_builder.py +294 -16
- ksadk/studio/codex_credentials.py +90 -0
- ksadk/studio/codex_manifest.py +51 -4
- ksadk/studio/codex_plugin_store.py +746 -0
- ksadk/studio/codex_provider_build.py +231 -0
- ksadk/studio/codex_run.py +200 -29
- ksadk/studio/compatibility_report.py +456 -0
- ksadk/studio/compiler.py +84 -15
- ksadk/studio/contracts.py +249 -4
- 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 +1075 -0
- ksadk/studio/event_store.py +54 -6
- ksadk/studio/framework_run.py +37 -4
- ksadk/studio/hosted_kernel.py +89 -14
- ksadk/studio/mcp_runtime.py +11 -3
- ksadk/studio/model_client.py +2 -2
- ksadk/studio/operations.py +12 -2
- ksadk/studio/plugin_composition.py +468 -0
- ksadk/studio/plugin_kernel_adapter.py +28 -0
- ksadk/studio/plugin_runtime.py +831 -0
- 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 +149 -3
- ksadk/studio/resource_connections.py +220 -0
- ksadk/studio/run_service.py +1144 -96
- ksadk/studio/runtime_source.py +24 -8
- ksadk/studio/scheduler_assistant.py +317 -0
- ksadk/studio/scheduler_runtime.py +383 -0
- ksadk/studio/scheduler_service.py +214 -0
- ksadk/studio/service.py +1131 -95
- ksadk/studio/shared_web.py +610 -87
- ksadk/studio/soul.py +52 -0
- 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/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
- agentengine_sdk_python-0.8.2.dist-info/RECORD +0 -778
- 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/PrismRenderer-IYN-ffww.js +0 -3
- 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.4.dist-info}/WHEEL +0 -0
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.4.dist-info}/entry_points.txt +0 -0
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.4.dist-info}/licenses/LICENSE +0 -0
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.4.dist-info}/top_level.txt +0 -0
ksadk/studio/run_service.py
CHANGED
|
@@ -3,15 +3,24 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
+
import hashlib
|
|
7
|
+
import json
|
|
6
8
|
import logging
|
|
7
9
|
import time
|
|
8
10
|
from collections.abc import Callable, Mapping
|
|
9
11
|
from dataclasses import asdict, dataclass, field
|
|
10
12
|
from datetime import datetime, timezone
|
|
13
|
+
from pathlib import Path
|
|
11
14
|
from typing import Any
|
|
12
15
|
from uuid import uuid4
|
|
13
16
|
|
|
17
|
+
from jsonschema import Draft202012Validator
|
|
18
|
+
|
|
14
19
|
from ksadk.agui.a2ui_projection import project_a2ui_operations
|
|
20
|
+
from ksadk.conversations.projector import (
|
|
21
|
+
project_conversation_item,
|
|
22
|
+
project_interaction_conversation_item,
|
|
23
|
+
)
|
|
15
24
|
from ksadk.events.canonical import (
|
|
16
25
|
ContinuationCreated,
|
|
17
26
|
ContinuationResumed,
|
|
@@ -22,6 +31,7 @@ from ksadk.events.canonical import (
|
|
|
22
31
|
ItemFailed,
|
|
23
32
|
ItemStarted,
|
|
24
33
|
ItemUpdated,
|
|
34
|
+
OutputRef,
|
|
25
35
|
RunCanceled,
|
|
26
36
|
RunCompleted,
|
|
27
37
|
RunFailed,
|
|
@@ -32,7 +42,9 @@ from ksadk.events.canonical import (
|
|
|
32
42
|
SourceRef,
|
|
33
43
|
UsageReported,
|
|
34
44
|
dump_runtime_event,
|
|
45
|
+
parse_runtime_event,
|
|
35
46
|
)
|
|
47
|
+
from ksadk.events.canonical_store import session_event_to_runtime_event
|
|
36
48
|
from ksadk.events.content import (
|
|
37
49
|
ContentSnapshot,
|
|
38
50
|
DataContent,
|
|
@@ -50,6 +62,7 @@ from ksadk.runtime import (
|
|
|
50
62
|
RuntimeLaunchContext,
|
|
51
63
|
StartRequest,
|
|
52
64
|
)
|
|
65
|
+
from ksadk.runtime.executor import RuntimeStartPreparation
|
|
53
66
|
from ksadk.sessions.base import BaseSessionService
|
|
54
67
|
from ksadk.sessions.local_service import LocalSessionService
|
|
55
68
|
from ksadk.studio.contracts import RunEvent, RunRecord, RunStatus, Usage
|
|
@@ -58,6 +71,7 @@ from ksadk.studio.event_store import RunEventStore
|
|
|
58
71
|
from ksadk.studio.workspace import Workspace
|
|
59
72
|
|
|
60
73
|
_CANCEL_TIMEOUT_SECONDS = 2.0
|
|
74
|
+
_INTERACTION_SUBMIT_TIMEOUT_SECONDS = 10.0
|
|
61
75
|
|
|
62
76
|
|
|
63
77
|
logger = logging.getLogger(__name__)
|
|
@@ -73,6 +87,7 @@ class StudioRunSpec:
|
|
|
73
87
|
model: str | None = None
|
|
74
88
|
request_config: Mapping[str, Any] = field(default_factory=dict)
|
|
75
89
|
manifest_sha256: str = ""
|
|
90
|
+
plugin_bundle_root: Path | None = None
|
|
76
91
|
|
|
77
92
|
|
|
78
93
|
class StudioRunService:
|
|
@@ -86,6 +101,7 @@ class StudioRunService:
|
|
|
86
101
|
event_store: RunEventStore | None = None,
|
|
87
102
|
session_service: BaseSessionService | None = None,
|
|
88
103
|
runtime_events: RuntimeEventStore | None = None,
|
|
104
|
+
plugin_runtime: Any | None = None,
|
|
89
105
|
) -> None:
|
|
90
106
|
self.workspace = workspace
|
|
91
107
|
self.executor = executor
|
|
@@ -94,10 +110,21 @@ class StudioRunService:
|
|
|
94
110
|
project_dir=str(workspace.root)
|
|
95
111
|
)
|
|
96
112
|
self.runtime_events = runtime_events or RuntimeEventStore(self.session_service)
|
|
113
|
+
self.plugin_runtime = plugin_runtime
|
|
114
|
+
self.schedule_assistant: Any | None = None
|
|
97
115
|
self._active_handles: dict[str, Any] = {}
|
|
116
|
+
self._active_sessions: set[tuple[str, str]] = set()
|
|
98
117
|
self._cancel_flags: dict[str, bool] = {}
|
|
99
|
-
self._control_queues: dict[
|
|
118
|
+
self._control_queues: dict[
|
|
119
|
+
str,
|
|
120
|
+
asyncio.Queue[tuple[str, ResumePayload | None, asyncio.Future[None] | None]],
|
|
121
|
+
] = {}
|
|
100
122
|
self._waiting_modes: dict[str, str] = {}
|
|
123
|
+
self._submitted_interactions: dict[str, set[str]] = {}
|
|
124
|
+
# Interaction resolution appends to one run-level event file. Serialise
|
|
125
|
+
# every decision for that run, not only identical interaction ids, so
|
|
126
|
+
# two concurrently visible cards cannot overwrite each other's receipt.
|
|
127
|
+
self._interaction_locks: dict[str, asyncio.Lock] = {}
|
|
101
128
|
|
|
102
129
|
async def recover_interrupted(self) -> None:
|
|
103
130
|
"""Settle local runs left active across a Studio restart.
|
|
@@ -122,6 +149,7 @@ class StudioRunService:
|
|
|
122
149
|
"run.interrupted",
|
|
123
150
|
{"reason": "local_studio_restarted", "recoverable": False},
|
|
124
151
|
)
|
|
152
|
+
self._expire_unresolved_interactions(record)
|
|
125
153
|
|
|
126
154
|
async def events(self, run_id: str, *, after: int = 0) -> list[RunEvent]:
|
|
127
155
|
"""Read the durable Studio event timeline for API/SSE replay."""
|
|
@@ -135,6 +163,38 @@ class StudioRunService:
|
|
|
135
163
|
runtime_input: Any = None,
|
|
136
164
|
session_id: str | None = None,
|
|
137
165
|
on_event: Callable[[RunEvent], None] | None = None,
|
|
166
|
+
) -> RunRecord:
|
|
167
|
+
session = session_id or f"ses_{uuid4().hex}"
|
|
168
|
+
key = (spec.agent_id, session)
|
|
169
|
+
# Claim before the first await, including preparation and plugin runs.
|
|
170
|
+
# A second turn must never attach another adapter to the live thread.
|
|
171
|
+
if key in self._active_sessions:
|
|
172
|
+
raise StudioError(
|
|
173
|
+
"SESSION_RUN_ACTIVE",
|
|
174
|
+
"上一轮仍在运行或等待回答,请先完成回答或停止运行。",
|
|
175
|
+
status_code=409,
|
|
176
|
+
details={"sessionId": session},
|
|
177
|
+
)
|
|
178
|
+
self._active_sessions.add(key)
|
|
179
|
+
try:
|
|
180
|
+
return await self._run_owned(
|
|
181
|
+
spec,
|
|
182
|
+
user_input,
|
|
183
|
+
runtime_input=runtime_input,
|
|
184
|
+
session_id=session,
|
|
185
|
+
on_event=on_event,
|
|
186
|
+
)
|
|
187
|
+
finally:
|
|
188
|
+
self._active_sessions.discard(key)
|
|
189
|
+
|
|
190
|
+
async def _run_owned(
|
|
191
|
+
self,
|
|
192
|
+
spec: StudioRunSpec,
|
|
193
|
+
user_input: str,
|
|
194
|
+
*,
|
|
195
|
+
runtime_input: Any,
|
|
196
|
+
session_id: str,
|
|
197
|
+
on_event: Callable[[RunEvent], None] | None,
|
|
138
198
|
) -> RunRecord:
|
|
139
199
|
run_id = f"run_{uuid4().hex}"
|
|
140
200
|
session = session_id or f"ses_{uuid4().hex}"
|
|
@@ -173,11 +233,41 @@ class StudioRunService:
|
|
|
173
233
|
if on_event is not None:
|
|
174
234
|
on_event(created)
|
|
175
235
|
|
|
176
|
-
|
|
236
|
+
if self.schedule_assistant is not None and (
|
|
237
|
+
self.schedule_assistant.matches(user_input)
|
|
238
|
+
or self.schedule_assistant.matches_followup(spec.agent_id, session, user_input)
|
|
239
|
+
):
|
|
240
|
+
handled = await self._schedule_run(record, user_input, on_event=on_event)
|
|
241
|
+
if handled is not None:
|
|
242
|
+
return handled
|
|
177
243
|
|
|
178
|
-
|
|
244
|
+
provider_runtime_adapter = (
|
|
245
|
+
spec.plugin_bundle_root is not None
|
|
246
|
+
and (
|
|
247
|
+
runtime_type == "codex"
|
|
248
|
+
or bool(spec.request_config.get("provider_runtime_adapter"))
|
|
249
|
+
)
|
|
250
|
+
)
|
|
251
|
+
# A process-global Codex Kernel can share Agent/cwd with this Build
|
|
252
|
+
# while owning a legacy direct factory. New Builds must retain their
|
|
253
|
+
# exact Provider activation, including when that global Kernel exists.
|
|
254
|
+
kernel_runtime = (
|
|
255
|
+
None if provider_runtime_adapter else self._kernel_runtime_for_spec(spec)
|
|
256
|
+
)
|
|
257
|
+
if kernel_runtime is not None:
|
|
179
258
|
return await self._kernel_run(
|
|
180
|
-
spec,
|
|
259
|
+
spec,
|
|
260
|
+
user_input,
|
|
261
|
+
record=record,
|
|
262
|
+
on_event=on_event,
|
|
263
|
+
kernel_runtime=kernel_runtime,
|
|
264
|
+
)
|
|
265
|
+
if spec.plugin_bundle_root is not None and not provider_runtime_adapter:
|
|
266
|
+
return await self._plugin_run(
|
|
267
|
+
spec,
|
|
268
|
+
user_input,
|
|
269
|
+
record=record,
|
|
270
|
+
on_event=on_event,
|
|
181
271
|
)
|
|
182
272
|
|
|
183
273
|
started = time.monotonic()
|
|
@@ -187,18 +277,32 @@ class StudioRunService:
|
|
|
187
277
|
|
|
188
278
|
async def persist(runtime_event: RuntimeEvent) -> RunEvent:
|
|
189
279
|
persisted = await self.runtime_events.append_one(record.session_id, runtime_event)
|
|
190
|
-
event_type, data = project_runtime_event(
|
|
280
|
+
event_type, data = project_runtime_event(
|
|
281
|
+
persisted,
|
|
282
|
+
session_id=record.session_id,
|
|
283
|
+
public_run_id=record.id,
|
|
284
|
+
)
|
|
285
|
+
if isinstance(persisted, InteractionRequested):
|
|
286
|
+
data["revision"] = 1
|
|
287
|
+
conversation_item = data.get("conversationItem")
|
|
288
|
+
if isinstance(conversation_item, dict):
|
|
289
|
+
item_payload = conversation_item.get("payload")
|
|
290
|
+
if isinstance(item_payload, dict):
|
|
291
|
+
item_payload["revision"] = 1
|
|
191
292
|
stored = self.event_store.append(record.id, event_type, data)
|
|
192
293
|
if on_event is not None:
|
|
193
294
|
on_event(stored)
|
|
194
295
|
return stored
|
|
195
296
|
|
|
196
297
|
handle = None
|
|
197
|
-
|
|
198
|
-
|
|
298
|
+
completed_text_by_item: dict[tuple[str, str], str] = {}
|
|
299
|
+
streamed_text_by_item: dict[tuple[str, str], str] = {}
|
|
300
|
+
terminal_output_refs: tuple[OutputRef, ...] = ()
|
|
199
301
|
runtime_duration_ms: int | None = None
|
|
200
302
|
item_phases: dict[tuple[str, str], str | None] = {}
|
|
201
|
-
control_queue: asyncio.Queue[
|
|
303
|
+
control_queue: asyncio.Queue[
|
|
304
|
+
tuple[str, ResumePayload | None, asyncio.Future[None] | None]
|
|
305
|
+
] = asyncio.Queue()
|
|
202
306
|
self._control_queues[run_id] = control_queue
|
|
203
307
|
try:
|
|
204
308
|
tool_approval_mode = str(spec.request_config.get("tool_approval_mode") or "")
|
|
@@ -225,10 +329,29 @@ class StudioRunService:
|
|
|
225
329
|
metadata={
|
|
226
330
|
"invocation_id": run_id,
|
|
227
331
|
CONVERSATION_PREPROCESSING_METADATA_KEY: conversation_request,
|
|
228
|
-
**self._native_session_metadata(
|
|
332
|
+
**self._native_session_metadata(
|
|
333
|
+
spec.agent_id,
|
|
334
|
+
session,
|
|
335
|
+
str(
|
|
336
|
+
spec.request_config.get("provider_runtime_type")
|
|
337
|
+
or runtime_type
|
|
338
|
+
),
|
|
339
|
+
spec.build_id,
|
|
340
|
+
),
|
|
229
341
|
},
|
|
230
342
|
)
|
|
231
|
-
|
|
343
|
+
preparation = None
|
|
344
|
+
if provider_runtime_adapter:
|
|
345
|
+
if self.plugin_runtime is None:
|
|
346
|
+
raise StudioError(
|
|
347
|
+
"PLUGIN_RUNTIME_UNAVAILABLE", "Codex Provider Runtime 不可用",
|
|
348
|
+
status_code=503,
|
|
349
|
+
)
|
|
350
|
+
adapter = self.plugin_runtime.kernel_adapter_provider(spec)()
|
|
351
|
+
preparation = RuntimeStartPreparation(context=spec.launch_context, adapter=adapter)
|
|
352
|
+
handle = await self.executor.start(
|
|
353
|
+
spec.launch_context, request, preparation=preparation,
|
|
354
|
+
)
|
|
232
355
|
record.runtime_handle = handle.model_dump(mode="json")
|
|
233
356
|
self.event_store.save(record)
|
|
234
357
|
self._active_handles[run_id] = handle
|
|
@@ -236,6 +359,27 @@ class StudioRunService:
|
|
|
236
359
|
terminal_seen = False
|
|
237
360
|
should_resume = False
|
|
238
361
|
async for event in self.executor.stream(handle):
|
|
362
|
+
if (
|
|
363
|
+
isinstance(event, RunInterrupted)
|
|
364
|
+
and event.interaction_id
|
|
365
|
+
and record.status == RunStatus.WAITING_INPUT
|
|
366
|
+
and event.source.framework == "codex"
|
|
367
|
+
):
|
|
368
|
+
# Codex emits this companion event immediately after a
|
|
369
|
+
# live JSON-RPC approval request. The native turn stays
|
|
370
|
+
# attached and must be released through submit; trying
|
|
371
|
+
# to resume it deadlocks that request and later breaks
|
|
372
|
+
# the app-server pipe.
|
|
373
|
+
self._waiting_modes[run_id] = "live"
|
|
374
|
+
continue
|
|
375
|
+
if isinstance(event, InteractionResolved) and event.source.framework == "codex":
|
|
376
|
+
submitted = self._submitted_interactions.get(run_id, set())
|
|
377
|
+
provider_call_id = str(getattr(event.response, "call_id", None) or "")
|
|
378
|
+
if event.interaction_id in submitted or provider_call_id in submitted:
|
|
379
|
+
# submit_interaction atomically persists the public
|
|
380
|
+
# terminal fact and receipt. Ignore the provider's
|
|
381
|
+
# echo so replay never shows the approval twice.
|
|
382
|
+
continue
|
|
239
383
|
await persist(event)
|
|
240
384
|
if self._cancel_flags.get(run_id):
|
|
241
385
|
raise asyncio.CancelledError()
|
|
@@ -244,28 +388,28 @@ class StudioRunService:
|
|
|
244
388
|
elif (
|
|
245
389
|
isinstance(event, ItemUpdated)
|
|
246
390
|
and event.item_kind == "message"
|
|
247
|
-
and item_phases.get(
|
|
248
|
-
(event.scope_id, event.item_id), "final_answer"
|
|
249
|
-
)
|
|
391
|
+
and item_phases.get((event.scope_id, event.item_id), "final_answer")
|
|
250
392
|
== "final_answer"
|
|
251
393
|
):
|
|
252
394
|
text = event.update.text if isinstance(event.update, TextContent) else ""
|
|
395
|
+
item_key = (event.scope_id, event.item_id)
|
|
253
396
|
if event.op == "replace":
|
|
254
|
-
|
|
397
|
+
streamed_text_by_item[item_key] = text
|
|
255
398
|
else:
|
|
256
|
-
|
|
399
|
+
streamed_text_by_item[item_key] = (
|
|
400
|
+
streamed_text_by_item.get(item_key, "") + text
|
|
401
|
+
)
|
|
257
402
|
elif (
|
|
258
403
|
isinstance(event, ItemCompleted)
|
|
259
404
|
and event.item_kind == "message"
|
|
260
|
-
and item_phases.get(
|
|
261
|
-
(event.scope_id, event.item_id), "final_answer"
|
|
262
|
-
)
|
|
405
|
+
and item_phases.get((event.scope_id, event.item_id), "final_answer")
|
|
263
406
|
== "final_answer"
|
|
264
407
|
):
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
408
|
+
completed_text_by_item[(event.scope_id, event.item_id)] = "".join(
|
|
409
|
+
part.text
|
|
410
|
+
for part in event.snapshot.parts
|
|
411
|
+
if isinstance(part, TextContent)
|
|
412
|
+
)
|
|
269
413
|
elif isinstance(event, UsageReported):
|
|
270
414
|
record.usage = Usage(
|
|
271
415
|
input_tokens=event.input_tokens,
|
|
@@ -318,6 +462,7 @@ class StudioRunService:
|
|
|
318
462
|
elif isinstance(event, RunCompleted):
|
|
319
463
|
terminal_seen = True
|
|
320
464
|
record.status = RunStatus.COMPLETED
|
|
465
|
+
terminal_output_refs = event.output_refs
|
|
321
466
|
raw_duration = event.source.metadata.get("duration_ms")
|
|
322
467
|
if raw_duration is None:
|
|
323
468
|
metrics = event.source.metadata.get("metrics")
|
|
@@ -331,7 +476,7 @@ class StudioRunService:
|
|
|
331
476
|
record.status = RunStatus.COMPLETED
|
|
332
477
|
break
|
|
333
478
|
|
|
334
|
-
command, resume_payload = await control_queue.get()
|
|
479
|
+
command, resume_payload, submit_ack = await control_queue.get()
|
|
335
480
|
if command == "cancel" or self._cancel_flags.get(run_id):
|
|
336
481
|
raise asyncio.CancelledError()
|
|
337
482
|
if command != "resume":
|
|
@@ -342,11 +487,18 @@ class StudioRunService:
|
|
|
342
487
|
if native_thread_id
|
|
343
488
|
else ResumeTarget(kind="invocation_id", id=handle.run_id)
|
|
344
489
|
)
|
|
345
|
-
|
|
346
|
-
handle
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
490
|
+
try:
|
|
491
|
+
handle = await self.executor.resume(
|
|
492
|
+
handle,
|
|
493
|
+
target,
|
|
494
|
+
resume_payload,
|
|
495
|
+
)
|
|
496
|
+
except Exception as exc:
|
|
497
|
+
if submit_ack is not None and not submit_ack.done():
|
|
498
|
+
submit_ack.set_exception(exc)
|
|
499
|
+
raise
|
|
500
|
+
if submit_ack is not None and not submit_ack.done():
|
|
501
|
+
submit_ack.set_result(None)
|
|
350
502
|
self._active_handles[run_id] = handle
|
|
351
503
|
record.runtime_handle = handle.model_dump(mode="json")
|
|
352
504
|
record.status = RunStatus.RUNNING
|
|
@@ -360,7 +512,17 @@ class StudioRunService:
|
|
|
360
512
|
if on_event is not None:
|
|
361
513
|
on_event(resumed)
|
|
362
514
|
self.event_store.save(record)
|
|
363
|
-
|
|
515
|
+
text_by_item = {**streamed_text_by_item, **completed_text_by_item}
|
|
516
|
+
if terminal_output_refs:
|
|
517
|
+
terminal_parts = [
|
|
518
|
+
text_by_item.get((ref.scope_id, ref.item_id), "")
|
|
519
|
+
for ref in terminal_output_refs
|
|
520
|
+
]
|
|
521
|
+
record.output = "\n\n".join(part for part in terminal_parts if part)
|
|
522
|
+
else:
|
|
523
|
+
completed_parts = [part for part in completed_text_by_item.values() if part]
|
|
524
|
+
fallback_parts = [part for part in streamed_text_by_item.values() if part]
|
|
525
|
+
record.output = "\n\n".join(completed_parts or fallback_parts)
|
|
364
526
|
except asyncio.CancelledError:
|
|
365
527
|
cancel_result = "task_cancelled"
|
|
366
528
|
if handle is not None and self.executor.is_attached(handle):
|
|
@@ -426,7 +588,9 @@ class StudioRunService:
|
|
|
426
588
|
self._cancel_flags.pop(run_id, None)
|
|
427
589
|
self._control_queues.pop(run_id, None)
|
|
428
590
|
self._waiting_modes.pop(run_id, None)
|
|
591
|
+
self._submitted_interactions.pop(run_id, None)
|
|
429
592
|
record.completed_at = datetime.now(timezone.utc)
|
|
593
|
+
self._expire_unresolved_interactions(record, on_event=on_event)
|
|
430
594
|
if runtime_duration_ms is not None:
|
|
431
595
|
record.duration_ms = runtime_duration_ms
|
|
432
596
|
record.duration_source = "runtime"
|
|
@@ -437,9 +601,400 @@ class StudioRunService:
|
|
|
437
601
|
await self._sync_trace(record)
|
|
438
602
|
return record
|
|
439
603
|
|
|
604
|
+
async def _schedule_run(
|
|
605
|
+
self,
|
|
606
|
+
record: RunRecord,
|
|
607
|
+
user_input: str,
|
|
608
|
+
*,
|
|
609
|
+
on_event: Callable[[RunEvent], None] | None,
|
|
610
|
+
) -> RunRecord | None:
|
|
611
|
+
"""Persist real host tool receipts on the canonical conversation stream."""
|
|
612
|
+
started = time.monotonic()
|
|
613
|
+
|
|
614
|
+
def envelope(kind: str) -> dict[str, Any]:
|
|
615
|
+
result = _plugin_event_envelope(record, kind)
|
|
616
|
+
result["source"] = SourceRef(
|
|
617
|
+
framework="ksadk",
|
|
618
|
+
metadata={"runtime": "studio-scheduler", "agent_id": record.agent_id},
|
|
619
|
+
)
|
|
620
|
+
return result
|
|
621
|
+
|
|
622
|
+
async def publish(event: RuntimeEvent) -> None:
|
|
623
|
+
persisted = await self.runtime_events.append_one(record.session_id, event)
|
|
624
|
+
kind, data = project_runtime_event(
|
|
625
|
+
persisted, session_id=record.session_id, public_run_id=record.id
|
|
626
|
+
)
|
|
627
|
+
stored = self.event_store.append(record.id, kind, data)
|
|
628
|
+
if on_event is not None:
|
|
629
|
+
on_event(stored)
|
|
630
|
+
|
|
631
|
+
call_id = None
|
|
632
|
+
try:
|
|
633
|
+
intent = await self.schedule_assistant.plan(
|
|
634
|
+
record.agent_id,
|
|
635
|
+
user_input,
|
|
636
|
+
record.session_id,
|
|
637
|
+
build_id=record.build_id,
|
|
638
|
+
model_name=record.model,
|
|
639
|
+
)
|
|
640
|
+
if self._cancel_flags.get(record.id):
|
|
641
|
+
raise asyncio.CancelledError()
|
|
642
|
+
if intent.action == "ignore":
|
|
643
|
+
return None
|
|
644
|
+
record.status = RunStatus.RUNNING
|
|
645
|
+
record.started_at = datetime.now(timezone.utc)
|
|
646
|
+
record.runtime_handle = {"provider": "studio-scheduler"}
|
|
647
|
+
self.event_store.save(record)
|
|
648
|
+
await publish(RunStarted(**envelope("run.started"), status="running"))
|
|
649
|
+
call_id = f"{record.id}:schedule"
|
|
650
|
+
arguments = intent.model_dump(mode="json", exclude_none=True)
|
|
651
|
+
await publish(
|
|
652
|
+
ItemStarted(
|
|
653
|
+
**envelope("tool.started"),
|
|
654
|
+
item_id=call_id,
|
|
655
|
+
item_kind="tool_call",
|
|
656
|
+
initial=ContentSnapshot(
|
|
657
|
+
parts=(
|
|
658
|
+
ToolCallContent(
|
|
659
|
+
part_id=f"{call_id}:call",
|
|
660
|
+
call_id=call_id,
|
|
661
|
+
name="studio_schedule",
|
|
662
|
+
arguments=arguments,
|
|
663
|
+
),
|
|
664
|
+
)
|
|
665
|
+
),
|
|
666
|
+
)
|
|
667
|
+
)
|
|
668
|
+
result = await self.schedule_assistant.execute(
|
|
669
|
+
record.agent_id, record.session_id, intent
|
|
670
|
+
)
|
|
671
|
+
await publish(
|
|
672
|
+
ItemCompleted(
|
|
673
|
+
**envelope("tool.completed"),
|
|
674
|
+
item_id=call_id,
|
|
675
|
+
item_kind="tool_call",
|
|
676
|
+
snapshot=ContentSnapshot(
|
|
677
|
+
parts=(
|
|
678
|
+
ToolResultContent(
|
|
679
|
+
part_id=f"{call_id}:result", call_id=call_id, result=result
|
|
680
|
+
),
|
|
681
|
+
)
|
|
682
|
+
),
|
|
683
|
+
)
|
|
684
|
+
)
|
|
685
|
+
output = result["message"]
|
|
686
|
+
item_id, part_id = f"{record.id}:assistant", f"{record.id}:text"
|
|
687
|
+
await publish(
|
|
688
|
+
ItemStarted(
|
|
689
|
+
**envelope("item.started"),
|
|
690
|
+
item_id=item_id,
|
|
691
|
+
item_kind="message",
|
|
692
|
+
phase="final_answer",
|
|
693
|
+
)
|
|
694
|
+
)
|
|
695
|
+
await publish(
|
|
696
|
+
ItemUpdated(
|
|
697
|
+
**envelope("item.updated"),
|
|
698
|
+
item_id=item_id,
|
|
699
|
+
item_kind="message",
|
|
700
|
+
op="append",
|
|
701
|
+
update=TextContent(part_id=part_id, text=output),
|
|
702
|
+
)
|
|
703
|
+
)
|
|
704
|
+
await publish(
|
|
705
|
+
ItemCompleted(
|
|
706
|
+
**envelope("item.completed"),
|
|
707
|
+
item_id=item_id,
|
|
708
|
+
item_kind="message",
|
|
709
|
+
snapshot=ContentSnapshot(parts=(TextContent(part_id=part_id, text=output),)),
|
|
710
|
+
)
|
|
711
|
+
)
|
|
712
|
+
if intent._usage is not None and intent._usage.reported:
|
|
713
|
+
record.usage = intent._usage.model_copy(update={"source": "studio-scheduler"})
|
|
714
|
+
await publish(
|
|
715
|
+
UsageReported(
|
|
716
|
+
**envelope("usage.reported"),
|
|
717
|
+
input_tokens=record.usage.input_tokens,
|
|
718
|
+
output_tokens=record.usage.output_tokens,
|
|
719
|
+
total_tokens=record.usage.total_tokens,
|
|
720
|
+
cached_tokens=record.usage.cached_input_tokens,
|
|
721
|
+
reasoning_tokens=record.usage.reasoning_output_tokens,
|
|
722
|
+
)
|
|
723
|
+
)
|
|
724
|
+
await publish(
|
|
725
|
+
RunCompleted(
|
|
726
|
+
**envelope("run.completed"),
|
|
727
|
+
status="completed",
|
|
728
|
+
output_refs=(OutputRef(scope_id=record.id, item_id=item_id, part_id=part_id),),
|
|
729
|
+
)
|
|
730
|
+
)
|
|
731
|
+
record.output = output
|
|
732
|
+
record.status = RunStatus.COMPLETED
|
|
733
|
+
except asyncio.CancelledError:
|
|
734
|
+
record.status = RunStatus.CANCELLED
|
|
735
|
+
record.completed_at = datetime.now(timezone.utc)
|
|
736
|
+
self._cancel_flags.pop(record.id, None)
|
|
737
|
+
await publish(
|
|
738
|
+
RunCanceled(**envelope("run.canceled"), status="canceled", reason="cancelled")
|
|
739
|
+
)
|
|
740
|
+
self.event_store.save(record)
|
|
741
|
+
raise
|
|
742
|
+
except Exception as error:
|
|
743
|
+
code = str(getattr(error, "code", "SCHEDULE_REQUEST_FAILED"))
|
|
744
|
+
record.status = RunStatus.FAILED
|
|
745
|
+
record.error = {"code": code, "message": str(error)}
|
|
746
|
+
if call_id is not None:
|
|
747
|
+
await publish(
|
|
748
|
+
ItemFailed(
|
|
749
|
+
**envelope("tool.failed"),
|
|
750
|
+
item_id=call_id,
|
|
751
|
+
item_kind="tool_call",
|
|
752
|
+
error=ErrorInfo(
|
|
753
|
+
code=code,
|
|
754
|
+
message=str(error),
|
|
755
|
+
source="studio-scheduler",
|
|
756
|
+
scope_id=record.id,
|
|
757
|
+
),
|
|
758
|
+
)
|
|
759
|
+
)
|
|
760
|
+
await publish(
|
|
761
|
+
RunFailed(
|
|
762
|
+
**envelope("run.failed"),
|
|
763
|
+
status="failed",
|
|
764
|
+
error=ErrorInfo(
|
|
765
|
+
code=code, message=str(error), source="studio-scheduler", scope_id=record.id
|
|
766
|
+
),
|
|
767
|
+
)
|
|
768
|
+
)
|
|
769
|
+
record.completed_at = datetime.now(timezone.utc)
|
|
770
|
+
record.duration_ms = int((time.monotonic() - started) * 1000)
|
|
771
|
+
record.duration_source = "studio"
|
|
772
|
+
self._cancel_flags.pop(record.id, None)
|
|
773
|
+
self.event_store.save(record)
|
|
774
|
+
await self._sync_trace(record)
|
|
775
|
+
return record
|
|
776
|
+
|
|
440
777
|
async def _sync_trace(self, record: RunRecord) -> None:
|
|
441
778
|
self.event_store.trace_store.sync(record, await self.events(record.id))
|
|
442
779
|
|
|
780
|
+
async def _plugin_run(
|
|
781
|
+
self,
|
|
782
|
+
spec: StudioRunSpec,
|
|
783
|
+
user_input: str,
|
|
784
|
+
*,
|
|
785
|
+
record: RunRecord,
|
|
786
|
+
on_event: Callable[[RunEvent], None] | None,
|
|
787
|
+
) -> RunRecord:
|
|
788
|
+
"""Execute a composed Build without translating it into ADK/LangGraph.
|
|
789
|
+
|
|
790
|
+
PluginHost retains the activation under the Studio session id. Harness
|
|
791
|
+
providers write canonical RuntimeEvents directly; providers that only
|
|
792
|
+
implement the minimum request/result protocol receive a canonical
|
|
793
|
+
envelope here so Studio still has one durable conversation/event model.
|
|
794
|
+
"""
|
|
795
|
+
|
|
796
|
+
started = time.monotonic()
|
|
797
|
+
record.status = RunStatus.RUNNING
|
|
798
|
+
record.started_at = datetime.now(timezone.utc)
|
|
799
|
+
record.runtime_handle = {
|
|
800
|
+
"provider": "pluginhost",
|
|
801
|
+
"bundleDigest": str(spec.request_config.get("plugin_bundle_digest") or ""),
|
|
802
|
+
"activationKey": record.session_id,
|
|
803
|
+
}
|
|
804
|
+
self.event_store.save(record)
|
|
805
|
+
rows_before = await self.session_service.get_events(record.session_id)
|
|
806
|
+
after_seq = max((int(row.seq_id or 0) for row in rows_before), default=0)
|
|
807
|
+
|
|
808
|
+
async def publish(events: list[RuntimeEvent]) -> None:
|
|
809
|
+
for runtime_event in events:
|
|
810
|
+
event_type, data = project_runtime_event(
|
|
811
|
+
runtime_event,
|
|
812
|
+
session_id=record.session_id,
|
|
813
|
+
)
|
|
814
|
+
stored = self.event_store.append(record.id, event_type, data)
|
|
815
|
+
if on_event is not None:
|
|
816
|
+
on_event(stored)
|
|
817
|
+
|
|
818
|
+
try:
|
|
819
|
+
if self.plugin_runtime is None:
|
|
820
|
+
raise StudioError(
|
|
821
|
+
"PLUGIN_RUNTIME_UNAVAILABLE",
|
|
822
|
+
"Studio 尚未配置 PluginHost Runtime",
|
|
823
|
+
status_code=503,
|
|
824
|
+
)
|
|
825
|
+
request_metadata = {
|
|
826
|
+
key: value
|
|
827
|
+
for key, value in {
|
|
828
|
+
"tool_approval_mode": spec.request_config.get("tool_approval_mode"),
|
|
829
|
+
"collaboration_mode": spec.request_config.get("collaboration_mode"),
|
|
830
|
+
"goal_objective": spec.request_config.get("goal_objective"),
|
|
831
|
+
"reasoning_effort": spec.request_config.get("effort"),
|
|
832
|
+
}.items()
|
|
833
|
+
if value not in {None, ""}
|
|
834
|
+
}
|
|
835
|
+
result = await self.plugin_runtime.execute(
|
|
836
|
+
spec,
|
|
837
|
+
{
|
|
838
|
+
"user_id": "local-user",
|
|
839
|
+
"session_id": record.session_id,
|
|
840
|
+
"invocation_id": record.id,
|
|
841
|
+
"messages": [{"role": "user", "content": user_input}],
|
|
842
|
+
"model": spec.model,
|
|
843
|
+
"request_metadata": request_metadata,
|
|
844
|
+
},
|
|
845
|
+
session_id=record.session_id,
|
|
846
|
+
)
|
|
847
|
+
if result.session_id != record.session_id:
|
|
848
|
+
raise StudioError(
|
|
849
|
+
"PLUGIN_SESSION_MISMATCH",
|
|
850
|
+
"AgentProvider 返回了不同的 Session",
|
|
851
|
+
status_code=502,
|
|
852
|
+
details={
|
|
853
|
+
"expected": record.session_id,
|
|
854
|
+
"actual": result.session_id,
|
|
855
|
+
},
|
|
856
|
+
)
|
|
857
|
+
canonical = await self._plugin_events_after(
|
|
858
|
+
record.session_id,
|
|
859
|
+
after_seq=after_seq,
|
|
860
|
+
run_id=record.id,
|
|
861
|
+
)
|
|
862
|
+
if not canonical:
|
|
863
|
+
canonical = await self._persist_plugin_result_events(
|
|
864
|
+
record,
|
|
865
|
+
result.output_text,
|
|
866
|
+
usage=result.usage,
|
|
867
|
+
)
|
|
868
|
+
await publish(canonical)
|
|
869
|
+
record.output = result.output_text
|
|
870
|
+
record.status = RunStatus.COMPLETED
|
|
871
|
+
_apply_plugin_usage(record, result.usage)
|
|
872
|
+
except asyncio.CancelledError:
|
|
873
|
+
record.status = RunStatus.CANCELLED
|
|
874
|
+
record.error = {"code": "RUN_CANCELLED", "message": "运行已取消"}
|
|
875
|
+
cancelled = await self.runtime_events.append_one(
|
|
876
|
+
record.session_id,
|
|
877
|
+
RunCanceled(
|
|
878
|
+
**_plugin_event_envelope(record, "run.canceled"),
|
|
879
|
+
status="canceled",
|
|
880
|
+
reason="cancelled",
|
|
881
|
+
),
|
|
882
|
+
)
|
|
883
|
+
await publish([cancelled])
|
|
884
|
+
raise
|
|
885
|
+
except Exception as exc: # noqa: BLE001 - plugin boundary is typed below
|
|
886
|
+
record.status = RunStatus.FAILED
|
|
887
|
+
code = str(getattr(exc, "code", "PLUGIN_RUNTIME_FAILED"))
|
|
888
|
+
record.error = {"code": code, "message": str(exc)}
|
|
889
|
+
canonical = await self._plugin_events_after(
|
|
890
|
+
record.session_id,
|
|
891
|
+
after_seq=after_seq,
|
|
892
|
+
run_id=record.id,
|
|
893
|
+
)
|
|
894
|
+
if canonical:
|
|
895
|
+
await publish(canonical)
|
|
896
|
+
else:
|
|
897
|
+
failed = await self.runtime_events.append_one(
|
|
898
|
+
record.session_id,
|
|
899
|
+
RunFailed(
|
|
900
|
+
**_plugin_event_envelope(record, "run.failed"),
|
|
901
|
+
status="failed",
|
|
902
|
+
error=ErrorInfo(
|
|
903
|
+
code=code,
|
|
904
|
+
message=str(exc),
|
|
905
|
+
source="pluginhost",
|
|
906
|
+
scope_id=record.id,
|
|
907
|
+
),
|
|
908
|
+
),
|
|
909
|
+
)
|
|
910
|
+
await publish([failed])
|
|
911
|
+
finally:
|
|
912
|
+
record.completed_at = datetime.now(timezone.utc)
|
|
913
|
+
record.duration_ms = int((time.monotonic() - started) * 1000)
|
|
914
|
+
record.duration_source = "studio"
|
|
915
|
+
self.event_store.save(record)
|
|
916
|
+
await self._sync_trace(record)
|
|
917
|
+
return record
|
|
918
|
+
|
|
919
|
+
async def _plugin_events_after(
|
|
920
|
+
self,
|
|
921
|
+
session_id: str,
|
|
922
|
+
*,
|
|
923
|
+
after_seq: int,
|
|
924
|
+
run_id: str,
|
|
925
|
+
) -> list[RuntimeEvent]:
|
|
926
|
+
rows = await self.session_service.get_events(
|
|
927
|
+
session_id,
|
|
928
|
+
after_seq_id=after_seq,
|
|
929
|
+
)
|
|
930
|
+
events: list[RuntimeEvent] = []
|
|
931
|
+
for row in rows:
|
|
932
|
+
event = session_event_to_runtime_event(row)
|
|
933
|
+
if event is not None and event.run_id == run_id:
|
|
934
|
+
events.append(event)
|
|
935
|
+
return events
|
|
936
|
+
|
|
937
|
+
async def _persist_plugin_result_events(
|
|
938
|
+
self,
|
|
939
|
+
record: RunRecord,
|
|
940
|
+
output_text: str,
|
|
941
|
+
*,
|
|
942
|
+
usage: Mapping[str, Any],
|
|
943
|
+
) -> list[RuntimeEvent]:
|
|
944
|
+
item_id = f"{record.id}:assistant"
|
|
945
|
+
part_id = f"{record.id}:text"
|
|
946
|
+
snapshot = ContentSnapshot(parts=(TextContent(part_id=part_id, text=output_text),))
|
|
947
|
+
events: list[RuntimeEvent] = [
|
|
948
|
+
RunStarted(
|
|
949
|
+
**_plugin_event_envelope(record, "run.started"),
|
|
950
|
+
status="running",
|
|
951
|
+
),
|
|
952
|
+
ItemStarted(
|
|
953
|
+
**_plugin_event_envelope(record, "item.started"),
|
|
954
|
+
item_id=item_id,
|
|
955
|
+
item_kind="message",
|
|
956
|
+
phase="final_answer",
|
|
957
|
+
),
|
|
958
|
+
ItemUpdated(
|
|
959
|
+
**_plugin_event_envelope(record, "item.updated"),
|
|
960
|
+
item_id=item_id,
|
|
961
|
+
item_kind="message",
|
|
962
|
+
op="append",
|
|
963
|
+
update=TextContent(part_id=part_id, text=output_text),
|
|
964
|
+
),
|
|
965
|
+
ItemCompleted(
|
|
966
|
+
**_plugin_event_envelope(record, "item.completed"),
|
|
967
|
+
item_id=item_id,
|
|
968
|
+
item_kind="message",
|
|
969
|
+
snapshot=snapshot,
|
|
970
|
+
),
|
|
971
|
+
]
|
|
972
|
+
normalized = _normalized_plugin_usage(usage)
|
|
973
|
+
if normalized["reported"]:
|
|
974
|
+
events.append(
|
|
975
|
+
UsageReported(
|
|
976
|
+
**_plugin_event_envelope(record, "usage.reported"),
|
|
977
|
+
input_tokens=normalized["input_tokens"],
|
|
978
|
+
output_tokens=normalized["output_tokens"],
|
|
979
|
+
total_tokens=normalized["total_tokens"],
|
|
980
|
+
cached_tokens=normalized["cached_tokens"],
|
|
981
|
+
reasoning_tokens=normalized["reasoning_tokens"],
|
|
982
|
+
)
|
|
983
|
+
)
|
|
984
|
+
events.append(
|
|
985
|
+
RunCompleted(
|
|
986
|
+
**_plugin_event_envelope(record, "run.completed"),
|
|
987
|
+
status="completed",
|
|
988
|
+
output_refs=(
|
|
989
|
+
OutputRef(
|
|
990
|
+
scope_id=record.id,
|
|
991
|
+
item_id=item_id,
|
|
992
|
+
part_id=part_id,
|
|
993
|
+
),
|
|
994
|
+
),
|
|
995
|
+
)
|
|
996
|
+
)
|
|
997
|
+
return [await self.runtime_events.append_one(record.session_id, event) for event in events]
|
|
443
998
|
|
|
444
999
|
async def _kernel_run(
|
|
445
1000
|
self,
|
|
@@ -448,6 +1003,7 @@ class StudioRunService:
|
|
|
448
1003
|
*,
|
|
449
1004
|
record: RunRecord,
|
|
450
1005
|
on_event: Callable[[RunEvent], None] | None,
|
|
1006
|
+
kernel_runtime: Any,
|
|
451
1007
|
) -> RunRecord:
|
|
452
1008
|
"""kernel 路径(灰度 opt-in):Studio run -> AgentControlCommand -> receipt。
|
|
453
1009
|
|
|
@@ -464,13 +1020,17 @@ class StudioRunService:
|
|
|
464
1020
|
trusted = _kernel_ingress.trusted_context(
|
|
465
1021
|
source_kind="studio",
|
|
466
1022
|
source_ref=record.id,
|
|
1023
|
+
# The Worker polls the concrete Kernel Runtime instance, not
|
|
1024
|
+
# whichever synthetic ``local-agent`` happened to be inferred
|
|
1025
|
+
# from a Studio request. A Studio Build may use the Kernel
|
|
1026
|
+
# path only after ``_kernel_runtime_for_spec`` established that
|
|
1027
|
+
# it is this Runtime's exact Agent/Runtime binding.
|
|
1028
|
+
tenant_id=str(kernel_runtime.config.tenant_id),
|
|
1029
|
+
agent_instance_id=str(kernel_runtime.config.agent_instance_id),
|
|
467
1030
|
session_id=record.session_id,
|
|
468
1031
|
operations=("enqueue",),
|
|
469
|
-
launch_context=spec.launch_context,
|
|
470
|
-
)
|
|
471
|
-
idempotency_key = str(
|
|
472
|
-
(spec.request_config or {}).get("idempotency_key") or record.id
|
|
473
1032
|
)
|
|
1033
|
+
idempotency_key = str((spec.request_config or {}).get("idempotency_key") or record.id)
|
|
474
1034
|
command = _kernel_ingress.map_studio_request(
|
|
475
1035
|
session_id=record.session_id,
|
|
476
1036
|
idempotency_key=idempotency_key,
|
|
@@ -478,9 +1038,7 @@ class StudioRunService:
|
|
|
478
1038
|
run_id=record.id,
|
|
479
1039
|
trusted=trusted,
|
|
480
1040
|
)
|
|
481
|
-
receipt = await _kernel_ingress.submit_command(
|
|
482
|
-
command, permit=trusted.permit
|
|
483
|
-
)
|
|
1041
|
+
receipt = await _kernel_ingress.submit_command(command, permit=trusted.permit)
|
|
484
1042
|
if receipt.status not in ("accepted", "duplicate"):
|
|
485
1043
|
record.status = RunStatus.FAILED
|
|
486
1044
|
record.error = {
|
|
@@ -513,6 +1071,7 @@ class StudioRunService:
|
|
|
513
1071
|
self.event_store.save(record)
|
|
514
1072
|
await self._sync_trace(record)
|
|
515
1073
|
return record
|
|
1074
|
+
|
|
516
1075
|
except Exception as exc: # noqa: BLE001
|
|
517
1076
|
logger.exception("Studio kernel ingress failed for run %s", record.id)
|
|
518
1077
|
record.status = RunStatus.FAILED
|
|
@@ -526,12 +1085,58 @@ class StudioRunService:
|
|
|
526
1085
|
await self._sync_trace(record)
|
|
527
1086
|
return record
|
|
528
1087
|
|
|
1088
|
+
@staticmethod
|
|
1089
|
+
def _kernel_runtime_for_spec(spec: StudioRunSpec) -> Any | None:
|
|
1090
|
+
"""Return the active Kernel Runtime only for its bound Studio Build.
|
|
1091
|
+
|
|
1092
|
+
An in-process AgentKernel owns one concrete RuntimeAdapter factory and
|
|
1093
|
+
its immutable startup defaults. Merely observing that a Kernel is
|
|
1094
|
+
enabled is therefore insufficient: sending an arbitrary Studio Build
|
|
1095
|
+
through it can silently execute the default Adapter instead. When the
|
|
1096
|
+
binding is not exact, the existing direct Studio path remains the
|
|
1097
|
+
compatible local behaviour; it is safer than manufacturing a false
|
|
1098
|
+
AgentControl/Scheduler success.
|
|
1099
|
+
"""
|
|
1100
|
+
|
|
1101
|
+
from ksadk.kernel.bootstrap import get_agent_kernel_runtime
|
|
1102
|
+
from ksadk.kernel.ingress import kernel_route_active
|
|
1103
|
+
|
|
1104
|
+
if not kernel_route_active():
|
|
1105
|
+
return None
|
|
1106
|
+
runtime = get_agent_kernel_runtime()
|
|
1107
|
+
if runtime is None:
|
|
1108
|
+
return None
|
|
1109
|
+
active_context = getattr(runtime.config, "launch_context", None)
|
|
1110
|
+
if active_context is None:
|
|
1111
|
+
return None
|
|
1112
|
+
if str(getattr(active_context, "runtime_type", "")).strip().lower() != (
|
|
1113
|
+
spec.launch_context.runtime_type.strip().lower()
|
|
1114
|
+
):
|
|
1115
|
+
return None
|
|
1116
|
+
try:
|
|
1117
|
+
active_project = Path(active_context.project_dir).resolve()
|
|
1118
|
+
build_project = Path(spec.launch_context.project_dir).resolve()
|
|
1119
|
+
except (OSError, TypeError, ValueError):
|
|
1120
|
+
return None
|
|
1121
|
+
if active_project != build_project:
|
|
1122
|
+
return None
|
|
1123
|
+
defaults = getattr(runtime.config, "start_request_defaults", {}) or {}
|
|
1124
|
+
bound_agent_id = str(defaults.get("agent_id") or runtime.config.agent_instance_id).strip()
|
|
1125
|
+
if bound_agent_id != spec.agent_id:
|
|
1126
|
+
return None
|
|
1127
|
+
declared_instance = str(
|
|
1128
|
+
(spec.launch_context.config or {}).get("agent_instance_id") or ""
|
|
1129
|
+
).strip()
|
|
1130
|
+
if declared_instance and declared_instance != str(runtime.config.agent_instance_id):
|
|
1131
|
+
return None
|
|
1132
|
+
return runtime
|
|
1133
|
+
|
|
529
1134
|
async def cancel_run(self, run_id: str) -> dict[str, str]:
|
|
530
1135
|
"""Request cancellation; the flag and executor perform the actual stop."""
|
|
531
1136
|
self._cancel_flags[run_id] = True
|
|
532
1137
|
queue = self._control_queues.get(run_id)
|
|
533
1138
|
if queue is not None:
|
|
534
|
-
queue.put_nowait(("cancel", None))
|
|
1139
|
+
queue.put_nowait(("cancel", None, None))
|
|
535
1140
|
handle = self._active_handles.get(run_id)
|
|
536
1141
|
if handle is not None and self.executor.is_attached(handle):
|
|
537
1142
|
try:
|
|
@@ -582,7 +1187,7 @@ class StudioRunService:
|
|
|
582
1187
|
"Studio 进程已重启,当前暂停点无法恢复",
|
|
583
1188
|
status_code=409,
|
|
584
1189
|
)
|
|
585
|
-
queue.put_nowait(("resume", ResumePayload(kind="free_text", data="继续运行")))
|
|
1190
|
+
queue.put_nowait(("resume", ResumePayload(kind="free_text", data="继续运行"), None))
|
|
586
1191
|
return {"runId": run_id, "status": "resuming"}
|
|
587
1192
|
|
|
588
1193
|
async def submit_interaction(
|
|
@@ -592,28 +1197,73 @@ class StudioRunService:
|
|
|
592
1197
|
*,
|
|
593
1198
|
name: str,
|
|
594
1199
|
data: dict[str, Any] | None = None,
|
|
1200
|
+
expected_revision: int,
|
|
1201
|
+
idempotency_key: str,
|
|
595
1202
|
) -> dict[str, Any]:
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
"
|
|
601
|
-
|
|
1203
|
+
lock = self._interaction_locks.setdefault(run_id, asyncio.Lock())
|
|
1204
|
+
payload_data = dict(data or {})
|
|
1205
|
+
digest = hashlib.sha256(
|
|
1206
|
+
json.dumps(
|
|
1207
|
+
{"name": name, "data": payload_data},
|
|
1208
|
+
ensure_ascii=False,
|
|
1209
|
+
sort_keys=True,
|
|
1210
|
+
separators=(",", ":"),
|
|
1211
|
+
).encode("utf-8")
|
|
1212
|
+
).hexdigest()
|
|
1213
|
+
async with lock:
|
|
1214
|
+
return await self._submit_interaction_locked(
|
|
1215
|
+
run_id,
|
|
1216
|
+
interaction_id,
|
|
1217
|
+
name=name,
|
|
1218
|
+
data=payload_data,
|
|
1219
|
+
expected_revision=expected_revision,
|
|
1220
|
+
idempotency_key=idempotency_key,
|
|
1221
|
+
request_digest=digest,
|
|
602
1222
|
)
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
1223
|
+
|
|
1224
|
+
async def _submit_interaction_locked(
|
|
1225
|
+
self,
|
|
1226
|
+
run_id: str,
|
|
1227
|
+
interaction_id: str,
|
|
1228
|
+
*,
|
|
1229
|
+
name: str,
|
|
1230
|
+
data: dict[str, Any],
|
|
1231
|
+
expected_revision: int,
|
|
1232
|
+
idempotency_key: str,
|
|
1233
|
+
request_digest: str,
|
|
1234
|
+
) -> dict[str, Any]:
|
|
1235
|
+
events = self.event_store.events(run_id)
|
|
1236
|
+
prior = next(
|
|
1237
|
+
(
|
|
1238
|
+
event
|
|
1239
|
+
for event in reversed(events)
|
|
1240
|
+
if event.type == "a2ui.action"
|
|
1241
|
+
and str(event.data.get("interactionId") or "") == interaction_id
|
|
1242
|
+
and _interaction_revision(event.data) > 0
|
|
1243
|
+
),
|
|
1244
|
+
None,
|
|
1245
|
+
)
|
|
1246
|
+
if prior is not None:
|
|
1247
|
+
if str(prior.data.get("idempotencyKey") or "") != idempotency_key:
|
|
1248
|
+
raise StudioError(
|
|
1249
|
+
"INTERACTION_ALREADY_RESOLVED",
|
|
1250
|
+
"该交互已由其他请求处理",
|
|
1251
|
+
status_code=409,
|
|
1252
|
+
)
|
|
1253
|
+
if str(prior.data.get("requestDigest") or "") != request_digest:
|
|
1254
|
+
raise StudioError(
|
|
1255
|
+
"INTERACTION_IDEMPOTENCY_CONFLICT",
|
|
1256
|
+
"同一幂等键不能提交不同内容",
|
|
1257
|
+
status_code=409,
|
|
1258
|
+
)
|
|
1259
|
+
# A receipt replay is read-only. The run may already be waiting
|
|
1260
|
+
# for a different interaction; never clear that newer wait state.
|
|
1261
|
+
return dict(prior.data.get("receipt") or {})
|
|
612
1262
|
|
|
613
1263
|
interaction = next(
|
|
614
1264
|
(
|
|
615
1265
|
event
|
|
616
|
-
for event in reversed(
|
|
1266
|
+
for event in reversed(events)
|
|
617
1267
|
if event.type == "a2ui.interaction"
|
|
618
1268
|
and str(event.data.get("interactionId") or event.data.get("interaction_id") or "")
|
|
619
1269
|
== interaction_id
|
|
@@ -622,11 +1272,73 @@ class StudioRunService:
|
|
|
622
1272
|
)
|
|
623
1273
|
if interaction is None:
|
|
624
1274
|
raise StudioError("INTERACTION_NOT_FOUND", "交互请求不存在", status_code=404)
|
|
1275
|
+
revision = _interaction_revision(interaction.data)
|
|
1276
|
+
if revision < 1:
|
|
1277
|
+
raise StudioError(
|
|
1278
|
+
"INTERACTION_READ_ONLY",
|
|
1279
|
+
"交互请求缺少权威 revision,只能查看不能操作",
|
|
1280
|
+
status_code=409,
|
|
1281
|
+
)
|
|
1282
|
+
if expected_revision != revision:
|
|
1283
|
+
raise StudioError(
|
|
1284
|
+
"INTERACTION_REVISION_MISMATCH",
|
|
1285
|
+
"交互版本已变化,请刷新后重试",
|
|
1286
|
+
status_code=409,
|
|
1287
|
+
details={"expectedRevision": expected_revision, "actualRevision": revision},
|
|
1288
|
+
)
|
|
1289
|
+
record = self.event_store.get(run_id)
|
|
1290
|
+
if record.status != RunStatus.WAITING_INPUT:
|
|
1291
|
+
raise StudioError(
|
|
1292
|
+
"INTERACTION_NOT_PENDING",
|
|
1293
|
+
"该 Run 当前没有等待中的交互",
|
|
1294
|
+
status_code=409,
|
|
1295
|
+
)
|
|
625
1296
|
kind = str(interaction.data.get("kind") or "form")
|
|
626
|
-
|
|
1297
|
+
if kind != "approval" and name not in {"cancel", "skip"}:
|
|
1298
|
+
schema = interaction.data.get("inputSchema") or {}
|
|
1299
|
+
errors = list(Draft202012Validator(schema).iter_errors(data))
|
|
1300
|
+
if errors:
|
|
1301
|
+
raise StudioError(
|
|
1302
|
+
"INTERACTION_RESPONSE_INVALID",
|
|
1303
|
+
"请填写必填问题后再提交。",
|
|
1304
|
+
status_code=422,
|
|
1305
|
+
)
|
|
1306
|
+
provider_call_id = str(
|
|
1307
|
+
interaction.data.get("callId") or interaction.data.get("call_id") or ""
|
|
1308
|
+
)
|
|
1309
|
+
if kind == "approval" and not provider_call_id:
|
|
1310
|
+
approval_request = next(
|
|
1311
|
+
(
|
|
1312
|
+
event
|
|
1313
|
+
for event in reversed(events)
|
|
1314
|
+
if event.type == "approval.requested"
|
|
1315
|
+
and str(event.data.get("approvalId") or event.data.get("interactionId") or "")
|
|
1316
|
+
== interaction_id
|
|
1317
|
+
),
|
|
1318
|
+
None,
|
|
1319
|
+
)
|
|
1320
|
+
if approval_request is not None:
|
|
1321
|
+
provider_call_id = str(
|
|
1322
|
+
approval_request.data.get("callId")
|
|
1323
|
+
or approval_request.data.get("call_id")
|
|
1324
|
+
or ""
|
|
1325
|
+
)
|
|
1326
|
+
provider_call_id = provider_call_id or interaction_id
|
|
1327
|
+
submitted = self._submitted_interactions.setdefault(run_id, set())
|
|
1328
|
+
submitted.update({interaction_id, provider_call_id})
|
|
1329
|
+
decision = str(data.get("decision") or name)
|
|
1330
|
+
if kind == "approval" and record.runtime_type == "codex":
|
|
1331
|
+
# The shared Web action is named `reject`; Codex app-server calls
|
|
1332
|
+
# the same decision `deny`/`decline`. Do not let a UI alias fall
|
|
1333
|
+
# through as an unsupported native approval decision.
|
|
1334
|
+
decision = {
|
|
1335
|
+
"reject": "deny",
|
|
1336
|
+
"rejected": "deny",
|
|
1337
|
+
}.get(decision.lower(), decision)
|
|
1338
|
+
payload_data = {**data, "decision": decision}
|
|
627
1339
|
payload = ResumePayload(
|
|
628
1340
|
kind="approval_decision" if kind == "approval" else "hitl_answer",
|
|
629
|
-
call_id=
|
|
1341
|
+
call_id=provider_call_id,
|
|
630
1342
|
data=payload_data,
|
|
631
1343
|
)
|
|
632
1344
|
mode = self._waiting_modes.get(run_id)
|
|
@@ -635,8 +1347,19 @@ class StudioRunService:
|
|
|
635
1347
|
if handle is None or not self.executor.is_attached(handle):
|
|
636
1348
|
raise StudioError("INTERACTION_EXPIRED", "运行时交互已失效", status_code=409)
|
|
637
1349
|
try:
|
|
638
|
-
await
|
|
639
|
-
|
|
1350
|
+
await asyncio.wait_for(
|
|
1351
|
+
self.executor.submit(handle, payload),
|
|
1352
|
+
timeout=_INTERACTION_SUBMIT_TIMEOUT_SECONDS,
|
|
1353
|
+
)
|
|
1354
|
+
except TimeoutError as exc:
|
|
1355
|
+
submitted.difference_update({interaction_id, provider_call_id})
|
|
1356
|
+
raise StudioError(
|
|
1357
|
+
"INTERACTION_SUBMIT_FAILED",
|
|
1358
|
+
"运行时未及时确认交互提交,请重试",
|
|
1359
|
+
status_code=409,
|
|
1360
|
+
) from exc
|
|
1361
|
+
except Exception as exc: # noqa: BLE001 - provider errors are user-safe here
|
|
1362
|
+
submitted.difference_update({interaction_id, provider_call_id})
|
|
640
1363
|
raise StudioError(
|
|
641
1364
|
"INTERACTION_SUBMIT_FAILED",
|
|
642
1365
|
str(exc),
|
|
@@ -646,45 +1369,143 @@ class StudioRunService:
|
|
|
646
1369
|
queue = self._control_queues.get(run_id)
|
|
647
1370
|
if queue is None:
|
|
648
1371
|
raise StudioError("INTERACTION_EXPIRED", "运行时交互已失效", status_code=409)
|
|
649
|
-
|
|
1372
|
+
submit_ack = asyncio.get_running_loop().create_future()
|
|
1373
|
+
queue.put_nowait(("resume", payload, submit_ack))
|
|
1374
|
+
try:
|
|
1375
|
+
await asyncio.wait_for(
|
|
1376
|
+
submit_ack,
|
|
1377
|
+
timeout=_INTERACTION_SUBMIT_TIMEOUT_SECONDS,
|
|
1378
|
+
)
|
|
1379
|
+
except TimeoutError as exc:
|
|
1380
|
+
submitted.difference_update({interaction_id, provider_call_id})
|
|
1381
|
+
raise StudioError(
|
|
1382
|
+
"INTERACTION_SUBMIT_FAILED",
|
|
1383
|
+
"运行时未及时确认交互提交,请重试",
|
|
1384
|
+
status_code=409,
|
|
1385
|
+
) from exc
|
|
1386
|
+
except Exception as exc: # noqa: BLE001 - provider errors are user-safe here
|
|
1387
|
+
submitted.difference_update({interaction_id, provider_call_id})
|
|
1388
|
+
raise StudioError(
|
|
1389
|
+
"INTERACTION_SUBMIT_FAILED",
|
|
1390
|
+
str(exc),
|
|
1391
|
+
status_code=409,
|
|
1392
|
+
) from exc
|
|
650
1393
|
else:
|
|
1394
|
+
submitted.difference_update({interaction_id, provider_call_id})
|
|
651
1395
|
raise StudioError("INTERACTION_EXPIRED", "运行时交互已失效", status_code=409)
|
|
652
1396
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
1397
|
+
next_revision = revision + 1
|
|
1398
|
+
normalized_action = name.lower()
|
|
1399
|
+
if normalized_action in {"approve", "approved", "accept", "accepted"}:
|
|
1400
|
+
outcome = "approved"
|
|
1401
|
+
response_summary = "已同意"
|
|
1402
|
+
elif normalized_action in {"reject", "rejected", "deny", "denied", "decline"}:
|
|
1403
|
+
outcome = "rejected"
|
|
1404
|
+
response_summary = "已拒绝"
|
|
1405
|
+
elif normalized_action in {"cancel", "cancelled", "canceled"}:
|
|
1406
|
+
outcome = "cancelled"
|
|
1407
|
+
response_summary = "已反馈给 Agent" if data.get("feedback") else "已取消"
|
|
1408
|
+
else:
|
|
1409
|
+
outcome = "submitted"
|
|
1410
|
+
response_summary = "已提交"
|
|
1411
|
+
resolved_data = {
|
|
1412
|
+
"runId": run_id,
|
|
1413
|
+
"interactionId": interaction_id,
|
|
1414
|
+
"callId": provider_call_id,
|
|
1415
|
+
"name": name,
|
|
1416
|
+
"action": name,
|
|
1417
|
+
"outcome": outcome,
|
|
1418
|
+
"actor": "user",
|
|
1419
|
+
"responseSummary": response_summary,
|
|
1420
|
+
"data": data,
|
|
1421
|
+
"revision": next_revision,
|
|
1422
|
+
"expectedRevision": expected_revision,
|
|
1423
|
+
"idempotencyKey": idempotency_key,
|
|
1424
|
+
}
|
|
1425
|
+
action_data = {
|
|
1426
|
+
"runId": run_id,
|
|
1427
|
+
"surfaceId": str(
|
|
1428
|
+
interaction.data.get("surfaceId") or interaction.data.get("surface_id") or ""
|
|
1429
|
+
),
|
|
1430
|
+
"interactionId": interaction_id,
|
|
1431
|
+
"callId": provider_call_id,
|
|
1432
|
+
"actionId": f"action-{interaction_id}",
|
|
1433
|
+
"name": name,
|
|
1434
|
+
"data": data,
|
|
1435
|
+
"revision": next_revision,
|
|
1436
|
+
"expectedRevision": expected_revision,
|
|
1437
|
+
"idempotencyKey": idempotency_key,
|
|
1438
|
+
"requestDigest": request_digest,
|
|
1439
|
+
}
|
|
1440
|
+
_, _, receipt = self.event_store.append_interaction_resolution(
|
|
665
1441
|
run_id,
|
|
666
|
-
"
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
"surfaceId": str(
|
|
670
|
-
interaction.data.get("surfaceId") or interaction.data.get("surface_id") or ""
|
|
671
|
-
),
|
|
672
|
-
"interactionId": interaction_id,
|
|
673
|
-
"actionId": f"action-{interaction_id}",
|
|
674
|
-
"name": name,
|
|
675
|
-
"data": dict(data or {}),
|
|
676
|
-
},
|
|
1442
|
+
resolved_type=("approval.resolved" if kind == "approval" else "interaction.resolved"),
|
|
1443
|
+
resolved_data=resolved_data,
|
|
1444
|
+
action_data=action_data,
|
|
677
1445
|
)
|
|
678
1446
|
record.status = RunStatus.RUNNING
|
|
679
1447
|
self.event_store.save(record)
|
|
680
1448
|
self._waiting_modes.pop(run_id, None)
|
|
681
|
-
return
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
1449
|
+
return receipt
|
|
1450
|
+
|
|
1451
|
+
def _expire_unresolved_interactions(
|
|
1452
|
+
self,
|
|
1453
|
+
record: RunRecord,
|
|
1454
|
+
*,
|
|
1455
|
+
on_event: Callable[[RunEvent], None] | None = None,
|
|
1456
|
+
) -> None:
|
|
1457
|
+
"""Close orphaned cards whenever their owning run is terminal."""
|
|
1458
|
+
|
|
1459
|
+
if record.status not in {
|
|
1460
|
+
RunStatus.COMPLETED,
|
|
1461
|
+
RunStatus.FAILED,
|
|
1462
|
+
RunStatus.CANCELLED,
|
|
1463
|
+
RunStatus.INTERRUPTED,
|
|
1464
|
+
RunStatus.TIMED_OUT,
|
|
1465
|
+
}:
|
|
1466
|
+
return
|
|
1467
|
+
events = self.event_store.events(record.id)
|
|
1468
|
+
closed_ids = {
|
|
1469
|
+
str(event.data.get("interactionId") or event.data.get("approvalId") or "")
|
|
1470
|
+
for event in events
|
|
1471
|
+
if event.type
|
|
1472
|
+
in {
|
|
1473
|
+
"a2ui.action",
|
|
1474
|
+
"approval.resolved",
|
|
1475
|
+
"interaction.resolved",
|
|
1476
|
+
"interaction.cancelled",
|
|
1477
|
+
"interaction.expired",
|
|
1478
|
+
}
|
|
687
1479
|
}
|
|
1480
|
+
pending: dict[str, RunEvent] = {}
|
|
1481
|
+
for event in events:
|
|
1482
|
+
if event.type != "a2ui.interaction":
|
|
1483
|
+
continue
|
|
1484
|
+
interaction_id = str(
|
|
1485
|
+
event.data.get("interactionId") or event.data.get("interaction_id") or ""
|
|
1486
|
+
)
|
|
1487
|
+
if interaction_id and interaction_id not in closed_ids:
|
|
1488
|
+
pending[interaction_id] = event
|
|
1489
|
+
for interaction_id, requested in pending.items():
|
|
1490
|
+
data = requested.data
|
|
1491
|
+
expired = self.event_store.append(
|
|
1492
|
+
record.id,
|
|
1493
|
+
"interaction.expired",
|
|
1494
|
+
{
|
|
1495
|
+
"runId": record.id,
|
|
1496
|
+
"interactionId": interaction_id,
|
|
1497
|
+
"approvalId": interaction_id,
|
|
1498
|
+
"callId": str(data.get("callId") or data.get("call_id") or ""),
|
|
1499
|
+
"kind": str(data.get("kind") or "form"),
|
|
1500
|
+
"title": data.get("title"),
|
|
1501
|
+
"message": data.get("message"),
|
|
1502
|
+
"detail": data.get("detail"),
|
|
1503
|
+
"outcome": "expired",
|
|
1504
|
+
"revision": _interaction_revision(data) + 1,
|
|
1505
|
+
},
|
|
1506
|
+
)
|
|
1507
|
+
if on_event is not None:
|
|
1508
|
+
on_event(expired)
|
|
688
1509
|
|
|
689
1510
|
async def _capture_pcm_evidence(
|
|
690
1511
|
self,
|
|
@@ -830,7 +1651,12 @@ class StudioRunService:
|
|
|
830
1651
|
"runId": record.id,
|
|
831
1652
|
"surfaceId": surface_id,
|
|
832
1653
|
"interactionId": approval_id,
|
|
1654
|
+
"callId": event.request.call_id or approval_id,
|
|
833
1655
|
"kind": "approval",
|
|
1656
|
+
"title": "需要审批",
|
|
1657
|
+
"message": "此次操作需要确认后才能继续。",
|
|
1658
|
+
"detail": detail,
|
|
1659
|
+
"revision": 1,
|
|
834
1660
|
"inputSchema": {
|
|
835
1661
|
"type": "object",
|
|
836
1662
|
"properties": {
|
|
@@ -867,21 +1693,120 @@ class StudioRunService:
|
|
|
867
1693
|
agent_id: str,
|
|
868
1694
|
session_id: str,
|
|
869
1695
|
runtime_type: str,
|
|
1696
|
+
build_id: str,
|
|
870
1697
|
) -> dict[str, str]:
|
|
871
1698
|
if runtime_type != "codex":
|
|
872
1699
|
return {}
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
1700
|
+
runs = self.event_store.list_runs(session_id=session_id, agent_id=agent_id)
|
|
1701
|
+
thread_builds: dict[str, str] = {}
|
|
1702
|
+
for previous in runs:
|
|
1703
|
+
native_ref = previous.runtime_handle.get("native_ref") or {}
|
|
1704
|
+
if isinstance(native_ref, dict) and native_ref.get("thread_id"):
|
|
1705
|
+
# Older Studio versions also saved failed cross-build resumes.
|
|
1706
|
+
# The first occurrence identifies the thread's actual home.
|
|
1707
|
+
thread_builds.setdefault(str(native_ref["thread_id"]), previous.build_id)
|
|
1708
|
+
for previous in reversed(runs):
|
|
1709
|
+
# Each immutable build has its own CODEX_HOME and plugin snapshot.
|
|
1710
|
+
# Starting a thread in that home applies the new capabilities; the
|
|
1711
|
+
# transport-neutral history above preserves the Studio conversation.
|
|
1712
|
+
if previous.build_id != build_id:
|
|
1713
|
+
return {}
|
|
876
1714
|
native_ref = previous.runtime_handle.get("native_ref")
|
|
877
1715
|
native_ref = native_ref if isinstance(native_ref, dict) else {}
|
|
878
1716
|
thread_id = str(native_ref.get("thread_id") or "")
|
|
879
1717
|
if thread_id:
|
|
1718
|
+
if thread_builds[thread_id] != build_id:
|
|
1719
|
+
return {}
|
|
880
1720
|
return {"thread_id": thread_id}
|
|
881
1721
|
return {}
|
|
882
1722
|
|
|
883
1723
|
|
|
884
|
-
def
|
|
1724
|
+
def _plugin_event_envelope(record: RunRecord, event_type: str) -> dict[str, Any]:
|
|
1725
|
+
return {
|
|
1726
|
+
"schema_version": 2,
|
|
1727
|
+
"event_id": f"{record.id}:{event_type}",
|
|
1728
|
+
"seq": 0,
|
|
1729
|
+
"timestamp": time.time(),
|
|
1730
|
+
"run_id": record.id,
|
|
1731
|
+
"scope_id": record.id,
|
|
1732
|
+
"source": SourceRef(
|
|
1733
|
+
framework="ksadk",
|
|
1734
|
+
metadata={
|
|
1735
|
+
"runtime": "pluginhost",
|
|
1736
|
+
"agent_id": record.agent_id,
|
|
1737
|
+
"session_id": record.session_id,
|
|
1738
|
+
"build_id": record.build_id,
|
|
1739
|
+
},
|
|
1740
|
+
),
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
|
|
1744
|
+
def _normalized_plugin_usage(usage: Mapping[str, Any]) -> dict[str, Any]:
|
|
1745
|
+
def number(*keys: str) -> int:
|
|
1746
|
+
for key in keys:
|
|
1747
|
+
value = usage.get(key)
|
|
1748
|
+
if value is not None:
|
|
1749
|
+
try:
|
|
1750
|
+
return max(0, int(value))
|
|
1751
|
+
except (TypeError, ValueError):
|
|
1752
|
+
return 0
|
|
1753
|
+
return 0
|
|
1754
|
+
|
|
1755
|
+
normalized = {
|
|
1756
|
+
"input_tokens": number("input_tokens", "inputTokens", "prompt_tokens"),
|
|
1757
|
+
"output_tokens": number("output_tokens", "outputTokens", "completion_tokens"),
|
|
1758
|
+
"total_tokens": number("total_tokens", "totalTokens"),
|
|
1759
|
+
"cached_tokens": number("cached_tokens", "cachedTokens"),
|
|
1760
|
+
"reasoning_tokens": number("reasoning_tokens", "reasoningTokens"),
|
|
1761
|
+
}
|
|
1762
|
+
if not normalized["total_tokens"]:
|
|
1763
|
+
normalized["total_tokens"] = normalized["input_tokens"] + normalized["output_tokens"]
|
|
1764
|
+
normalized["reported"] = bool(usage) and any(
|
|
1765
|
+
key in usage
|
|
1766
|
+
for key in (
|
|
1767
|
+
"input_tokens",
|
|
1768
|
+
"inputTokens",
|
|
1769
|
+
"prompt_tokens",
|
|
1770
|
+
"output_tokens",
|
|
1771
|
+
"outputTokens",
|
|
1772
|
+
"completion_tokens",
|
|
1773
|
+
"total_tokens",
|
|
1774
|
+
"totalTokens",
|
|
1775
|
+
)
|
|
1776
|
+
)
|
|
1777
|
+
return normalized
|
|
1778
|
+
|
|
1779
|
+
|
|
1780
|
+
def _interaction_revision(data: Mapping[str, Any]) -> int:
|
|
1781
|
+
"""Treat malformed or historical interaction revisions as read-only."""
|
|
1782
|
+
|
|
1783
|
+
try:
|
|
1784
|
+
return max(0, int(data.get("revision") or 0))
|
|
1785
|
+
except (TypeError, ValueError):
|
|
1786
|
+
return 0
|
|
1787
|
+
|
|
1788
|
+
|
|
1789
|
+
def _apply_plugin_usage(record: RunRecord, usage: Mapping[str, Any]) -> None:
|
|
1790
|
+
normalized = _normalized_plugin_usage(usage)
|
|
1791
|
+
if not normalized["reported"]:
|
|
1792
|
+
return
|
|
1793
|
+
record.usage = Usage(
|
|
1794
|
+
input_tokens=normalized["input_tokens"],
|
|
1795
|
+
output_tokens=normalized["output_tokens"],
|
|
1796
|
+
total_tokens=normalized["total_tokens"],
|
|
1797
|
+
cached_input_tokens=normalized["cached_tokens"],
|
|
1798
|
+
reasoning_output_tokens=normalized["reasoning_tokens"],
|
|
1799
|
+
reported=True,
|
|
1800
|
+
source="pluginhost",
|
|
1801
|
+
)
|
|
1802
|
+
|
|
1803
|
+
|
|
1804
|
+
def project_runtime_event(
|
|
1805
|
+
event: RuntimeEvent,
|
|
1806
|
+
*,
|
|
1807
|
+
session_id: str | None = None,
|
|
1808
|
+
public_run_id: str | None = None,
|
|
1809
|
+
) -> tuple[str, dict[str, Any]]:
|
|
885
1810
|
"""Project the canonical RuntimeEvent into Studio's persisted event view.
|
|
886
1811
|
|
|
887
1812
|
公开承诺字段(契约声明见 ``ksadk/events/projections.py``,执行形态为
|
|
@@ -1005,7 +1930,9 @@ def project_runtime_event(event: RuntimeEvent) -> tuple[str, dict[str, Any]]:
|
|
|
1005
1930
|
payload = {
|
|
1006
1931
|
"interactionId": event.interaction_id,
|
|
1007
1932
|
"kind": "form",
|
|
1008
|
-
"inputSchema":
|
|
1933
|
+
"inputSchema": event.request.schema_,
|
|
1934
|
+
"message": event.request.prompt or "",
|
|
1935
|
+
"callId": event.source.native_event_id or event.interaction_id,
|
|
1009
1936
|
}
|
|
1010
1937
|
elif isinstance(event, InteractionResolved):
|
|
1011
1938
|
if event.interaction_kind == "approval":
|
|
@@ -1045,13 +1972,22 @@ def project_runtime_event(event: RuntimeEvent) -> tuple[str, dict[str, Any]]:
|
|
|
1045
1972
|
payload = {}
|
|
1046
1973
|
|
|
1047
1974
|
_attach_studio_identity(payload, event)
|
|
1975
|
+
if public_run_id and public_run_id != event.run_id:
|
|
1976
|
+
payload["runtimeRunId"] = event.run_id
|
|
1977
|
+
payload["runId"] = public_run_id
|
|
1048
1978
|
payload["runtimeEvent"] = dump_runtime_event(event)
|
|
1979
|
+
# Additive only: legacy Studio/Web consumers keep reading the established
|
|
1980
|
+
# event type and payload keys. New surfaces may opt into this typed,
|
|
1981
|
+
# identity-aware representation without reconstructing one from text.
|
|
1982
|
+
payload["conversationItem"] = project_conversation_item(
|
|
1983
|
+
event,
|
|
1984
|
+
session_id=session_id,
|
|
1985
|
+
run_id=public_run_id,
|
|
1986
|
+
).model_dump(by_alias=True, exclude_none=True, mode="json")
|
|
1049
1987
|
return projected, payload
|
|
1050
1988
|
|
|
1051
1989
|
|
|
1052
|
-
def _first_content(
|
|
1053
|
-
snapshot: ContentSnapshot | None, content_type: type
|
|
1054
|
-
) -> Any | None:
|
|
1990
|
+
def _first_content(snapshot: ContentSnapshot | None, content_type: type) -> Any | None:
|
|
1055
1991
|
if snapshot is None:
|
|
1056
1992
|
return None
|
|
1057
1993
|
for part in snapshot.parts:
|
|
@@ -1073,6 +2009,11 @@ def _project_a2ui_surface(
|
|
|
1073
2009
|
projected = "a2ui.surface.end"
|
|
1074
2010
|
data_parts = event.snapshot.parts
|
|
1075
2011
|
|
|
2012
|
+
if event.source.metadata.get("operation_batch") is True and isinstance(event, ItemCompleted):
|
|
2013
|
+
# Completion closes the immutable canonical batch; it is not a request
|
|
2014
|
+
# to remove the surface that the batch just created.
|
|
2015
|
+
projected = "a2ui.surface.begin"
|
|
2016
|
+
|
|
1076
2017
|
surface_id = str(event.source.metadata.get("surface_id") or "")
|
|
1077
2018
|
operations: list[dict[str, Any]] = []
|
|
1078
2019
|
for part in data_parts:
|
|
@@ -1080,6 +2021,8 @@ def _project_a2ui_surface(
|
|
|
1080
2021
|
data = part.data
|
|
1081
2022
|
if isinstance(data, list):
|
|
1082
2023
|
operations.extend(dict(op) for op in data if isinstance(op, Mapping))
|
|
2024
|
+
elif isinstance(data, Mapping):
|
|
2025
|
+
operations.extend(project_a2ui_operations(projected, data))
|
|
1083
2026
|
if not operations:
|
|
1084
2027
|
operations = project_a2ui_operations(projected, {"surface_id": surface_id})
|
|
1085
2028
|
payload: dict[str, Any] = {
|
|
@@ -1122,6 +2065,44 @@ def _studio_envelope_projection(envelope) -> tuple[str, dict[str, Any]] | None:
|
|
|
1122
2065
|
"""Session envelope -> Studio RunEvent 投影;cursor 仍用 envelope.seq。"""
|
|
1123
2066
|
|
|
1124
2067
|
payload = envelope.payload or {}
|
|
2068
|
+
# Kernel RuntimeEvent is persisted as a typed runtime/v2 SessionEvent
|
|
2069
|
+
# envelope. Reuse the exact same projector as the direct Studio path so
|
|
2070
|
+
# a consumer receives its additive ConversationItem regardless of which
|
|
2071
|
+
# ingress delivered the Run. Legacy event families retain the historical
|
|
2072
|
+
# text-only fallback below.
|
|
2073
|
+
if envelope.family == "runtime" and envelope.family_version == 2:
|
|
2074
|
+
try:
|
|
2075
|
+
runtime_event = parse_runtime_event(payload)
|
|
2076
|
+
except (TypeError, ValueError):
|
|
2077
|
+
# A damaged/unknown runtime payload must not make old Studio
|
|
2078
|
+
# subscriptions fail. Preserve its event type for legacy
|
|
2079
|
+
# observability while refusing to invent a typed conversation item.
|
|
2080
|
+
return envelope.event_type, {"runId": envelope.run_id or ""}
|
|
2081
|
+
return project_runtime_event(runtime_event, session_id=envelope.session_id)
|
|
2082
|
+
if envelope.family == "interaction" and envelope.family_version == 1:
|
|
2083
|
+
conversation_item = project_interaction_conversation_item(envelope)
|
|
2084
|
+
if conversation_item is None:
|
|
2085
|
+
# Keep a malformed durable fact observable without reusing an
|
|
2086
|
+
# actionable legacy event name. Existing Studio reducers turn
|
|
2087
|
+
# ``approval.requested`` into a submit button, so emitting that
|
|
2088
|
+
# name without an authoritative revision would bypass the typed
|
|
2089
|
+
# ConversationItem fail-closed boundary.
|
|
2090
|
+
payload = envelope.payload or {}
|
|
2091
|
+
return envelope.event_type, {
|
|
2092
|
+
"runId": envelope.run_id or str(payload.get("run_id") or ""),
|
|
2093
|
+
"itemId": str(payload.get("interaction_id") or envelope.event_id),
|
|
2094
|
+
"interactionReadOnly": True,
|
|
2095
|
+
}
|
|
2096
|
+
projected = _project_interaction_envelope_legacy(envelope)
|
|
2097
|
+
if projected is None:
|
|
2098
|
+
return None
|
|
2099
|
+
event_type, data = projected
|
|
2100
|
+
data["conversationItem"] = conversation_item.model_dump(
|
|
2101
|
+
by_alias=True,
|
|
2102
|
+
exclude_none=True,
|
|
2103
|
+
mode="json",
|
|
2104
|
+
)
|
|
2105
|
+
return event_type, data
|
|
1125
2106
|
if envelope.event_type == "run.completed":
|
|
1126
2107
|
return "run.completed", {
|
|
1127
2108
|
"runId": envelope.run_id or "",
|
|
@@ -1131,3 +2112,70 @@ def _studio_envelope_projection(envelope) -> tuple[str, dict[str, Any]] | None:
|
|
|
1131
2112
|
if text:
|
|
1132
2113
|
return "message.delta", {"delta": text}
|
|
1133
2114
|
return None
|
|
2115
|
+
|
|
2116
|
+
|
|
2117
|
+
def _project_interaction_envelope_legacy(
|
|
2118
|
+
envelope,
|
|
2119
|
+
) -> tuple[str, dict[str, Any]] | None:
|
|
2120
|
+
"""Keep established Studio event names while Interaction/v1 owns truth."""
|
|
2121
|
+
|
|
2122
|
+
payload = envelope.payload or {}
|
|
2123
|
+
interaction_id = str(payload.get("interaction_id") or "")
|
|
2124
|
+
interaction_kind = str(payload.get("kind") or "")
|
|
2125
|
+
revision = payload.get("revision")
|
|
2126
|
+
request = payload.get("request")
|
|
2127
|
+
request = request if isinstance(request, Mapping) else {}
|
|
2128
|
+
presentation = request.get("presentation")
|
|
2129
|
+
presentation = presentation if isinstance(presentation, Mapping) else {}
|
|
2130
|
+
common: dict[str, Any] = {
|
|
2131
|
+
"runId": envelope.run_id or str(payload.get("run_id") or ""),
|
|
2132
|
+
"itemId": interaction_id,
|
|
2133
|
+
"interactionId": interaction_id,
|
|
2134
|
+
}
|
|
2135
|
+
# Expose the value as received for legacy/read-only diagnostics. Only the
|
|
2136
|
+
# typed ConversationItem validates it as an authoritative writable token.
|
|
2137
|
+
if revision is not None:
|
|
2138
|
+
common["revision"] = revision
|
|
2139
|
+
|
|
2140
|
+
if envelope.event_type == "interaction.requested":
|
|
2141
|
+
if interaction_kind == "approval":
|
|
2142
|
+
return "approval.requested", {
|
|
2143
|
+
**common,
|
|
2144
|
+
"approvalId": interaction_id,
|
|
2145
|
+
"callId": "",
|
|
2146
|
+
"kind": str(presentation.get("title") or interaction_kind),
|
|
2147
|
+
"detail": presentation.get("description"),
|
|
2148
|
+
}
|
|
2149
|
+
return "a2ui.interaction", {
|
|
2150
|
+
**common,
|
|
2151
|
+
"kind": interaction_kind or "form",
|
|
2152
|
+
"inputSchema": (
|
|
2153
|
+
dict(request.get("request_schema"))
|
|
2154
|
+
if isinstance(request.get("request_schema"), Mapping)
|
|
2155
|
+
else {}
|
|
2156
|
+
),
|
|
2157
|
+
}
|
|
2158
|
+
if envelope.event_type in {
|
|
2159
|
+
"interaction.resolved",
|
|
2160
|
+
"interaction.cancelled",
|
|
2161
|
+
"interaction.expired",
|
|
2162
|
+
}:
|
|
2163
|
+
outcome = str(
|
|
2164
|
+
payload.get("outcome")
|
|
2165
|
+
or (
|
|
2166
|
+
"cancelled"
|
|
2167
|
+
if envelope.event_type == "interaction.cancelled"
|
|
2168
|
+
else "expired"
|
|
2169
|
+
if envelope.event_type == "interaction.expired"
|
|
2170
|
+
else ""
|
|
2171
|
+
)
|
|
2172
|
+
)
|
|
2173
|
+
if interaction_kind == "approval":
|
|
2174
|
+
return "approval.resolved", {
|
|
2175
|
+
**common,
|
|
2176
|
+
"approvalId": interaction_id,
|
|
2177
|
+
"callId": "",
|
|
2178
|
+
"decision": outcome,
|
|
2179
|
+
}
|
|
2180
|
+
return "a2ui.action", {**common, "name": outcome}
|
|
2181
|
+
return None
|