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.
Files changed (250) hide show
  1. {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/METADATA +18 -25
  2. {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/RECORD +224 -169
  3. ksadk/_build_provenance.json +1 -0
  4. ksadk/agui/_agent_helpers.py +4 -0
  5. ksadk/api/client.py +3 -0
  6. ksadk/builders/managed_runtime_builder.py +3 -0
  7. ksadk/cli/__init__.py +6 -0
  8. ksadk/cli/cmd_plugin.py +657 -0
  9. ksadk/codex/runtime.py +50 -8
  10. ksadk/configs/env_registry.py +31 -3
  11. ksadk/configs/settings.py +9 -0
  12. ksadk/context_engine/contributors.py +73 -8
  13. ksadk/conversations/__init__.py +19 -0
  14. ksadk/conversations/context.py +104 -24
  15. ksadk/conversations/contracts.py +266 -0
  16. ksadk/conversations/message_projection.py +19 -1
  17. ksadk/conversations/projector.py +616 -0
  18. ksadk/conversations/reducer.py +98 -0
  19. ksadk/events/_v1_compat/projection.py +2 -0
  20. ksadk/events/adapters/codex.py +190 -0
  21. ksadk/harness/__init__.py +45 -35
  22. ksadk/harness/runtime.py +60 -1
  23. ksadk/kernel/contract_fingerprints.py +5 -4
  24. ksadk/kernel/contracts.py +8 -0
  25. ksadk/kernel/control.py +1 -0
  26. ksadk/kernel/ingress.py +21 -0
  27. ksadk/kernel/memory_store.py +1 -0
  28. ksadk/kernel/postgres_store.py +1 -0
  29. ksadk/kernel/sqlite_store.py +1 -0
  30. ksadk/kernel/worker.py +11 -0
  31. ksadk/memory/providers/local_sqlite.py +7 -0
  32. ksadk/plugins/__init__.py +167 -0
  33. ksadk/plugins/bridges/__init__.py +45 -0
  34. ksadk/plugins/bridges/codex.py +552 -0
  35. ksadk/plugins/bridges/dsh.py +1116 -0
  36. ksadk/plugins/builtins.py +747 -0
  37. ksadk/plugins/bundle.py +246 -0
  38. ksadk/plugins/bundle_security.py +167 -0
  39. ksadk/plugins/composition.py +529 -0
  40. ksadk/plugins/context_contributor.py +437 -0
  41. ksadk/plugins/contracts.py +451 -0
  42. ksadk/plugins/dsh_toolchain.py +935 -0
  43. ksadk/plugins/ecosystem_bridge.py +1105 -0
  44. ksadk/plugins/ecosystem_probe.py +191 -0
  45. ksadk/plugins/host.py +724 -0
  46. ksadk/plugins/providers/__init__.py +85 -0
  47. ksadk/plugins/providers/bundles/ksadk-codex/cordis.patch.yml +3 -0
  48. ksadk/plugins/providers/bundles/ksadk-codex/index.mjs +18 -0
  49. ksadk/plugins/providers/bundles/ksadk-codex/package.json +19 -0
  50. ksadk/plugins/providers/bundles/ksadk-harness/cordis.patch.yml +3 -0
  51. ksadk/plugins/providers/bundles/ksadk-harness/index.mjs +19 -0
  52. ksadk/plugins/providers/bundles/ksadk-harness/package.json +19 -0
  53. ksadk/plugins/providers/codex.py +722 -0
  54. ksadk/plugins/providers/codex_dsh.py +83 -0
  55. ksadk/plugins/providers/dsh.py +951 -0
  56. ksadk/plugins/providers/dsh_descriptor_host.py +226 -0
  57. ksadk/plugins/providers/harness.py +627 -0
  58. ksadk/plugins/providers/harness_dsh.py +91 -0
  59. ksadk/plugins/providers/legacy.py +215 -0
  60. ksadk/plugins/providers/legacy_catalog.py +100 -0
  61. ksadk/plugins/providers/shipped_dsh.py +370 -0
  62. ksadk/plugins/resolver.py +331 -0
  63. ksadk/plugins/subagent_providers/__init__.py +12 -0
  64. ksadk/plugins/subagent_providers/codex.py +443 -0
  65. ksadk/plugins/subagents.py +333 -0
  66. ksadk/runtime/_runner_adapter/stream_mapping.py +51 -42
  67. ksadk/runtime/adapter.py +1 -0
  68. ksadk/runtime/factory.py +76 -2
  69. ksadk/runtime/framework_adapters.py +18 -0
  70. ksadk/runtime/runner_adapter.py +5 -0
  71. ksadk/sandbox/__init__.py +2 -1
  72. ksadk/sandbox/factory.py +72 -0
  73. ksadk/scheduler/__init__.py +18 -0
  74. ksadk/scheduler/calendar.py +102 -0
  75. ksadk/scheduler/contracts.py +254 -0
  76. ksadk/scheduler/dispatcher.py +129 -0
  77. ksadk/scheduler/engine.py +416 -0
  78. ksadk/scheduler/sqlite_store.py +563 -0
  79. ksadk/server/static/assets/{ArtifactsPanel-DSU1sWD_.js → ArtifactsPanel-10GPXIJ6.js} +1 -1
  80. ksadk/server/static/assets/{CodeBlock-DOH6MVcn.js → CodeBlock-BXk1l-G1.js} +1 -1
  81. ksadk/server/static/assets/{MathMessageMarkdown-BL8my1R2.js → MathMessageMarkdown-D9uB55Oh.js} +1 -1
  82. ksadk/server/static/assets/{MermaidBlock-Dz4IP-Tx.js → MermaidBlock--OEYoXIJ.js} +6 -6
  83. ksadk/server/static/assets/{NativeTerminalPanel-DUrK0JpZ.js → NativeTerminalPanel-DrEe1syq.js} +1 -1
  84. ksadk/server/static/assets/{abnfDiagram-VCTEODGH-g20pFzNV.js → abnfDiagram-VCTEODGH-eeTFJLvo.js} +1 -1
  85. ksadk/server/static/assets/{arc-C4FzinUA.js → arc-S3ZNbIAd.js} +1 -1
  86. ksadk/server/static/assets/architecture-7GRP2DOG-Dk9nRQ6-.js +1 -0
  87. ksadk/server/static/assets/{architectureDiagram-5GKGNRK7-DpIqL5h4.js → architectureDiagram-5GKGNRK7-CRjgEJDX.js} +1 -1
  88. ksadk/server/static/assets/{blockDiagram-NRAW4CY4-BjkljTNz.js → blockDiagram-NRAW4CY4-C7bsBoNH.js} +1 -1
  89. ksadk/server/static/assets/{c4Diagram-UCG6FXSJ-Dk_ieq2X.js → c4Diagram-UCG6FXSJ-BX3c33Ob.js} +1 -1
  90. ksadk/server/static/assets/channel-s354Yo6o.js +1 -0
  91. ksadk/server/static/assets/chunk-2Q5K7J3B-CDpPpKR5.js +1 -0
  92. ksadk/server/static/assets/{chunk-5VM5RSS4-BVHAchFb.js → chunk-5VM5RSS4-bZ7ws-8m.js} +1 -1
  93. ksadk/server/static/assets/{chunk-F27PBJKO-C-ipQzuS.js → chunk-F27PBJKO-D2r0pvhY.js} +1 -1
  94. ksadk/server/static/assets/{chunk-G27WJ6UU-C2EyJbIK.js → chunk-G27WJ6UU-CZwF3oUP.js} +1 -1
  95. ksadk/server/static/assets/{chunk-JWPE2WC7-vYvVJb_M.js → chunk-JWPE2WC7-DigFYCML.js} +1 -1
  96. ksadk/server/static/assets/{chunk-LCL6LL3I-BcI6ysPT.js → chunk-LCL6LL3I-CnorGV5q.js} +1 -1
  97. ksadk/server/static/assets/{chunk-POPQ4Y6H-C030x_Z1.js → chunk-POPQ4Y6H-CexntQA-.js} +1 -1
  98. ksadk/server/static/assets/{chunk-RHFEMEQ7-yyGpsz4n.js → chunk-RHFEMEQ7-CHTC7UxA.js} +1 -1
  99. ksadk/server/static/assets/{chunk-SVP7TREG-BLTlmMU7.js → chunk-SVP7TREG-BC1EWt_-.js} +1 -1
  100. ksadk/server/static/assets/{chunk-XXDRQBXY-C_32ArgP.js → chunk-XXDRQBXY-BTG24xN0.js} +1 -1
  101. ksadk/server/static/assets/classDiagram-DTDB5LWJ-Dd0nucl9.js +1 -0
  102. ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-Dd0nucl9.js +1 -0
  103. ksadk/server/static/assets/{cose-bilkent-JH36ORCC-9YQYwdl0.js → cose-bilkent-JH36ORCC-CkonRdAS.js} +1 -1
  104. ksadk/server/static/assets/cynefin-OW5HDTMX-BBlT4uMf.js +1 -0
  105. ksadk/server/static/assets/{cynefinDiagram-5FMLGOSQ-ErLF5N13.js → cynefinDiagram-5FMLGOSQ-DEOCBe15.js} +1 -1
  106. ksadk/server/static/assets/{dagre-3AP2YEHR-DFiQF-6f.js → dagre-3AP2YEHR-DBThPuLa.js} +1 -1
  107. ksadk/server/static/assets/{dagre-BuhGnRI1.js → dagre-ClTPrTht.js} +1 -1
  108. ksadk/server/static/assets/{diagram-S7CK7UJ4-Bt1v8-GC.js → diagram-S7CK7UJ4-DsZHrnpJ.js} +1 -1
  109. ksadk/server/static/assets/{diagram-UQ7AKVKN-DSCxJdBK.js → diagram-UQ7AKVKN-BJR4nhVr.js} +1 -1
  110. ksadk/server/static/assets/{diagram-VSXAHHWV-DHfYwp_9.js → diagram-VSXAHHWV-xzcERX0a.js} +1 -1
  111. ksadk/server/static/assets/{diagram-VX7I27RA-DdzD-4Le.js → diagram-VX7I27RA-tn2xM1xu.js} +1 -1
  112. ksadk/server/static/assets/{diagram-Z3DM3KII-Du-nAJ9F.js → diagram-Z3DM3KII-BZPGBkw5.js} +1 -1
  113. ksadk/server/static/assets/{dist-ClxGviKw.js → dist-4zN1_tq5.js} +1 -1
  114. ksadk/server/static/assets/{dist-BE7bu-DL.js → dist-8VWI-2bZ.js} +1 -1
  115. ksadk/server/static/assets/{dist-BX8z72IC.js → dist-B-46q3Hk.js} +1 -1
  116. ksadk/server/static/assets/{dist-BjU6y-A2.js → dist-B0gdk4rl.js} +1 -1
  117. ksadk/server/static/assets/{dist-C_wsv-Qd.js → dist-B1oWRmrH.js} +1 -1
  118. ksadk/server/static/assets/{dist-ngtN0DJB.js → dist-BI1qQH4n.js} +1 -1
  119. ksadk/server/static/assets/{dist-dydu68Fl.js → dist-BJLUwH6W.js} +1 -1
  120. ksadk/server/static/assets/{dist-Dq9gLD7L.js → dist-Bbwqa8TG.js} +1 -1
  121. ksadk/server/static/assets/{dist-CttYUqzS.js → dist-Btyo4vX4.js} +1 -1
  122. ksadk/server/static/assets/{dist-CXYYBw3_.js → dist-C71CsujO.js} +1 -1
  123. ksadk/server/static/assets/{dist-dDdADKFA.js → dist-CXpH_Rdd.js} +1 -1
  124. ksadk/server/static/assets/{dist-CSh_DE14.js → dist-Cq4OL6nO.js} +1 -1
  125. ksadk/server/static/assets/{dist-8hqcx0sU.js → dist-D1V3f1B4.js} +1 -1
  126. ksadk/server/static/assets/{dist-BVDffZ0U.js → dist-DJmCPRVB.js} +1 -1
  127. ksadk/server/static/assets/{dist-DnfXs8Vn.js → dist-DQAB0qn_.js} +1 -1
  128. ksadk/server/static/assets/{dist-DNp6rLRA.js → dist-DUwKa_l6.js} +1 -1
  129. ksadk/server/static/assets/{dist-B7seoj3d.js → dist-DdRBIceA.js} +1 -1
  130. ksadk/server/static/assets/{dist-BytlxIDT.js → dist-DfGFrb0v.js} +1 -1
  131. ksadk/server/static/assets/{dist-Bf2M8m3N.js → dist-DjhwGUgZ.js} +1 -1
  132. ksadk/server/static/assets/{dist-W-TIVntx.js → dist-DyqV4fL4.js} +1 -1
  133. ksadk/server/static/assets/{dist-DUX-id_F.js → dist-M67Hwu0n.js} +1 -1
  134. ksadk/server/static/assets/{dist-DmldrHd_.js → dist-u8tVH5Ak.js} +1 -1
  135. ksadk/server/static/assets/{ebnfDiagram-PWID7BFC-Da9C9NO1.js → ebnfDiagram-PWID7BFC-DVfMsLge.js} +1 -1
  136. ksadk/server/static/assets/{erDiagram-SSCWMZ5O-EAGqqR79.js → erDiagram-SSCWMZ5O-CWEZSYaw.js} +1 -1
  137. ksadk/server/static/assets/eventmodeling-NTZA5JFV-Dr5okd8V.js +1 -0
  138. ksadk/server/static/assets/flowDiagram-A5DVABFB-VEO-cVcO.js +1 -0
  139. ksadk/server/static/assets/{ganttDiagram-EL5Y4UJY-DqJsKb59.js → ganttDiagram-EL5Y4UJY-TWIxoyYL.js} +1 -1
  140. ksadk/server/static/assets/gitGraph-4MIJSDKK-CKiKY9Cf.js +1 -0
  141. ksadk/server/static/assets/{gitGraphDiagram-WWUBYQGX-B6g4dtDi.js → gitGraphDiagram-WWUBYQGX-DDh2hd2S.js} +1 -1
  142. ksadk/server/static/assets/index-B2k_urY8.js +241 -0
  143. ksadk/server/static/assets/index-ILPt6IWF.css +2 -0
  144. ksadk/server/static/assets/info-A6RAGUB7-ubA4Nm_p.js +1 -0
  145. ksadk/server/static/assets/{infoDiagram-RXCK75RN-Djm_nbd5.js → infoDiagram-RXCK75RN-CgoyWiDR.js} +1 -1
  146. ksadk/server/static/assets/{ishikawaDiagram-5VMMS53U-DWya9SG2.js → ishikawaDiagram-5VMMS53U-CyRIu_om.js} +1 -1
  147. ksadk/server/static/assets/{journeyDiagram-EYS64GPL-DutxhSNI.js → journeyDiagram-EYS64GPL-DN5JXHzN.js} +1 -1
  148. ksadk/server/static/assets/{kanban-definition-3QL26DDD-DJdOF8Fm.js → kanban-definition-3QL26DDD-CNP3ui66.js} +1 -1
  149. ksadk/server/static/assets/{katex-vFWytM5c.js → katex-DWEzQy1d.js} +1 -1
  150. ksadk/server/static/assets/{linear-BHjG8b-J.js → linear-C-qHp7d2.js} +1 -1
  151. ksadk/server/static/assets/{mermaid-parser.core-KGSy4jWT.js → mermaid-parser.core-Cl-K943T.js} +2 -2
  152. ksadk/server/static/assets/{mindmap-definition-FBJOCRG2-9-HC88D4.js → mindmap-definition-FBJOCRG2-D5DB5mYm.js} +1 -1
  153. ksadk/server/static/assets/packet-AYTQ26CC-C7R3NUlK.js +1 -0
  154. ksadk/server/static/assets/{pegDiagram-XKGWAZYB-DrEZd4fD.js → pegDiagram-XKGWAZYB-BQVc9ia_.js} +1 -1
  155. ksadk/server/static/assets/pie-WAS4IAKB-BIT6qjr3.js +1 -0
  156. ksadk/server/static/assets/{pieDiagram-E7YTZNPT-DpjqOFFp.js → pieDiagram-E7YTZNPT-B6UCAvtY.js} +1 -1
  157. ksadk/server/static/assets/{quadrantDiagram-AXDQQJYC-BiNhgOXX.js → quadrantDiagram-AXDQQJYC-mIr2d3eb.js} +1 -1
  158. ksadk/server/static/assets/radar-RG4KPBEZ-l5noef9X.js +1 -0
  159. ksadk/server/static/assets/railroad-74A4TZTK-Baea6ZvM.js +1 -0
  160. ksadk/server/static/assets/railroad-abnf-HS5TGJTU-OKn8zZnU.js +1 -0
  161. ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-DdKAKC4l.js +1 -0
  162. ksadk/server/static/assets/railroad-peg-WCYAUIDC-Bds8udlG.js +1 -0
  163. ksadk/server/static/assets/{railroadDiagram-O6MQD6OU-_Y3Ojg6G.js → railroadDiagram-O6MQD6OU-Dt3FFDmK.js} +1 -1
  164. ksadk/server/static/assets/{requirementDiagram-EFPCY7ZU-DeCFdg9K.js → requirementDiagram-EFPCY7ZU-Cz_vQtgF.js} +1 -1
  165. ksadk/server/static/assets/{sankeyDiagram-P5KCCOFB-CPH75rhw.js → sankeyDiagram-P5KCCOFB-wlO1TsH5.js} +1 -1
  166. ksadk/server/static/assets/{sequenceDiagram-WJ2MYXX4-OEpQQdr4.js → sequenceDiagram-WJ2MYXX4-CHRbElYq.js} +1 -1
  167. ksadk/server/static/assets/{sizeCapture-X5ZJPWSS-heUErzhI.js → sizeCapture-X5ZJPWSS-DXX54iyO.js} +1 -1
  168. ksadk/server/static/assets/{stateDiagram-HBIQ2CUA-DvA3jSMB.js → stateDiagram-HBIQ2CUA-Dp_lw9II.js} +1 -1
  169. ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-5b-8o2iS.js +1 -0
  170. ksadk/server/static/assets/{swimlanes-XN3QIQJK-CR-dfN_t.js → swimlanes-XN3QIQJK-D8O5UlLG.js} +1 -1
  171. ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-uf6WPRA4.js +8 -0
  172. ksadk/server/static/assets/{timeline-definition-24CTP7MA-DAVJf1TX.js → timeline-definition-24CTP7MA-8mfKcxvG.js} +1 -1
  173. ksadk/server/static/assets/treeView-Q6P3EWNA-CK5CSrSO.js +1 -0
  174. ksadk/server/static/assets/treemap-WGGIJYW6-Ch2BKgAM.js +1 -0
  175. ksadk/server/static/assets/{vennDiagram-4TSXK5OY-DwM8eDkh.js → vennDiagram-4TSXK5OY-EnNl0DE7.js} +1 -1
  176. ksadk/server/static/assets/wardley-WFR3VGLG-YW3Q0ml_.js +1 -0
  177. ksadk/server/static/assets/{wardleyDiagram-VM6X3IG4-DZL-Zz2t.js → wardleyDiagram-VM6X3IG4-DBu5fbYr.js} +1 -1
  178. ksadk/server/static/assets/{xychartDiagram-S5SC5T6Z-Dy6Yh_hu.js → xychartDiagram-S5SC5T6Z-BSVXs4IY.js} +1 -1
  179. ksadk/server/static/index.html +2 -2
  180. ksadk/skills/service_client.py +48 -0
  181. ksadk/studio/agent_lifecycle.py +26 -20
  182. ksadk/studio/api.py +513 -71
  183. ksadk/studio/api_catalog_routes.py +5 -0
  184. ksadk/studio/api_contracts.py +34 -1
  185. ksadk/studio/api_plugin_routes.py +480 -0
  186. ksadk/studio/attachment_store.py +187 -0
  187. ksadk/studio/authoring.py +10 -4
  188. ksadk/studio/authoring_coordinator.py +12 -2
  189. ksadk/studio/builder.py +123 -43
  190. ksadk/studio/cloud.py +4 -0
  191. ksadk/studio/codex_agent_service.py +12 -1
  192. ksadk/studio/codex_builder.py +54 -15
  193. ksadk/studio/codex_manifest.py +21 -1
  194. ksadk/studio/codex_run.py +16 -4
  195. ksadk/studio/compatibility_report.py +445 -0
  196. ksadk/studio/compiler.py +6 -1
  197. ksadk/studio/contracts.py +123 -4
  198. ksadk/studio/dsh_provider_registration.py +768 -0
  199. ksadk/studio/event_store.py +54 -6
  200. ksadk/studio/framework_run.py +37 -4
  201. ksadk/studio/hosted_kernel.py +1 -1
  202. ksadk/studio/operations.py +12 -2
  203. ksadk/studio/plugin_composition.py +301 -0
  204. ksadk/studio/plugin_kernel_adapter.py +128 -0
  205. ksadk/studio/plugin_runtime.py +652 -0
  206. ksadk/studio/resource_catalog.py +6 -1
  207. ksadk/studio/run_service.py +713 -75
  208. ksadk/studio/runtime_source.py +9 -1
  209. ksadk/studio/scheduler_runtime.py +374 -0
  210. ksadk/studio/scheduler_service.py +211 -0
  211. ksadk/studio/service.py +672 -82
  212. ksadk/studio/shared_web.py +167 -28
  213. ksadk/studio/soul.py +52 -0
  214. ksadk/studio/static/assets/index-DXs1Eq4M.css +1 -0
  215. ksadk/studio/static/assets/index-DeDqwTsD.js +283 -0
  216. ksadk/studio/static/index.html +2 -2
  217. ksadk/studio/validator.py +1 -1
  218. ksadk/studio/workspace.py +1 -0
  219. ksadk/version.py +1 -1
  220. ksadk_runtime_common/workspace_files/path_utils.py +41 -0
  221. ksadk/server/static/assets/architecture-7GRP2DOG-DjKratdf.js +0 -1
  222. ksadk/server/static/assets/channel-4cQHKtx1.js +0 -1
  223. ksadk/server/static/assets/chunk-2Q5K7J3B-DmgWkESh.js +0 -1
  224. ksadk/server/static/assets/classDiagram-DTDB5LWJ-DLFd9Xi0.js +0 -1
  225. ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-DLFd9Xi0.js +0 -1
  226. ksadk/server/static/assets/cynefin-OW5HDTMX-DjEM48Qp.js +0 -1
  227. ksadk/server/static/assets/eventmodeling-NTZA5JFV-CMgMaJrC.js +0 -1
  228. ksadk/server/static/assets/flowDiagram-A5DVABFB-BBKL0x3P.js +0 -1
  229. ksadk/server/static/assets/gitGraph-4MIJSDKK-Dw63mrhw.js +0 -1
  230. ksadk/server/static/assets/index-8ipRcQ-M.js +0 -240
  231. ksadk/server/static/assets/index-ByFCqjlh.css +0 -2
  232. ksadk/server/static/assets/info-A6RAGUB7-B6iAblII.js +0 -1
  233. ksadk/server/static/assets/packet-AYTQ26CC-BOYAipuq.js +0 -1
  234. ksadk/server/static/assets/pie-WAS4IAKB-Ctq0Kbku.js +0 -1
  235. ksadk/server/static/assets/radar-RG4KPBEZ-DN0-ul_0.js +0 -1
  236. ksadk/server/static/assets/railroad-74A4TZTK-C6cC_to0.js +0 -1
  237. ksadk/server/static/assets/railroad-abnf-HS5TGJTU-BimjIuRc.js +0 -1
  238. ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-CvY65ABq.js +0 -1
  239. ksadk/server/static/assets/railroad-peg-WCYAUIDC-D72Iq_Tv.js +0 -1
  240. ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BgQY03nz.js +0 -1
  241. ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-DLVw3q5Q.js +0 -8
  242. ksadk/server/static/assets/treeView-Q6P3EWNA-BMpO0wsB.js +0 -1
  243. ksadk/server/static/assets/treemap-WGGIJYW6-Et0aqzU3.js +0 -1
  244. ksadk/server/static/assets/wardley-WFR3VGLG-CbY0mhpl.js +0 -1
  245. ksadk/studio/static/assets/index-BLpcdrgW.css +0 -1
  246. ksadk/studio/static/assets/index-CYekR2Xv.js +0 -260
  247. {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/WHEEL +0 -0
  248. {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/entry_points.txt +0 -0
  249. {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/licenses/LICENSE +0 -0
  250. {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
- response = FileResponse(path, media_type="text/html")
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
- async def render():
602
- while True:
603
- try:
604
- event = await asyncio.wait_for(queue.get(), timeout=0.1)
605
- except TimeoutError:
606
- event = None
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
- return StreamingResponse(
631
- render(),
632
- media_type="text/event-stream",
633
- headers={"Cache-Control": "no-store", "X-Accel-Buffering": "no"},
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", "run.complete", "run.succeeded",
1435
- "run.failed", "run.cancelled", "run.expired", "run.error",
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 in {
1439
- "completed", "complete", "succeeded", "success",
1440
- "failed", "cancelled", "canceled", "expired", "error", "aborted",
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(event, ensure_ascii=False)}\n\n"
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