agentengine-sdk-python 0.8.2__py3-none-any.whl → 0.8.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/METADATA +18 -25
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/RECORD +224 -169
- ksadk/_build_provenance.json +1 -0
- ksadk/agui/_agent_helpers.py +4 -0
- ksadk/api/client.py +3 -0
- ksadk/builders/managed_runtime_builder.py +3 -0
- ksadk/cli/__init__.py +6 -0
- ksadk/cli/cmd_plugin.py +657 -0
- ksadk/codex/runtime.py +50 -8
- ksadk/configs/env_registry.py +31 -3
- ksadk/configs/settings.py +9 -0
- ksadk/context_engine/contributors.py +73 -8
- ksadk/conversations/__init__.py +19 -0
- ksadk/conversations/context.py +104 -24
- ksadk/conversations/contracts.py +266 -0
- ksadk/conversations/message_projection.py +19 -1
- ksadk/conversations/projector.py +616 -0
- ksadk/conversations/reducer.py +98 -0
- ksadk/events/_v1_compat/projection.py +2 -0
- ksadk/events/adapters/codex.py +190 -0
- ksadk/harness/__init__.py +45 -35
- ksadk/harness/runtime.py +60 -1
- ksadk/kernel/contract_fingerprints.py +5 -4
- ksadk/kernel/contracts.py +8 -0
- ksadk/kernel/control.py +1 -0
- ksadk/kernel/ingress.py +21 -0
- ksadk/kernel/memory_store.py +1 -0
- ksadk/kernel/postgres_store.py +1 -0
- ksadk/kernel/sqlite_store.py +1 -0
- ksadk/kernel/worker.py +11 -0
- ksadk/memory/providers/local_sqlite.py +7 -0
- ksadk/plugins/__init__.py +167 -0
- ksadk/plugins/bridges/__init__.py +45 -0
- ksadk/plugins/bridges/codex.py +552 -0
- ksadk/plugins/bridges/dsh.py +1116 -0
- ksadk/plugins/builtins.py +747 -0
- ksadk/plugins/bundle.py +246 -0
- ksadk/plugins/bundle_security.py +167 -0
- ksadk/plugins/composition.py +529 -0
- ksadk/plugins/context_contributor.py +437 -0
- ksadk/plugins/contracts.py +451 -0
- ksadk/plugins/dsh_toolchain.py +935 -0
- ksadk/plugins/ecosystem_bridge.py +1105 -0
- ksadk/plugins/ecosystem_probe.py +191 -0
- ksadk/plugins/host.py +724 -0
- ksadk/plugins/providers/__init__.py +85 -0
- ksadk/plugins/providers/bundles/ksadk-codex/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/ksadk-codex/index.mjs +18 -0
- ksadk/plugins/providers/bundles/ksadk-codex/package.json +19 -0
- ksadk/plugins/providers/bundles/ksadk-harness/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/ksadk-harness/index.mjs +19 -0
- ksadk/plugins/providers/bundles/ksadk-harness/package.json +19 -0
- ksadk/plugins/providers/codex.py +722 -0
- ksadk/plugins/providers/codex_dsh.py +83 -0
- ksadk/plugins/providers/dsh.py +951 -0
- ksadk/plugins/providers/dsh_descriptor_host.py +226 -0
- ksadk/plugins/providers/harness.py +627 -0
- ksadk/plugins/providers/harness_dsh.py +91 -0
- ksadk/plugins/providers/legacy.py +215 -0
- ksadk/plugins/providers/legacy_catalog.py +100 -0
- ksadk/plugins/providers/shipped_dsh.py +370 -0
- ksadk/plugins/resolver.py +331 -0
- ksadk/plugins/subagent_providers/__init__.py +12 -0
- ksadk/plugins/subagent_providers/codex.py +443 -0
- ksadk/plugins/subagents.py +333 -0
- ksadk/runtime/_runner_adapter/stream_mapping.py +51 -42
- ksadk/runtime/adapter.py +1 -0
- ksadk/runtime/factory.py +76 -2
- ksadk/runtime/framework_adapters.py +18 -0
- ksadk/runtime/runner_adapter.py +5 -0
- ksadk/sandbox/__init__.py +2 -1
- ksadk/sandbox/factory.py +72 -0
- ksadk/scheduler/__init__.py +18 -0
- ksadk/scheduler/calendar.py +102 -0
- ksadk/scheduler/contracts.py +254 -0
- ksadk/scheduler/dispatcher.py +129 -0
- ksadk/scheduler/engine.py +416 -0
- ksadk/scheduler/sqlite_store.py +563 -0
- ksadk/server/static/assets/{ArtifactsPanel-DSU1sWD_.js → ArtifactsPanel-10GPXIJ6.js} +1 -1
- ksadk/server/static/assets/{CodeBlock-DOH6MVcn.js → CodeBlock-BXk1l-G1.js} +1 -1
- ksadk/server/static/assets/{MathMessageMarkdown-BL8my1R2.js → MathMessageMarkdown-D9uB55Oh.js} +1 -1
- ksadk/server/static/assets/{MermaidBlock-Dz4IP-Tx.js → MermaidBlock--OEYoXIJ.js} +6 -6
- ksadk/server/static/assets/{NativeTerminalPanel-DUrK0JpZ.js → NativeTerminalPanel-DrEe1syq.js} +1 -1
- ksadk/server/static/assets/{abnfDiagram-VCTEODGH-g20pFzNV.js → abnfDiagram-VCTEODGH-eeTFJLvo.js} +1 -1
- ksadk/server/static/assets/{arc-C4FzinUA.js → arc-S3ZNbIAd.js} +1 -1
- ksadk/server/static/assets/architecture-7GRP2DOG-Dk9nRQ6-.js +1 -0
- ksadk/server/static/assets/{architectureDiagram-5GKGNRK7-DpIqL5h4.js → architectureDiagram-5GKGNRK7-CRjgEJDX.js} +1 -1
- ksadk/server/static/assets/{blockDiagram-NRAW4CY4-BjkljTNz.js → blockDiagram-NRAW4CY4-C7bsBoNH.js} +1 -1
- ksadk/server/static/assets/{c4Diagram-UCG6FXSJ-Dk_ieq2X.js → c4Diagram-UCG6FXSJ-BX3c33Ob.js} +1 -1
- ksadk/server/static/assets/channel-s354Yo6o.js +1 -0
- ksadk/server/static/assets/chunk-2Q5K7J3B-CDpPpKR5.js +1 -0
- ksadk/server/static/assets/{chunk-5VM5RSS4-BVHAchFb.js → chunk-5VM5RSS4-bZ7ws-8m.js} +1 -1
- ksadk/server/static/assets/{chunk-F27PBJKO-C-ipQzuS.js → chunk-F27PBJKO-D2r0pvhY.js} +1 -1
- ksadk/server/static/assets/{chunk-G27WJ6UU-C2EyJbIK.js → chunk-G27WJ6UU-CZwF3oUP.js} +1 -1
- ksadk/server/static/assets/{chunk-JWPE2WC7-vYvVJb_M.js → chunk-JWPE2WC7-DigFYCML.js} +1 -1
- ksadk/server/static/assets/{chunk-LCL6LL3I-BcI6ysPT.js → chunk-LCL6LL3I-CnorGV5q.js} +1 -1
- ksadk/server/static/assets/{chunk-POPQ4Y6H-C030x_Z1.js → chunk-POPQ4Y6H-CexntQA-.js} +1 -1
- ksadk/server/static/assets/{chunk-RHFEMEQ7-yyGpsz4n.js → chunk-RHFEMEQ7-CHTC7UxA.js} +1 -1
- ksadk/server/static/assets/{chunk-SVP7TREG-BLTlmMU7.js → chunk-SVP7TREG-BC1EWt_-.js} +1 -1
- ksadk/server/static/assets/{chunk-XXDRQBXY-C_32ArgP.js → chunk-XXDRQBXY-BTG24xN0.js} +1 -1
- ksadk/server/static/assets/classDiagram-DTDB5LWJ-Dd0nucl9.js +1 -0
- ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-Dd0nucl9.js +1 -0
- ksadk/server/static/assets/{cose-bilkent-JH36ORCC-9YQYwdl0.js → cose-bilkent-JH36ORCC-CkonRdAS.js} +1 -1
- ksadk/server/static/assets/cynefin-OW5HDTMX-BBlT4uMf.js +1 -0
- ksadk/server/static/assets/{cynefinDiagram-5FMLGOSQ-ErLF5N13.js → cynefinDiagram-5FMLGOSQ-DEOCBe15.js} +1 -1
- ksadk/server/static/assets/{dagre-3AP2YEHR-DFiQF-6f.js → dagre-3AP2YEHR-DBThPuLa.js} +1 -1
- ksadk/server/static/assets/{dagre-BuhGnRI1.js → dagre-ClTPrTht.js} +1 -1
- ksadk/server/static/assets/{diagram-S7CK7UJ4-Bt1v8-GC.js → diagram-S7CK7UJ4-DsZHrnpJ.js} +1 -1
- ksadk/server/static/assets/{diagram-UQ7AKVKN-DSCxJdBK.js → diagram-UQ7AKVKN-BJR4nhVr.js} +1 -1
- ksadk/server/static/assets/{diagram-VSXAHHWV-DHfYwp_9.js → diagram-VSXAHHWV-xzcERX0a.js} +1 -1
- ksadk/server/static/assets/{diagram-VX7I27RA-DdzD-4Le.js → diagram-VX7I27RA-tn2xM1xu.js} +1 -1
- ksadk/server/static/assets/{diagram-Z3DM3KII-Du-nAJ9F.js → diagram-Z3DM3KII-BZPGBkw5.js} +1 -1
- ksadk/server/static/assets/{dist-ClxGviKw.js → dist-4zN1_tq5.js} +1 -1
- ksadk/server/static/assets/{dist-BE7bu-DL.js → dist-8VWI-2bZ.js} +1 -1
- ksadk/server/static/assets/{dist-BX8z72IC.js → dist-B-46q3Hk.js} +1 -1
- ksadk/server/static/assets/{dist-BjU6y-A2.js → dist-B0gdk4rl.js} +1 -1
- ksadk/server/static/assets/{dist-C_wsv-Qd.js → dist-B1oWRmrH.js} +1 -1
- ksadk/server/static/assets/{dist-ngtN0DJB.js → dist-BI1qQH4n.js} +1 -1
- ksadk/server/static/assets/{dist-dydu68Fl.js → dist-BJLUwH6W.js} +1 -1
- ksadk/server/static/assets/{dist-Dq9gLD7L.js → dist-Bbwqa8TG.js} +1 -1
- ksadk/server/static/assets/{dist-CttYUqzS.js → dist-Btyo4vX4.js} +1 -1
- ksadk/server/static/assets/{dist-CXYYBw3_.js → dist-C71CsujO.js} +1 -1
- ksadk/server/static/assets/{dist-dDdADKFA.js → dist-CXpH_Rdd.js} +1 -1
- ksadk/server/static/assets/{dist-CSh_DE14.js → dist-Cq4OL6nO.js} +1 -1
- ksadk/server/static/assets/{dist-8hqcx0sU.js → dist-D1V3f1B4.js} +1 -1
- ksadk/server/static/assets/{dist-BVDffZ0U.js → dist-DJmCPRVB.js} +1 -1
- ksadk/server/static/assets/{dist-DnfXs8Vn.js → dist-DQAB0qn_.js} +1 -1
- ksadk/server/static/assets/{dist-DNp6rLRA.js → dist-DUwKa_l6.js} +1 -1
- ksadk/server/static/assets/{dist-B7seoj3d.js → dist-DdRBIceA.js} +1 -1
- ksadk/server/static/assets/{dist-BytlxIDT.js → dist-DfGFrb0v.js} +1 -1
- ksadk/server/static/assets/{dist-Bf2M8m3N.js → dist-DjhwGUgZ.js} +1 -1
- ksadk/server/static/assets/{dist-W-TIVntx.js → dist-DyqV4fL4.js} +1 -1
- ksadk/server/static/assets/{dist-DUX-id_F.js → dist-M67Hwu0n.js} +1 -1
- ksadk/server/static/assets/{dist-DmldrHd_.js → dist-u8tVH5Ak.js} +1 -1
- ksadk/server/static/assets/{ebnfDiagram-PWID7BFC-Da9C9NO1.js → ebnfDiagram-PWID7BFC-DVfMsLge.js} +1 -1
- ksadk/server/static/assets/{erDiagram-SSCWMZ5O-EAGqqR79.js → erDiagram-SSCWMZ5O-CWEZSYaw.js} +1 -1
- ksadk/server/static/assets/eventmodeling-NTZA5JFV-Dr5okd8V.js +1 -0
- ksadk/server/static/assets/flowDiagram-A5DVABFB-VEO-cVcO.js +1 -0
- ksadk/server/static/assets/{ganttDiagram-EL5Y4UJY-DqJsKb59.js → ganttDiagram-EL5Y4UJY-TWIxoyYL.js} +1 -1
- ksadk/server/static/assets/gitGraph-4MIJSDKK-CKiKY9Cf.js +1 -0
- ksadk/server/static/assets/{gitGraphDiagram-WWUBYQGX-B6g4dtDi.js → gitGraphDiagram-WWUBYQGX-DDh2hd2S.js} +1 -1
- ksadk/server/static/assets/index-B2k_urY8.js +241 -0
- ksadk/server/static/assets/index-ILPt6IWF.css +2 -0
- ksadk/server/static/assets/info-A6RAGUB7-ubA4Nm_p.js +1 -0
- ksadk/server/static/assets/{infoDiagram-RXCK75RN-Djm_nbd5.js → infoDiagram-RXCK75RN-CgoyWiDR.js} +1 -1
- ksadk/server/static/assets/{ishikawaDiagram-5VMMS53U-DWya9SG2.js → ishikawaDiagram-5VMMS53U-CyRIu_om.js} +1 -1
- ksadk/server/static/assets/{journeyDiagram-EYS64GPL-DutxhSNI.js → journeyDiagram-EYS64GPL-DN5JXHzN.js} +1 -1
- ksadk/server/static/assets/{kanban-definition-3QL26DDD-DJdOF8Fm.js → kanban-definition-3QL26DDD-CNP3ui66.js} +1 -1
- ksadk/server/static/assets/{katex-vFWytM5c.js → katex-DWEzQy1d.js} +1 -1
- ksadk/server/static/assets/{linear-BHjG8b-J.js → linear-C-qHp7d2.js} +1 -1
- ksadk/server/static/assets/{mermaid-parser.core-KGSy4jWT.js → mermaid-parser.core-Cl-K943T.js} +2 -2
- ksadk/server/static/assets/{mindmap-definition-FBJOCRG2-9-HC88D4.js → mindmap-definition-FBJOCRG2-D5DB5mYm.js} +1 -1
- ksadk/server/static/assets/packet-AYTQ26CC-C7R3NUlK.js +1 -0
- ksadk/server/static/assets/{pegDiagram-XKGWAZYB-DrEZd4fD.js → pegDiagram-XKGWAZYB-BQVc9ia_.js} +1 -1
- ksadk/server/static/assets/pie-WAS4IAKB-BIT6qjr3.js +1 -0
- ksadk/server/static/assets/{pieDiagram-E7YTZNPT-DpjqOFFp.js → pieDiagram-E7YTZNPT-B6UCAvtY.js} +1 -1
- ksadk/server/static/assets/{quadrantDiagram-AXDQQJYC-BiNhgOXX.js → quadrantDiagram-AXDQQJYC-mIr2d3eb.js} +1 -1
- ksadk/server/static/assets/radar-RG4KPBEZ-l5noef9X.js +1 -0
- ksadk/server/static/assets/railroad-74A4TZTK-Baea6ZvM.js +1 -0
- ksadk/server/static/assets/railroad-abnf-HS5TGJTU-OKn8zZnU.js +1 -0
- ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-DdKAKC4l.js +1 -0
- ksadk/server/static/assets/railroad-peg-WCYAUIDC-Bds8udlG.js +1 -0
- ksadk/server/static/assets/{railroadDiagram-O6MQD6OU-_Y3Ojg6G.js → railroadDiagram-O6MQD6OU-Dt3FFDmK.js} +1 -1
- ksadk/server/static/assets/{requirementDiagram-EFPCY7ZU-DeCFdg9K.js → requirementDiagram-EFPCY7ZU-Cz_vQtgF.js} +1 -1
- ksadk/server/static/assets/{sankeyDiagram-P5KCCOFB-CPH75rhw.js → sankeyDiagram-P5KCCOFB-wlO1TsH5.js} +1 -1
- ksadk/server/static/assets/{sequenceDiagram-WJ2MYXX4-OEpQQdr4.js → sequenceDiagram-WJ2MYXX4-CHRbElYq.js} +1 -1
- ksadk/server/static/assets/{sizeCapture-X5ZJPWSS-heUErzhI.js → sizeCapture-X5ZJPWSS-DXX54iyO.js} +1 -1
- ksadk/server/static/assets/{stateDiagram-HBIQ2CUA-DvA3jSMB.js → stateDiagram-HBIQ2CUA-Dp_lw9II.js} +1 -1
- ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-5b-8o2iS.js +1 -0
- ksadk/server/static/assets/{swimlanes-XN3QIQJK-CR-dfN_t.js → swimlanes-XN3QIQJK-D8O5UlLG.js} +1 -1
- ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-uf6WPRA4.js +8 -0
- ksadk/server/static/assets/{timeline-definition-24CTP7MA-DAVJf1TX.js → timeline-definition-24CTP7MA-8mfKcxvG.js} +1 -1
- ksadk/server/static/assets/treeView-Q6P3EWNA-CK5CSrSO.js +1 -0
- ksadk/server/static/assets/treemap-WGGIJYW6-Ch2BKgAM.js +1 -0
- ksadk/server/static/assets/{vennDiagram-4TSXK5OY-DwM8eDkh.js → vennDiagram-4TSXK5OY-EnNl0DE7.js} +1 -1
- ksadk/server/static/assets/wardley-WFR3VGLG-YW3Q0ml_.js +1 -0
- ksadk/server/static/assets/{wardleyDiagram-VM6X3IG4-DZL-Zz2t.js → wardleyDiagram-VM6X3IG4-DBu5fbYr.js} +1 -1
- ksadk/server/static/assets/{xychartDiagram-S5SC5T6Z-Dy6Yh_hu.js → xychartDiagram-S5SC5T6Z-BSVXs4IY.js} +1 -1
- ksadk/server/static/index.html +2 -2
- ksadk/skills/service_client.py +48 -0
- ksadk/studio/agent_lifecycle.py +26 -20
- ksadk/studio/api.py +513 -71
- ksadk/studio/api_catalog_routes.py +5 -0
- ksadk/studio/api_contracts.py +34 -1
- ksadk/studio/api_plugin_routes.py +480 -0
- ksadk/studio/attachment_store.py +187 -0
- ksadk/studio/authoring.py +10 -4
- ksadk/studio/authoring_coordinator.py +12 -2
- ksadk/studio/builder.py +123 -43
- ksadk/studio/cloud.py +4 -0
- ksadk/studio/codex_agent_service.py +12 -1
- ksadk/studio/codex_builder.py +54 -15
- ksadk/studio/codex_manifest.py +21 -1
- ksadk/studio/codex_run.py +16 -4
- ksadk/studio/compatibility_report.py +445 -0
- ksadk/studio/compiler.py +6 -1
- ksadk/studio/contracts.py +123 -4
- ksadk/studio/dsh_provider_registration.py +768 -0
- ksadk/studio/event_store.py +54 -6
- ksadk/studio/framework_run.py +37 -4
- ksadk/studio/hosted_kernel.py +1 -1
- ksadk/studio/operations.py +12 -2
- ksadk/studio/plugin_composition.py +301 -0
- ksadk/studio/plugin_kernel_adapter.py +128 -0
- ksadk/studio/plugin_runtime.py +652 -0
- ksadk/studio/resource_catalog.py +6 -1
- ksadk/studio/run_service.py +713 -75
- ksadk/studio/runtime_source.py +9 -1
- ksadk/studio/scheduler_runtime.py +374 -0
- ksadk/studio/scheduler_service.py +211 -0
- ksadk/studio/service.py +672 -82
- ksadk/studio/shared_web.py +167 -28
- ksadk/studio/soul.py +52 -0
- ksadk/studio/static/assets/index-DXs1Eq4M.css +1 -0
- ksadk/studio/static/assets/index-DeDqwTsD.js +283 -0
- ksadk/studio/static/index.html +2 -2
- ksadk/studio/validator.py +1 -1
- ksadk/studio/workspace.py +1 -0
- ksadk/version.py +1 -1
- ksadk_runtime_common/workspace_files/path_utils.py +41 -0
- ksadk/server/static/assets/architecture-7GRP2DOG-DjKratdf.js +0 -1
- ksadk/server/static/assets/channel-4cQHKtx1.js +0 -1
- ksadk/server/static/assets/chunk-2Q5K7J3B-DmgWkESh.js +0 -1
- ksadk/server/static/assets/classDiagram-DTDB5LWJ-DLFd9Xi0.js +0 -1
- ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-DLFd9Xi0.js +0 -1
- ksadk/server/static/assets/cynefin-OW5HDTMX-DjEM48Qp.js +0 -1
- ksadk/server/static/assets/eventmodeling-NTZA5JFV-CMgMaJrC.js +0 -1
- ksadk/server/static/assets/flowDiagram-A5DVABFB-BBKL0x3P.js +0 -1
- ksadk/server/static/assets/gitGraph-4MIJSDKK-Dw63mrhw.js +0 -1
- ksadk/server/static/assets/index-8ipRcQ-M.js +0 -240
- ksadk/server/static/assets/index-ByFCqjlh.css +0 -2
- ksadk/server/static/assets/info-A6RAGUB7-B6iAblII.js +0 -1
- ksadk/server/static/assets/packet-AYTQ26CC-BOYAipuq.js +0 -1
- ksadk/server/static/assets/pie-WAS4IAKB-Ctq0Kbku.js +0 -1
- ksadk/server/static/assets/radar-RG4KPBEZ-DN0-ul_0.js +0 -1
- ksadk/server/static/assets/railroad-74A4TZTK-C6cC_to0.js +0 -1
- ksadk/server/static/assets/railroad-abnf-HS5TGJTU-BimjIuRc.js +0 -1
- ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-CvY65ABq.js +0 -1
- ksadk/server/static/assets/railroad-peg-WCYAUIDC-D72Iq_Tv.js +0 -1
- ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BgQY03nz.js +0 -1
- ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-DLVw3q5Q.js +0 -8
- ksadk/server/static/assets/treeView-Q6P3EWNA-BMpO0wsB.js +0 -1
- ksadk/server/static/assets/treemap-WGGIJYW6-Et0aqzU3.js +0 -1
- ksadk/server/static/assets/wardley-WFR3VGLG-CbY0mhpl.js +0 -1
- ksadk/studio/static/assets/index-BLpcdrgW.css +0 -1
- ksadk/studio/static/assets/index-CYekR2Xv.js +0 -260
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/WHEEL +0 -0
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/entry_points.txt +0 -0
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/licenses/LICENSE +0 -0
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/top_level.txt +0 -0
ksadk/studio/api.py
CHANGED
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
+
import base64
|
|
6
7
|
import hmac
|
|
7
8
|
import json
|
|
8
9
|
import os
|
|
9
10
|
import secrets
|
|
11
|
+
import time
|
|
10
12
|
from collections.abc import AsyncIterator
|
|
11
13
|
from contextlib import asynccontextmanager
|
|
12
14
|
from pathlib import Path
|
|
@@ -18,8 +20,18 @@ from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
|
|
|
18
20
|
from fastapi.staticfiles import StaticFiles
|
|
19
21
|
from starlette.background import BackgroundTask
|
|
20
22
|
|
|
23
|
+
from ksadk.api.client import AgentEngineAPIError
|
|
24
|
+
from ksadk.conversations.contracts import (
|
|
25
|
+
ConversationAttachmentPart,
|
|
26
|
+
ConversationTextPart,
|
|
27
|
+
validate_conversation_input,
|
|
28
|
+
)
|
|
29
|
+
from ksadk.conversations.projector import project_conversation_item
|
|
30
|
+
from ksadk.events.canonical import parse_runtime_event_lenient
|
|
31
|
+
from ksadk.scheduler.contracts import ScheduledTask
|
|
21
32
|
from ksadk.studio.api_catalog_routes import register_catalog_routes
|
|
22
33
|
from ksadk.studio.api_contracts import (
|
|
34
|
+
AgentScheduleRequest,
|
|
23
35
|
AuthoringCommitRequest,
|
|
24
36
|
BuildRequest,
|
|
25
37
|
CloudAgentVersionRollbackRequest,
|
|
@@ -27,6 +39,7 @@ from ksadk.studio.api_contracts import (
|
|
|
27
39
|
CloudChatMessageRequest,
|
|
28
40
|
ContextPreviewRequest,
|
|
29
41
|
ConversationAuthoringRequest,
|
|
42
|
+
ConversationTurnRequest,
|
|
30
43
|
CreateAgentRequest,
|
|
31
44
|
ImportRootRequest,
|
|
32
45
|
InteractionSubmitRequest,
|
|
@@ -65,6 +78,7 @@ from ksadk.studio.api_helpers import (
|
|
|
65
78
|
sse as _sse,
|
|
66
79
|
)
|
|
67
80
|
from ksadk.studio.api_memory_routes import register_memory_routes
|
|
81
|
+
from ksadk.studio.api_plugin_routes import register_plugin_routes
|
|
68
82
|
from ksadk.studio.codex_manifest import CodexAgentManifest
|
|
69
83
|
from ksadk.studio.contracts import (
|
|
70
84
|
AgentAppearance,
|
|
@@ -85,6 +99,117 @@ _PUBLIC_API_PATHS = {
|
|
|
85
99
|
}
|
|
86
100
|
_LOCAL_HOSTS = {"127.0.0.1", "::1", "localhost", "testserver"}
|
|
87
101
|
|
|
102
|
+
# RuntimeEvent/v2 itself is snake_case. Some pre-existing cloud SessionEvent
|
|
103
|
+
# projections, however, serialized the *envelope* with the REST camelCase
|
|
104
|
+
# convention. This is a narrow transport normalizer, not a provider payload
|
|
105
|
+
# rewrite: only fields owned by the frozen RuntimeEvent envelope/content
|
|
106
|
+
# contracts are translated before strict parsing.
|
|
107
|
+
_RUNTIME_EVENT_WIRE_ALIASES = {
|
|
108
|
+
"schemaVersion": "schema_version",
|
|
109
|
+
"eventId": "event_id",
|
|
110
|
+
"runId": "run_id",
|
|
111
|
+
"runSeq": "run_seq",
|
|
112
|
+
"scopeId": "scope_id",
|
|
113
|
+
"parentScopeId": "parent_scope_id",
|
|
114
|
+
"eventType": "event_type",
|
|
115
|
+
"itemId": "item_id",
|
|
116
|
+
"itemKind": "item_kind",
|
|
117
|
+
"interactionId": "interaction_id",
|
|
118
|
+
"interactionKind": "interaction_kind",
|
|
119
|
+
"continuationId": "continuation_id",
|
|
120
|
+
"resumeAttemptId": "resume_attempt_id",
|
|
121
|
+
"outputRefs": "output_refs",
|
|
122
|
+
"inputTokens": "input_tokens",
|
|
123
|
+
"outputTokens": "output_tokens",
|
|
124
|
+
"totalTokens": "total_tokens",
|
|
125
|
+
"cachedTokens": "cached_tokens",
|
|
126
|
+
"reasoningTokens": "reasoning_tokens",
|
|
127
|
+
}
|
|
128
|
+
_SOURCE_WIRE_ALIASES = {
|
|
129
|
+
"nativeEventId": "native_event_id",
|
|
130
|
+
"nativeCursor": "native_cursor",
|
|
131
|
+
"nativeRunId": "native_run_id",
|
|
132
|
+
"nativeItemId": "native_item_id",
|
|
133
|
+
}
|
|
134
|
+
_CONTENT_WIRE_ALIASES = {
|
|
135
|
+
"contentType": "content_type",
|
|
136
|
+
"partId": "part_id",
|
|
137
|
+
"callId": "call_id",
|
|
138
|
+
"artifactId": "artifact_id",
|
|
139
|
+
"mimeType": "mime_type",
|
|
140
|
+
"isError": "is_error",
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _normalize_cloud_runtime_event_wire(value: dict[str, Any]) -> dict[str, Any]:
|
|
145
|
+
"""Normalize only historic REST casing at the RuntimeEvent boundary."""
|
|
146
|
+
|
|
147
|
+
normalized = {
|
|
148
|
+
_RUNTIME_EVENT_WIRE_ALIASES.get(key, key): item for key, item in value.items()
|
|
149
|
+
}
|
|
150
|
+
source = normalized.get("source")
|
|
151
|
+
if isinstance(source, dict):
|
|
152
|
+
normalized["source"] = {
|
|
153
|
+
_SOURCE_WIRE_ALIASES.get(key, key): item for key, item in source.items()
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
def normalize_content(content: Any) -> Any:
|
|
157
|
+
if not isinstance(content, dict):
|
|
158
|
+
return content
|
|
159
|
+
return {_CONTENT_WIRE_ALIASES.get(key, key): item for key, item in content.items()}
|
|
160
|
+
|
|
161
|
+
normalized["update"] = normalize_content(normalized.get("update"))
|
|
162
|
+
for key in ("initial", "snapshot"):
|
|
163
|
+
snapshot = normalized.get(key)
|
|
164
|
+
if not isinstance(snapshot, dict):
|
|
165
|
+
continue
|
|
166
|
+
parts = snapshot.get("parts")
|
|
167
|
+
if isinstance(parts, list):
|
|
168
|
+
normalized[key] = {
|
|
169
|
+
**snapshot,
|
|
170
|
+
"parts": [normalize_content(part) for part in parts],
|
|
171
|
+
}
|
|
172
|
+
return normalized
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _cloud_event_conversation_item(
|
|
176
|
+
event: dict[str, Any], *, session_id: str
|
|
177
|
+
) -> dict[str, Any] | None:
|
|
178
|
+
"""Project a complete cloud RuntimeEvent without inventing a browser item.
|
|
179
|
+
|
|
180
|
+
Cloud SessionEvent history has a compatibility envelope around the runtime
|
|
181
|
+
payload. Keep the source event untouched, but add the same typed
|
|
182
|
+
ConversationItem that local Studio streams use whenever the nested event
|
|
183
|
+
validates against RuntimeEvent/v2. A malformed or older envelope is still
|
|
184
|
+
observable to diagnostics, never guessed into an actionable chat card.
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
payload = event.get("payload") if isinstance(event.get("payload"), dict) else event
|
|
188
|
+
content = payload.get("content") if isinstance(payload.get("content"), dict) else {}
|
|
189
|
+
candidates = (
|
|
190
|
+
content.get("runtime_event"),
|
|
191
|
+
content.get("runtimeEvent"),
|
|
192
|
+
payload.get("runtime_event"),
|
|
193
|
+
payload.get("runtimeEvent"),
|
|
194
|
+
event.get("runtime_event"),
|
|
195
|
+
event.get("runtimeEvent"),
|
|
196
|
+
)
|
|
197
|
+
raw_event = next((candidate for candidate in candidates if isinstance(candidate, dict)), None)
|
|
198
|
+
if raw_event is None:
|
|
199
|
+
return None
|
|
200
|
+
try:
|
|
201
|
+
runtime_event = parse_runtime_event_lenient(
|
|
202
|
+
_normalize_cloud_runtime_event_wire(raw_event)
|
|
203
|
+
)
|
|
204
|
+
item = project_conversation_item(
|
|
205
|
+
runtime_event, # type: ignore[arg-type]
|
|
206
|
+
session_id=session_id,
|
|
207
|
+
run_id=runtime_event.run_id,
|
|
208
|
+
)
|
|
209
|
+
except (TypeError, ValueError):
|
|
210
|
+
return None
|
|
211
|
+
return item.model_dump(by_alias=True, exclude_none=True, mode="json")
|
|
212
|
+
|
|
88
213
|
|
|
89
214
|
def create_studio_app(
|
|
90
215
|
root: Path | str,
|
|
@@ -101,9 +226,13 @@ def create_studio_app(
|
|
|
101
226
|
@asynccontextmanager
|
|
102
227
|
async def lifespan(_app: FastAPI) -> AsyncIterator[None]:
|
|
103
228
|
try:
|
|
229
|
+
await studio.start()
|
|
104
230
|
await studio.run_service.recover_interrupted()
|
|
231
|
+
await studio.scheduler.start_if_available()
|
|
105
232
|
yield
|
|
106
233
|
finally:
|
|
234
|
+
await studio.scheduler.stop()
|
|
235
|
+
await studio.aclose()
|
|
107
236
|
studio.credentials.clear_session()
|
|
108
237
|
|
|
109
238
|
app = FastAPI(
|
|
@@ -117,7 +246,82 @@ def create_studio_app(
|
|
|
117
246
|
app.state.studio_service = studio
|
|
118
247
|
app.state.session_token = session_secret
|
|
119
248
|
app.state.csrf_token = csrf_secret
|
|
249
|
+
|
|
250
|
+
def _stream_studio_run(
|
|
251
|
+
build_id: str,
|
|
252
|
+
*,
|
|
253
|
+
user_input: str,
|
|
254
|
+
session_id: str | None,
|
|
255
|
+
model: str | None,
|
|
256
|
+
sandbox: str | None,
|
|
257
|
+
reasoning_effort: str | None = None,
|
|
258
|
+
approval_mode: str | None = None,
|
|
259
|
+
collaboration_mode: str | None = None,
|
|
260
|
+
goal_objective: str | None = None,
|
|
261
|
+
runtime_input: Any = None,
|
|
262
|
+
idempotency_key: str,
|
|
263
|
+
) -> StreamingResponse:
|
|
264
|
+
"""Create an observer-only SSE response for one durable Studio run."""
|
|
265
|
+
|
|
266
|
+
queue: asyncio.Queue = asyncio.Queue()
|
|
267
|
+
|
|
268
|
+
def observe(event):
|
|
269
|
+
queue.put_nowait(event)
|
|
270
|
+
|
|
271
|
+
# The Operation owns the runtime task. The SSE response is only one
|
|
272
|
+
# observer, so refreshing or switching chats cannot cancel the Run.
|
|
273
|
+
operation = studio.submit_studio_run(
|
|
274
|
+
build_id,
|
|
275
|
+
user_input,
|
|
276
|
+
session_id=session_id,
|
|
277
|
+
model=model,
|
|
278
|
+
sandbox=sandbox,
|
|
279
|
+
reasoning_effort=reasoning_effort,
|
|
280
|
+
approval_mode=approval_mode,
|
|
281
|
+
collaboration_mode=collaboration_mode,
|
|
282
|
+
goal_objective=goal_objective,
|
|
283
|
+
runtime_input=runtime_input,
|
|
284
|
+
idempotency_key=idempotency_key,
|
|
285
|
+
on_event=observe,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
async def render():
|
|
289
|
+
while True:
|
|
290
|
+
try:
|
|
291
|
+
event = await asyncio.wait_for(queue.get(), timeout=0.1)
|
|
292
|
+
except TimeoutError:
|
|
293
|
+
event = None
|
|
294
|
+
if event is not None:
|
|
295
|
+
data = json.dumps(
|
|
296
|
+
event.data,
|
|
297
|
+
ensure_ascii=False,
|
|
298
|
+
separators=(",", ":"),
|
|
299
|
+
)
|
|
300
|
+
yield f"id: {event.id}\nevent: {event.type}\ndata: {data}\n\n"
|
|
301
|
+
current = studio.operations.get(operation.id)
|
|
302
|
+
if (
|
|
303
|
+
current.status
|
|
304
|
+
in {
|
|
305
|
+
OperationStatus.SUCCEEDED,
|
|
306
|
+
OperationStatus.FAILED,
|
|
307
|
+
OperationStatus.CANCELLED,
|
|
308
|
+
OperationStatus.INTERRUPTED,
|
|
309
|
+
}
|
|
310
|
+
and queue.empty()
|
|
311
|
+
):
|
|
312
|
+
if current.status == OperationStatus.FAILED:
|
|
313
|
+
data = json.dumps(current.error or {}, ensure_ascii=False)
|
|
314
|
+
yield f"event: run.failed\ndata: {data}\n\n"
|
|
315
|
+
break
|
|
316
|
+
|
|
317
|
+
return StreamingResponse(
|
|
318
|
+
render(),
|
|
319
|
+
media_type="text/event-stream",
|
|
320
|
+
headers={"Cache-Control": "no-store", "X-Accel-Buffering": "no"},
|
|
321
|
+
)
|
|
322
|
+
|
|
120
323
|
static_root = Path(__file__).with_name("static")
|
|
324
|
+
_studio_startup_epoch = str(int(time.time()))
|
|
121
325
|
shared_web = StudioSharedWebBridge(studio)
|
|
122
326
|
app.state.shared_web_bridge = shared_web
|
|
123
327
|
app.mount("/static", StaticFiles(directory=static_root), name="studio-static")
|
|
@@ -212,6 +416,39 @@ def create_studio_app(
|
|
|
212
416
|
async def studio_error_handler(request: Request, exc: StudioError):
|
|
213
417
|
return _error_response(exc, request)
|
|
214
418
|
|
|
419
|
+
@app.exception_handler(AgentEngineAPIError)
|
|
420
|
+
async def agentengine_api_error_handler(request: Request, exc: AgentEngineAPIError):
|
|
421
|
+
http_status = exc.details.get("http_status", 502)
|
|
422
|
+
status_code = (
|
|
423
|
+
http_status if isinstance(http_status, int) and 400 <= http_status < 600 else 502
|
|
424
|
+
)
|
|
425
|
+
return _error_response(
|
|
426
|
+
StudioError(
|
|
427
|
+
"CLOUD_API_ERROR",
|
|
428
|
+
exc.message or "云端服务返回错误",
|
|
429
|
+
status_code=status_code,
|
|
430
|
+
details={"api_code": exc.code, "raw_code": exc.raw_code},
|
|
431
|
+
),
|
|
432
|
+
request,
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
@app.exception_handler(Exception)
|
|
436
|
+
async def generic_error_handler(request: Request, exc: Exception):
|
|
437
|
+
# Catch-all for bare Exception raises from the API client (e.g. HTTP
|
|
438
|
+
# errors, JSON parse failures) so they surface as structured JSON
|
|
439
|
+
# instead of opaque 500s. StudioError and AgentEngineAPIError are
|
|
440
|
+
# handled by their own dedicated handlers above.
|
|
441
|
+
if isinstance(exc, StudioError):
|
|
442
|
+
return _error_response(exc, request)
|
|
443
|
+
return _error_response(
|
|
444
|
+
StudioError(
|
|
445
|
+
"INTERNAL_ERROR",
|
|
446
|
+
"Studio 内部错误,请根据请求 ID 查看本地诊断日志。",
|
|
447
|
+
status_code=500,
|
|
448
|
+
),
|
|
449
|
+
request,
|
|
450
|
+
)
|
|
451
|
+
|
|
215
452
|
@app.exception_handler(RequestValidationError)
|
|
216
453
|
async def validation_error_handler(request: Request, exc: RequestValidationError):
|
|
217
454
|
first = exc.errors()[0] if exc.errors() else {}
|
|
@@ -229,7 +466,24 @@ def create_studio_app(
|
|
|
229
466
|
@app.get("/")
|
|
230
467
|
async def index():
|
|
231
468
|
path = static_root / "index.html"
|
|
232
|
-
|
|
469
|
+
html = path.read_text(encoding="utf-8")
|
|
470
|
+
# Inject a startup-scoped version so a restarted Studio with a rebuilt
|
|
471
|
+
# bundle always wins over a stale browser tab. The bundle filenames
|
|
472
|
+
# are already content-hashed; this only defeats cached index.html.
|
|
473
|
+
if "?v=" not in html:
|
|
474
|
+
import re
|
|
475
|
+
|
|
476
|
+
def _add_version(match: "re.Match[str]") -> str:
|
|
477
|
+
attr, path_part = match.group(1), match.group(2)
|
|
478
|
+
return f'{attr}="/static/assets/{path_part}?v={_studio_startup_epoch}"'
|
|
479
|
+
|
|
480
|
+
html = re.sub(
|
|
481
|
+
r'(src|href)="/static/assets/([^"]+)"',
|
|
482
|
+
_add_version,
|
|
483
|
+
html,
|
|
484
|
+
)
|
|
485
|
+
response = Response(content=html, media_type="text/html")
|
|
486
|
+
response.headers["Cache-Control"] = "no-store"
|
|
233
487
|
if security_enabled:
|
|
234
488
|
response.set_cookie(
|
|
235
489
|
"agentkit_studio_session",
|
|
@@ -324,6 +578,7 @@ def create_studio_app(
|
|
|
324
578
|
"CollaborationMode": collaboration_mode,
|
|
325
579
|
"GoalObjective": goal_objective,
|
|
326
580
|
"ReasoningEffort": reasoning_effort,
|
|
581
|
+
"ModelExplicit": bool(str(payload.get("model") or "").strip()),
|
|
327
582
|
}
|
|
328
583
|
bridge_payload["Model"] = shared_web.select_model(
|
|
329
584
|
bridge_payload["AgentId"],
|
|
@@ -491,6 +746,7 @@ def create_studio_app(
|
|
|
491
746
|
"deployment": True,
|
|
492
747
|
"cloudRebuild": False,
|
|
493
748
|
"reactChat": True,
|
|
749
|
+
"scheduler": studio.scheduler.availability(),
|
|
494
750
|
},
|
|
495
751
|
"runtimes": studio.runtime_catalog(),
|
|
496
752
|
"importableProject": studio.detect_importable_project(),
|
|
@@ -504,6 +760,111 @@ def create_studio_app(
|
|
|
504
760
|
async def update_settings(payload: dict[str, Any]):
|
|
505
761
|
return studio.update_settings(payload)
|
|
506
762
|
|
|
763
|
+
@app.get("/api/v1/schedules")
|
|
764
|
+
async def list_schedules():
|
|
765
|
+
return {
|
|
766
|
+
"items": studio.scheduler.list_tasks(),
|
|
767
|
+
"availability": studio.scheduler.availability(),
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
@app.post("/api/v1/schedules", status_code=201)
|
|
771
|
+
async def create_schedule(payload: ScheduledTask):
|
|
772
|
+
studio.validate_schedule_build(
|
|
773
|
+
payload.target.agent_version_ref,
|
|
774
|
+
agent_id=payload.target.agent_id,
|
|
775
|
+
)
|
|
776
|
+
return studio.scheduler.create_task(payload)
|
|
777
|
+
|
|
778
|
+
@app.get("/api/v1/schedules/{task_id}")
|
|
779
|
+
async def get_schedule(task_id: str):
|
|
780
|
+
return studio.scheduler.get_task(task_id)
|
|
781
|
+
|
|
782
|
+
@app.put("/api/v1/schedules/{task_id}")
|
|
783
|
+
async def update_schedule(task_id: str, payload: ScheduledTask):
|
|
784
|
+
studio.validate_schedule_build(
|
|
785
|
+
payload.target.agent_version_ref,
|
|
786
|
+
agent_id=payload.target.agent_id,
|
|
787
|
+
)
|
|
788
|
+
return studio.scheduler.update_task(task_id, payload)
|
|
789
|
+
|
|
790
|
+
@app.delete("/api/v1/schedules/{task_id}", status_code=204)
|
|
791
|
+
async def delete_schedule(task_id: str):
|
|
792
|
+
studio.scheduler.delete_task(task_id)
|
|
793
|
+
return Response(status_code=204)
|
|
794
|
+
|
|
795
|
+
@app.post("/api/v1/schedules/{task_id}:run", status_code=202)
|
|
796
|
+
async def run_schedule_now(task_id: str):
|
|
797
|
+
return await studio.scheduler.run_now(task_id)
|
|
798
|
+
|
|
799
|
+
@app.get("/api/v1/schedules/{task_id}/occurrences")
|
|
800
|
+
async def list_schedule_occurrences(task_id: str, limit: int = Query(default=50, ge=1, le=200)):
|
|
801
|
+
return {"items": studio.scheduler.list_occurrences(task_id, limit=limit)}
|
|
802
|
+
|
|
803
|
+
@app.get("/api/v1/schedule-occurrences")
|
|
804
|
+
async def list_all_schedule_occurrences(
|
|
805
|
+
limit: int = Query(default=200, ge=1, le=500),
|
|
806
|
+
):
|
|
807
|
+
return {"items": studio.scheduler.list_all_occurrences(limit=limit)}
|
|
808
|
+
|
|
809
|
+
@app.get("/api/v1/agents/{agent_id}/schedules")
|
|
810
|
+
async def list_agent_schedules(agent_id: str):
|
|
811
|
+
return {
|
|
812
|
+
"items": studio.list_agent_schedules(agent_id),
|
|
813
|
+
"availability": studio.scheduler.availability(),
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
@app.post("/api/v1/agents/{agent_id}/schedules", status_code=201)
|
|
817
|
+
async def create_agent_schedule(agent_id: str, payload: AgentScheduleRequest):
|
|
818
|
+
return await studio.create_agent_schedule(
|
|
819
|
+
agent_id,
|
|
820
|
+
display_name=payload.display_name,
|
|
821
|
+
prompt=payload.prompt,
|
|
822
|
+
schedule=payload.schedule,
|
|
823
|
+
enabled=payload.enabled,
|
|
824
|
+
continuity=payload.continuity,
|
|
825
|
+
session_id=payload.session_id,
|
|
826
|
+
)
|
|
827
|
+
|
|
828
|
+
@app.get("/api/v1/agents/{agent_id}/schedules/{task_id}")
|
|
829
|
+
async def get_agent_schedule(agent_id: str, task_id: str):
|
|
830
|
+
return studio.get_agent_schedule(agent_id, task_id)
|
|
831
|
+
|
|
832
|
+
@app.put("/api/v1/agents/{agent_id}/schedules/{task_id}")
|
|
833
|
+
async def update_agent_schedule(
|
|
834
|
+
agent_id: str,
|
|
835
|
+
task_id: str,
|
|
836
|
+
payload: AgentScheduleRequest,
|
|
837
|
+
):
|
|
838
|
+
return studio.update_agent_schedule(
|
|
839
|
+
agent_id,
|
|
840
|
+
task_id,
|
|
841
|
+
display_name=payload.display_name,
|
|
842
|
+
prompt=payload.prompt,
|
|
843
|
+
schedule=payload.schedule,
|
|
844
|
+
enabled=payload.enabled,
|
|
845
|
+
continuity=payload.continuity,
|
|
846
|
+
session_id=payload.session_id,
|
|
847
|
+
)
|
|
848
|
+
|
|
849
|
+
@app.delete("/api/v1/agents/{agent_id}/schedules/{task_id}", status_code=204)
|
|
850
|
+
async def delete_agent_schedule(agent_id: str, task_id: str):
|
|
851
|
+
studio.get_agent_schedule(agent_id, task_id)
|
|
852
|
+
studio.scheduler.delete_task(task_id)
|
|
853
|
+
return Response(status_code=204)
|
|
854
|
+
|
|
855
|
+
@app.post("/api/v1/agents/{agent_id}/schedules/{task_id}:run", status_code=202)
|
|
856
|
+
async def run_agent_schedule_now(agent_id: str, task_id: str):
|
|
857
|
+
return await studio.run_agent_schedule_now(agent_id, task_id)
|
|
858
|
+
|
|
859
|
+
@app.get("/api/v1/agents/{agent_id}/schedules/{task_id}/occurrences")
|
|
860
|
+
async def list_agent_schedule_occurrences(
|
|
861
|
+
agent_id: str,
|
|
862
|
+
task_id: str,
|
|
863
|
+
limit: int = Query(default=50, ge=1, le=200),
|
|
864
|
+
):
|
|
865
|
+
studio.get_agent_schedule(agent_id, task_id)
|
|
866
|
+
return {"items": studio.scheduler.list_occurrences(task_id, limit=limit)}
|
|
867
|
+
|
|
507
868
|
@app.post("/api/v1/workspaces:open")
|
|
508
869
|
async def open_workspace(payload: WorkspaceOpenRequest):
|
|
509
870
|
# This endpoint only reconnects to the daemon's already-bound root. Do
|
|
@@ -580,57 +941,117 @@ def create_studio_app(
|
|
|
580
941
|
"Codex Studio 只支持只读本地运行",
|
|
581
942
|
status_code=422,
|
|
582
943
|
)
|
|
583
|
-
|
|
584
|
-
queue: asyncio.Queue = asyncio.Queue()
|
|
585
|
-
|
|
586
|
-
def observe(event):
|
|
587
|
-
queue.put_nowait(event)
|
|
588
|
-
|
|
589
|
-
# The Operation owns the runtime task. The SSE response is only one
|
|
590
|
-
# observer, so refreshing or switching chats cannot cancel the Run.
|
|
591
|
-
operation = studio.submit_studio_run(
|
|
944
|
+
return _stream_studio_run(
|
|
592
945
|
build_id,
|
|
593
|
-
payload.input.content,
|
|
946
|
+
user_input=payload.input.content,
|
|
594
947
|
session_id=payload.session_id,
|
|
595
948
|
model=payload.model,
|
|
596
949
|
sandbox=payload.sandbox,
|
|
597
950
|
idempotency_key=key,
|
|
598
|
-
on_event=observe,
|
|
599
951
|
)
|
|
600
952
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
if event is not None:
|
|
608
|
-
data = json.dumps(
|
|
609
|
-
event.data,
|
|
610
|
-
ensure_ascii=False,
|
|
611
|
-
separators=(",", ":"),
|
|
612
|
-
)
|
|
613
|
-
yield f"id: {event.id}\nevent: {event.type}\ndata: {data}\n\n"
|
|
614
|
-
current = studio.operations.get(operation.id)
|
|
615
|
-
if (
|
|
616
|
-
current.status
|
|
617
|
-
in {
|
|
618
|
-
OperationStatus.SUCCEEDED,
|
|
619
|
-
OperationStatus.FAILED,
|
|
620
|
-
OperationStatus.CANCELLED,
|
|
621
|
-
OperationStatus.INTERRUPTED,
|
|
622
|
-
}
|
|
623
|
-
and queue.empty()
|
|
624
|
-
):
|
|
625
|
-
if current.status == OperationStatus.FAILED:
|
|
626
|
-
data = json.dumps(current.error or {}, ensure_ascii=False)
|
|
627
|
-
yield f"event: run.failed\ndata: {data}\n\n"
|
|
628
|
-
break
|
|
953
|
+
@app.get("/api/v1/builds/{build_id}/conversation-surface")
|
|
954
|
+
async def get_conversation_surface(
|
|
955
|
+
build_id: str,
|
|
956
|
+
session_id: str = Query(alias="sessionId", min_length=1, max_length=256),
|
|
957
|
+
):
|
|
958
|
+
return studio.conversation_surface(build_id, session_id=session_id)
|
|
629
959
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
960
|
+
@app.get("/api/v1/agents/{agent_id}/conversation-surface")
|
|
961
|
+
async def get_agent_conversation_surface(
|
|
962
|
+
agent_id: str,
|
|
963
|
+
session_id: str = Query(alias="sessionId", min_length=1, max_length=256),
|
|
964
|
+
):
|
|
965
|
+
"""Resolve the immutable Build and its composer contract atomically.
|
|
966
|
+
|
|
967
|
+
The browser selects an Agent, not a mutable Draft or a Build id. This
|
|
968
|
+
route gives it the same current-Build decision used by chat and local
|
|
969
|
+
Scheduler authoring, so controls cannot be rendered from a stale or
|
|
970
|
+
unrelated Build.
|
|
971
|
+
"""
|
|
972
|
+
|
|
973
|
+
build = await studio.ensure_current_build(agent_id)
|
|
974
|
+
return {
|
|
975
|
+
"buildId": build.id,
|
|
976
|
+
"surface": studio.conversation_surface(
|
|
977
|
+
build.id,
|
|
978
|
+
session_id=session_id,
|
|
979
|
+
),
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
@app.post("/api/v1/builds/{build_id}/conversation:stream")
|
|
983
|
+
async def stream_conversation_turn(
|
|
984
|
+
build_id: str,
|
|
985
|
+
payload: ConversationTurnRequest,
|
|
986
|
+
idempotency_key: str | None = Header(default=None, alias="Idempotency-Key"),
|
|
987
|
+
):
|
|
988
|
+
key = _require_idempotency_key(idempotency_key)
|
|
989
|
+
conversation_input = payload.input
|
|
990
|
+
if conversation_input.idempotency_key != key:
|
|
991
|
+
raise StudioError(
|
|
992
|
+
"CONVERSATION_IDEMPOTENCY_MISMATCH",
|
|
993
|
+
"ConversationInput 的幂等键必须与请求头一致",
|
|
994
|
+
status_code=422,
|
|
995
|
+
)
|
|
996
|
+
surface = studio.conversation_surface(
|
|
997
|
+
build_id,
|
|
998
|
+
session_id=conversation_input.session_id,
|
|
999
|
+
)
|
|
1000
|
+
try:
|
|
1001
|
+
validate_conversation_input(surface, conversation_input)
|
|
1002
|
+
except ValueError as exc:
|
|
1003
|
+
raise StudioError(
|
|
1004
|
+
"CONVERSATION_INPUT_UNSUPPORTED",
|
|
1005
|
+
"当前 Agent 不支持此会话输入",
|
|
1006
|
+
status_code=422,
|
|
1007
|
+
details={"reason": str(exc), "surfaceId": surface.surface_id},
|
|
1008
|
+
) from exc
|
|
1009
|
+
text_parts = [
|
|
1010
|
+
part.text for part in conversation_input.parts if isinstance(part, ConversationTextPart)
|
|
1011
|
+
]
|
|
1012
|
+
runtime_parts: list[dict[str, str]] = [
|
|
1013
|
+
{"type": "text", "text": value} for value in text_parts
|
|
1014
|
+
]
|
|
1015
|
+
attachment_names: list[str] = []
|
|
1016
|
+
for part in conversation_input.parts:
|
|
1017
|
+
if not isinstance(part, ConversationAttachmentPart):
|
|
1018
|
+
continue
|
|
1019
|
+
stored = studio.conversation_attachments.resolve(part.attachment_ref)
|
|
1020
|
+
if part.media_type.lower() != stored.media_type.lower():
|
|
1021
|
+
raise StudioError(
|
|
1022
|
+
"CONVERSATION_ATTACHMENT_METADATA_MISMATCH",
|
|
1023
|
+
"会话附件类型与已上传内容不一致",
|
|
1024
|
+
status_code=422,
|
|
1025
|
+
details={"attachmentRef": part.attachment_ref},
|
|
1026
|
+
)
|
|
1027
|
+
encoded = base64.b64encode(stored.path.read_bytes()).decode("ascii")
|
|
1028
|
+
data_url = f"data:{stored.media_type};base64,{encoded}"
|
|
1029
|
+
attachment_names.append(part.name or stored.name)
|
|
1030
|
+
if stored.media_type.startswith("image/"):
|
|
1031
|
+
runtime_parts.append({"type": "image", "url": data_url})
|
|
1032
|
+
else:
|
|
1033
|
+
runtime_parts.append(
|
|
1034
|
+
{
|
|
1035
|
+
"type": "input_file",
|
|
1036
|
+
"filename": part.name or stored.name,
|
|
1037
|
+
"file_data": data_url,
|
|
1038
|
+
}
|
|
1039
|
+
)
|
|
1040
|
+
display_input = "\n".join(text_parts).strip()
|
|
1041
|
+
if not display_input:
|
|
1042
|
+
display_input = "请处理本轮附件:" + "、".join(attachment_names)
|
|
1043
|
+
return _stream_studio_run(
|
|
1044
|
+
build_id,
|
|
1045
|
+
user_input=display_input,
|
|
1046
|
+
session_id=conversation_input.session_id,
|
|
1047
|
+
model=conversation_input.model_ref,
|
|
1048
|
+
sandbox=payload.sandbox,
|
|
1049
|
+
reasoning_effort=conversation_input.reasoning,
|
|
1050
|
+
approval_mode=conversation_input.approval_mode,
|
|
1051
|
+
collaboration_mode=conversation_input.collaboration_mode,
|
|
1052
|
+
goal_objective=conversation_input.goal_objective,
|
|
1053
|
+
runtime_input=runtime_parts if attachment_names else None,
|
|
1054
|
+
idempotency_key=key,
|
|
634
1055
|
)
|
|
635
1056
|
|
|
636
1057
|
@app.post("/api/v1/agents", status_code=201)
|
|
@@ -644,6 +1065,15 @@ def create_studio_app(
|
|
|
644
1065
|
runtime=payload.runtime,
|
|
645
1066
|
)
|
|
646
1067
|
|
|
1068
|
+
@app.post("/api/v1/conversation-attachments", status_code=201)
|
|
1069
|
+
async def upload_conversation_attachment(file: UploadFile = File(...)):
|
|
1070
|
+
content = await file.read(studio.conversation_attachments.MAX_BYTES + 1)
|
|
1071
|
+
return studio.conversation_attachments.store(
|
|
1072
|
+
content,
|
|
1073
|
+
filename=file.filename or "attachment",
|
|
1074
|
+
media_type=file.content_type or "application/octet-stream",
|
|
1075
|
+
)
|
|
1076
|
+
|
|
647
1077
|
@app.post("/api/v1/assets/agent-avatars", status_code=201)
|
|
648
1078
|
async def upload_agent_avatar(request: Request):
|
|
649
1079
|
declared_size = request.headers.get("Content-Length")
|
|
@@ -955,6 +1385,8 @@ def create_studio_app(
|
|
|
955
1385
|
interaction_id,
|
|
956
1386
|
name=payload.name,
|
|
957
1387
|
data=payload.data,
|
|
1388
|
+
expected_revision=payload.expected_revision,
|
|
1389
|
+
idempotency_key=payload.idempotency_key,
|
|
958
1390
|
)
|
|
959
1391
|
|
|
960
1392
|
@app.get("/api/v1/runs/{run_id}/context")
|
|
@@ -1312,9 +1744,7 @@ def create_studio_app(
|
|
|
1312
1744
|
):
|
|
1313
1745
|
"""List Server-owned sessions for this local deployment receipt only."""
|
|
1314
1746
|
|
|
1315
|
-
return await studio.cloud.list_cloud_chat_sessions(
|
|
1316
|
-
deployment_id, page=page, size=size
|
|
1317
|
-
)
|
|
1747
|
+
return await studio.cloud.list_cloud_chat_sessions(deployment_id, page=page, size=size)
|
|
1318
1748
|
|
|
1319
1749
|
@app.get("/api/v1/deployments/{deployment_id}/cloud-chat/models")
|
|
1320
1750
|
async def list_cloud_chat_models(deployment_id: str):
|
|
@@ -1331,9 +1761,7 @@ def create_studio_app(
|
|
|
1331
1761
|
|
|
1332
1762
|
return await studio.cloud.create_cloud_chat_session(deployment_id)
|
|
1333
1763
|
|
|
1334
|
-
@app.get(
|
|
1335
|
-
"/api/v1/deployments/{deployment_id}/cloud-chat/sessions/{session_id}/messages"
|
|
1336
|
-
)
|
|
1764
|
+
@app.get("/api/v1/deployments/{deployment_id}/cloud-chat/sessions/{session_id}/messages")
|
|
1337
1765
|
async def list_cloud_chat_messages(
|
|
1338
1766
|
deployment_id: str,
|
|
1339
1767
|
session_id: str,
|
|
@@ -1347,25 +1775,23 @@ def create_studio_app(
|
|
|
1347
1775
|
limit=limit,
|
|
1348
1776
|
)
|
|
1349
1777
|
|
|
1350
|
-
@app.get(
|
|
1351
|
-
"/api/v1/deployments/{deployment_id}/cloud-chat/sessions/{session_id}/events"
|
|
1352
|
-
)
|
|
1778
|
+
@app.get("/api/v1/deployments/{deployment_id}/cloud-chat/sessions/{session_id}/events")
|
|
1353
1779
|
async def list_cloud_chat_events(
|
|
1354
1780
|
deployment_id: str,
|
|
1355
1781
|
session_id: str,
|
|
1356
1782
|
after_seq_id: int | None = Query(default=None, alias="afterSeqId", ge=0),
|
|
1783
|
+
offset: int | None = Query(default=None, ge=0),
|
|
1357
1784
|
limit: int = Query(default=200, ge=1, le=1000),
|
|
1358
1785
|
):
|
|
1359
1786
|
return await studio.cloud.list_cloud_chat_events(
|
|
1360
1787
|
deployment_id,
|
|
1361
1788
|
session_id=session_id,
|
|
1362
1789
|
after_seq_id=after_seq_id,
|
|
1790
|
+
offset=offset,
|
|
1363
1791
|
limit=limit,
|
|
1364
1792
|
)
|
|
1365
1793
|
|
|
1366
|
-
@app.get(
|
|
1367
|
-
"/api/v1/deployments/{deployment_id}/cloud-chat/sessions/{session_id}/events/stream"
|
|
1368
|
-
)
|
|
1794
|
+
@app.get("/api/v1/deployments/{deployment_id}/cloud-chat/sessions/{session_id}/events/stream")
|
|
1369
1795
|
async def stream_cloud_chat_events(
|
|
1370
1796
|
request: Request,
|
|
1371
1797
|
deployment_id: str,
|
|
@@ -1399,9 +1825,7 @@ def create_studio_app(
|
|
|
1399
1825
|
if not isinstance(event, dict):
|
|
1400
1826
|
continue
|
|
1401
1827
|
event_payload = (
|
|
1402
|
-
event.get("payload")
|
|
1403
|
-
if isinstance(event.get("payload"), dict)
|
|
1404
|
-
else event
|
|
1828
|
+
event.get("payload") if isinstance(event.get("payload"), dict) else event
|
|
1405
1829
|
)
|
|
1406
1830
|
seq = int(
|
|
1407
1831
|
event_payload.get("seq")
|
|
@@ -1427,25 +1851,44 @@ def create_studio_app(
|
|
|
1427
1851
|
if isinstance(event_payload.get("content"), dict)
|
|
1428
1852
|
else {}
|
|
1429
1853
|
)
|
|
1430
|
-
status = str(
|
|
1431
|
-
event_payload.get("status") or content.get("status") or ""
|
|
1432
|
-
).lower()
|
|
1854
|
+
status = str(event_payload.get("status") or content.get("status") or "").lower()
|
|
1433
1855
|
event_is_terminal = event_type in {
|
|
1434
|
-
"run.completed",
|
|
1435
|
-
"run.
|
|
1856
|
+
"run.completed",
|
|
1857
|
+
"run.complete",
|
|
1858
|
+
"run.succeeded",
|
|
1859
|
+
"run.failed",
|
|
1860
|
+
"run.cancelled",
|
|
1861
|
+
"run.expired",
|
|
1862
|
+
"run.error",
|
|
1436
1863
|
} or (
|
|
1437
1864
|
event_type in {"run_status", "run.status"}
|
|
1438
|
-
and status
|
|
1439
|
-
|
|
1440
|
-
"
|
|
1865
|
+
and status
|
|
1866
|
+
in {
|
|
1867
|
+
"completed",
|
|
1868
|
+
"complete",
|
|
1869
|
+
"succeeded",
|
|
1870
|
+
"success",
|
|
1871
|
+
"failed",
|
|
1872
|
+
"cancelled",
|
|
1873
|
+
"canceled",
|
|
1874
|
+
"expired",
|
|
1875
|
+
"error",
|
|
1876
|
+
"aborted",
|
|
1441
1877
|
}
|
|
1442
1878
|
)
|
|
1443
1879
|
terminal = terminal or event_is_terminal
|
|
1444
1880
|
emitted = True
|
|
1881
|
+
projected_event = dict(event)
|
|
1882
|
+
conversation_item = _cloud_event_conversation_item(
|
|
1883
|
+
event,
|
|
1884
|
+
session_id=session_id,
|
|
1885
|
+
)
|
|
1886
|
+
if conversation_item is not None:
|
|
1887
|
+
projected_event["conversationItem"] = conversation_item
|
|
1445
1888
|
yield (
|
|
1446
1889
|
(f"id: {seq}\n" if seq else "")
|
|
1447
1890
|
+ "event: session.event\n"
|
|
1448
|
-
+ f"data: {json.dumps(
|
|
1891
|
+
+ f"data: {json.dumps(projected_event, ensure_ascii=False)}\n\n"
|
|
1449
1892
|
)
|
|
1450
1893
|
if terminal or payload.get("session_deleted"):
|
|
1451
1894
|
break
|
|
@@ -1465,9 +1908,7 @@ def create_studio_app(
|
|
|
1465
1908
|
status_code=204,
|
|
1466
1909
|
)
|
|
1467
1910
|
async def delete_cloud_chat_session(deployment_id: str, session_id: str):
|
|
1468
|
-
await studio.cloud.delete_cloud_chat_session(
|
|
1469
|
-
deployment_id, session_id=session_id
|
|
1470
|
-
)
|
|
1911
|
+
await studio.cloud.delete_cloud_chat_session(deployment_id, session_id=session_id)
|
|
1471
1912
|
return Response(status_code=204)
|
|
1472
1913
|
|
|
1473
1914
|
@app.post(
|
|
@@ -1594,5 +2035,6 @@ def create_studio_app(
|
|
|
1594
2035
|
runtime_model_catalog=runtime_model_catalog,
|
|
1595
2036
|
)
|
|
1596
2037
|
register_memory_routes(app, studio)
|
|
2038
|
+
register_plugin_routes(app, studio)
|
|
1597
2039
|
|
|
1598
2040
|
return app
|