agentengine-sdk-python 0.8.0__py3-none-any.whl → 0.8.2__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 (494) hide show
  1. {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/METADATA +43 -7
  2. agentengine_sdk_python-0.8.2.dist-info/RECORD +778 -0
  3. {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/WHEEL +1 -1
  4. ksadk/a2a/__init__.py +2 -0
  5. ksadk/a2a/_space_client_events.py +381 -0
  6. ksadk/a2a/bootstrap.py +0 -2
  7. ksadk/a2a/card.py +6 -111
  8. ksadk/a2a/control_plane.py +239 -1
  9. ksadk/a2a/event_adapter.py +156 -64
  10. ksadk/a2a/executor.py +195 -172
  11. ksadk/a2a/routes.py +12 -5
  12. ksadk/a2a/server.py +1 -8
  13. ksadk/a2a/service_env.py +33 -0
  14. ksadk/a2a/space_client.py +43 -349
  15. ksadk/a2a_card.py +101 -0
  16. ksadk/a2ui/core.py +222 -53
  17. ksadk/agui/_agent_helpers.py +243 -0
  18. ksadk/agui/a2ui_projection.py +12 -5
  19. ksadk/agui/agent.py +474 -275
  20. ksadk/agui/config.py +11 -5
  21. ksadk/agui/routes.py +7 -15
  22. ksadk/api/client.py +530 -40
  23. ksadk/api/environment.py +27 -0
  24. ksadk/builders/code_builder.py +352 -53
  25. ksadk/builders/container_builder.py +71 -44
  26. ksadk/builders/framework_requirements.py +2 -1
  27. ksadk/builders/ks3_uploader.py +2 -2
  28. ksadk/builders/managed_runtime_builder.py +60 -28
  29. ksadk/cli/__init__.py +25 -0
  30. ksadk/cli/cmd_a2a.py +18 -31
  31. ksadk/cli/cmd_create.py +13 -26
  32. ksadk/cli/cmd_dashboard.py +15 -1
  33. ksadk/cli/cmd_deploy.py +28 -3
  34. ksadk/cli/cmd_eval.py +443 -0
  35. ksadk/cli/cmd_evalset.py +284 -0
  36. ksadk/cli/cmd_files.py +5 -1
  37. ksadk/cli/cmd_hermes.py +137 -163
  38. ksadk/cli/cmd_invoke.py +28 -38
  39. ksadk/cli/cmd_launch.py +12 -0
  40. ksadk/cli/cmd_managed_runtime.py +95 -0
  41. ksadk/cli/cmd_observe.py +85 -0
  42. ksadk/cli/cmd_openclaw.py +85 -479
  43. ksadk/cli/cmd_replay.py +28 -4
  44. ksadk/cli/cmd_run.py +29 -67
  45. ksadk/cli/cmd_studio.py +201 -0
  46. ksadk/cli/cmd_web.py +15 -6
  47. ksadk/cli/dry_run.py +2 -1
  48. ksadk/cli/env_options.py +83 -0
  49. ksadk/cli/global_options.py +3 -1
  50. ksadk/cli/hermes_env.py +151 -0
  51. ksadk/cli/invoke_payload.py +49 -0
  52. ksadk/cli/network_options.py +7 -5
  53. ksadk/cli/openclaw_env.py +458 -0
  54. ksadk/cli/runtime_bootstrap.py +51 -0
  55. ksadk/cli/ui.py +6 -0
  56. ksadk/cli/workflow_common.py +5 -5
  57. ksadk/codex/__init__.py +3 -3
  58. ksadk/codex/client.py +833 -49
  59. ksadk/codex/phase.py +1 -1
  60. ksadk/codex/runtime.py +720 -127
  61. ksadk/common/kop_client.py +232 -0
  62. ksadk/configs/__init__.py +1 -3
  63. ksadk/configs/env_registry.py +244 -45
  64. ksadk/configs/env_registry_pcm.py +181 -0
  65. ksadk/configs/env_var_spec.py +18 -0
  66. ksadk/configs/global_config.py +6 -1
  67. ksadk/configs/settings.py +5 -108
  68. ksadk/context_engine/__init__.py +98 -0
  69. ksadk/context_engine/assembler.py +176 -0
  70. ksadk/context_engine/baseline.py +419 -0
  71. ksadk/context_engine/cache_observability.py +228 -0
  72. ksadk/context_engine/capabilities.py +443 -0
  73. ksadk/context_engine/contributors.py +344 -0
  74. ksadk/context_engine/hosted_pipeline.py +417 -0
  75. ksadk/context_engine/models.py +117 -0
  76. ksadk/context_engine/planner.py +698 -0
  77. ksadk/context_engine/policies.py +319 -0
  78. ksadk/context_engine/projection.py +31 -0
  79. ksadk/context_engine/shadow_plan.py +266 -0
  80. ksadk/context_engine/tokenizer.py +164 -0
  81. ksadk/conversations/__init__.py +85 -94
  82. ksadk/conversations/context.py +126 -6
  83. ksadk/conversations/message_projection.py +292 -28
  84. ksadk/conversations/model_context.py +49 -3
  85. ksadk/conversations/runtime_compaction.py +207 -5
  86. ksadk/conversations/runtime_input.py +193 -3
  87. ksadk/conversations/runtime_invocation.py +81 -3
  88. ksadk/conversations/runtime_observability.py +157 -4
  89. ksadk/conversations/runtime_payloads.py +42 -0
  90. ksadk/conversations/runtime_preparation.py +280 -2
  91. ksadk/conversations/runtime_resume.py +71 -32
  92. ksadk/conversations/runtime_stream_events.py +94 -9
  93. ksadk/conversations/runtime_streaming.py +175 -22
  94. ksadk/conversations/semantic_summary.py +311 -1
  95. ksadk/conversations/session_lock.py +51 -0
  96. ksadk/deployment/env_forward.py +72 -0
  97. ksadk/deployment/managed_runtime.py +38 -0
  98. ksadk/deployment/manager.py +24 -8
  99. ksadk/deployment/providers/serverless.py +228 -92
  100. ksadk/evaluation/__init__.py +116 -0
  101. ksadk/evaluation/a2a_adapter.py +452 -0
  102. ksadk/evaluation/adapters.py +67 -0
  103. ksadk/evaluation/agent_eval_client.py +395 -0
  104. ksadk/evaluation/cloud_binding.py +80 -0
  105. ksadk/evaluation/cloud_converter.py +194 -0
  106. ksadk/evaluation/cloud_service.py +199 -0
  107. ksadk/evaluation/contracts.py +478 -0
  108. ksadk/evaluation/evalset.py +351 -0
  109. ksadk/evaluation/evaluators.py +627 -0
  110. ksadk/evaluation/evidence.py +258 -0
  111. ksadk/evaluation/executor.py +143 -0
  112. ksadk/evaluation/local_adapter.py +550 -0
  113. ksadk/evaluation/service_env.py +27 -0
  114. ksadk/evaluation/storage.py +68 -0
  115. ksadk/evaluation/studio_build_adapter.py +253 -0
  116. ksadk/evaluation/target.py +82 -0
  117. ksadk/events/__init__.py +13 -13
  118. ksadk/events/_v1_compat/__init__.py +1 -0
  119. ksadk/events/_v1_compat/models.py +299 -0
  120. ksadk/events/_v1_compat/parser.py +247 -0
  121. ksadk/events/_v1_compat/projection.py +753 -0
  122. ksadk/events/adapters/__init__.py +34 -0
  123. ksadk/events/adapters/_a2a_snapshot.py +622 -0
  124. ksadk/events/adapters/_a2a_support.py +275 -0
  125. ksadk/events/adapters/_codex_interactions.py +375 -0
  126. ksadk/events/adapters/_codex_items.py +801 -0
  127. ksadk/events/adapters/_codex_validators.py +311 -0
  128. ksadk/events/adapters/_langgraph_support.py +785 -0
  129. ksadk/events/adapters/a2a.py +841 -0
  130. ksadk/events/adapters/adk.py +503 -0
  131. ksadk/events/adapters/codex.py +717 -0
  132. ksadk/events/adapters/langgraph.py +745 -0
  133. ksadk/events/canonical.py +440 -0
  134. ksadk/events/canonical_replay.py +497 -0
  135. ksadk/events/canonical_store.py +468 -0
  136. ksadk/events/cold_recovery.py +257 -0
  137. ksadk/events/content.py +89 -0
  138. ksadk/events/identity.py +86 -0
  139. ksadk/events/pipeline.py +455 -0
  140. ksadk/events/projections.py +66 -0
  141. ksadk/events/reducer.py +661 -0
  142. ksadk/events/replay.py +3 -36
  143. ksadk/events/runtime_event.py +20 -256
  144. ksadk/events/session_event.py +324 -0
  145. ksadk/events/store.py +3 -316
  146. ksadk/events/v1_compat.py +41 -0
  147. ksadk/harness/__init__.py +3 -2
  148. ksadk/harness/app.py +114 -123
  149. ksadk/harness/config.py +1 -1
  150. ksadk/harness/runtime.py +467 -0
  151. ksadk/hermes_terminal.py +5 -0
  152. ksadk/identity/resolver.py +4 -3
  153. ksadk/interaction/__init__.py +28 -0
  154. ksadk/interaction/contracts.py +136 -0
  155. ksadk/interaction/ledger.py +189 -0
  156. ksadk/interaction/provider.py +122 -0
  157. ksadk/interaction/providers/__init__.py +52 -0
  158. ksadk/interaction/providers/adk.py +40 -0
  159. ksadk/interaction/providers/codex.py +94 -0
  160. ksadk/interaction/providers/langgraph.py +58 -0
  161. ksadk/kernel/__init__.py +117 -0
  162. ksadk/kernel/authorization.py +258 -0
  163. ksadk/kernel/bootstrap.py +1074 -0
  164. ksadk/kernel/contract_fingerprints.py +61 -0
  165. ksadk/kernel/contracts.py +362 -0
  166. ksadk/kernel/control.py +219 -0
  167. ksadk/kernel/errors.py +79 -0
  168. ksadk/kernel/ingress.py +1102 -0
  169. ksadk/kernel/mapping.py +111 -0
  170. ksadk/kernel/memory_store.py +1133 -0
  171. ksadk/kernel/postgres_store.py +1756 -0
  172. ksadk/kernel/recovery.py +452 -0
  173. ksadk/kernel/runtime_identity.py +103 -0
  174. ksadk/kernel/sql/001_agent_kernel.sql +120 -0
  175. ksadk/kernel/sqlite_store.py +1410 -0
  176. ksadk/kernel/state.py +145 -0
  177. ksadk/kernel/store.py +232 -0
  178. ksadk/kernel/worker.py +953 -0
  179. ksadk/knowledge_base/client.py +22 -4
  180. ksadk/knowledge_base/service.py +32 -5
  181. ksadk/managed_a2a_card.py +160 -0
  182. ksadk/managed_runtime.py +16 -9
  183. ksadk/memory/__init__.py +61 -0
  184. ksadk/memory/adk/backends/base_ltm_backend.py +96 -1
  185. ksadk/memory/adk/backends/http_ltm_backend.py +14 -5
  186. ksadk/memory/adk/backends/inmemory_ltm_backend.py +136 -3
  187. ksadk/memory/adk/backends/sdk_ltm_backend.py +394 -5
  188. ksadk/memory/adk/backends/sqlite_ltm_backend.py +95 -0
  189. ksadk/memory/adk/long_term_memory.py +3 -2
  190. ksadk/memory/coordinator.py +461 -0
  191. ksadk/memory/events.py +202 -0
  192. ksadk/memory/extraction.py +228 -0
  193. ksadk/memory/ltm_backend_factory.py +16 -3
  194. ksadk/memory/models.py +316 -0
  195. ksadk/memory/policy.py +215 -0
  196. ksadk/memory/provider.py +47 -0
  197. ksadk/memory/provider_adapter.py +159 -0
  198. ksadk/memory/provider_resolver.py +71 -0
  199. ksadk/memory/providers/__init__.py +5 -0
  200. ksadk/memory/providers/local_sqlite.py +490 -0
  201. ksadk/memory/resolved_policy.py +145 -0
  202. ksadk/memory/service.py +198 -8
  203. ksadk/memory/tool.py +4 -0
  204. ksadk/model_proxy/bootstrap.py +16 -9
  205. ksadk/model_proxy/config.py +22 -1
  206. ksadk/model_proxy/detect.py +202 -9
  207. ksadk/model_proxy/namespace.py +67 -15
  208. ksadk/model_proxy/server.py +168 -6
  209. ksadk/model_proxy/transform.py +105 -3
  210. ksadk/observability/__init__.py +25 -0
  211. ksadk/observability/session_log.py +420 -0
  212. ksadk/observability/trajectory.py +105 -0
  213. ksadk/prompts/__init__.py +71 -0
  214. ksadk/prompts/compiler.py +215 -0
  215. ksadk/prompts/models.py +82 -0
  216. ksadk/prompts/projection.py +89 -0
  217. ksadk/prompts/resolved.py +137 -0
  218. ksadk/prompts/sources.py +233 -0
  219. ksadk/runners/_langgraph_runner_streams.py +822 -0
  220. ksadk/runners/adk_runner.py +14 -31
  221. ksadk/runners/base_runner.py +44 -3
  222. ksadk/runners/factory.py +3 -3
  223. ksadk/runners/langgraph_runner.py +252 -433
  224. ksadk/runners/utils/__init__.py +0 -10
  225. ksadk/runners/utils/loader.py +16 -0
  226. ksadk/runtime/__init__.py +67 -41
  227. ksadk/runtime/_runner_adapter/__init__.py +1 -0
  228. ksadk/runtime/_runner_adapter/stream_mapping.py +788 -0
  229. ksadk/runtime/adapter.py +199 -16
  230. ksadk/runtime/conversation_execution.py +687 -0
  231. ksadk/runtime/executor.py +392 -0
  232. ksadk/runtime/factory.py +342 -0
  233. ksadk/runtime/framework_adapters.py +6 -12
  234. ksadk/runtime/hosted_finalizer.py +268 -0
  235. ksadk/runtime/launch.py +61 -0
  236. ksadk/runtime/preprocessing.py +49 -14
  237. ksadk/runtime/runner_adapter.py +330 -391
  238. ksadk/runtime/runner_loading.py +36 -0
  239. ksadk/runtime/usage.py +33 -0
  240. ksadk/sandbox/backends/e2b.py +36 -3
  241. ksadk/server/__init__.py +39 -4
  242. ksadk/server/app.py +18 -492
  243. ksadk/server/composition.py +36 -2
  244. ksadk/server/factory.py +210 -149
  245. ksadk/server/routes/common.py +15 -46
  246. ksadk/server/routes/control.py +13 -9
  247. ksadk/server/routes/kernel_ingress.py +258 -0
  248. ksadk/server/routes/misc.py +10 -33
  249. ksadk/server/routes/models.py +14 -6
  250. ksadk/server/routes/openai_compat.py +129 -27
  251. ksadk/server/routes/projection.py +84 -16
  252. ksadk/server/routes/run.py +347 -460
  253. ksadk/server/routes/sessions.py +37 -40
  254. ksadk/server/routes/streaming.py +52 -7
  255. ksadk/server/routes/workspace.py +54 -21
  256. ksadk/server/static/assets/{ArtifactsPanel-Cjnqt-8W.js → ArtifactsPanel-DSU1sWD_.js} +1 -1
  257. ksadk/server/static/assets/{CodeBlock-BqyqZfuB.js → CodeBlock-DOH6MVcn.js} +4 -4
  258. ksadk/server/static/assets/{MathMessageMarkdown-BpH7jiFM.js → MathMessageMarkdown-BL8my1R2.js} +4 -4
  259. ksadk/server/static/assets/MathMessageMarkdown-BorAY7qD.css +1 -0
  260. ksadk/server/static/assets/MermaidBlock-Dz4IP-Tx.js +312 -0
  261. ksadk/server/static/assets/NativeTerminalPanel-DUrK0JpZ.js +1 -0
  262. ksadk/server/static/assets/abnfDiagram-VCTEODGH-g20pFzNV.js +1 -0
  263. ksadk/server/static/assets/arc-C4FzinUA.js +1 -0
  264. ksadk/server/static/assets/architecture-7GRP2DOG-DjKratdf.js +1 -0
  265. ksadk/server/static/assets/architectureDiagram-5GKGNRK7-DpIqL5h4.js +36 -0
  266. ksadk/server/static/assets/blockDiagram-NRAW4CY4-BjkljTNz.js +129 -0
  267. ksadk/server/static/assets/c4Diagram-UCG6FXSJ-Dk_ieq2X.js +38 -0
  268. ksadk/server/static/assets/channel-4cQHKtx1.js +1 -0
  269. ksadk/server/static/assets/chunk-2Q5K7J3B-DmgWkESh.js +1 -0
  270. ksadk/server/static/assets/{chunk-FMBD7UC4-BGhvzDYt.js → chunk-5VM5RSS4-BVHAchFb.js} +1 -1
  271. ksadk/server/static/assets/{chunk-ND2GUHAM-D98vPd-E.js → chunk-F27PBJKO-C-ipQzuS.js} +1 -1
  272. ksadk/server/static/assets/chunk-G27WJ6UU-C2EyJbIK.js +231 -0
  273. ksadk/server/static/assets/{chunk-4BX2VUAB-CsWIQRhc.js → chunk-JWPE2WC7-vYvVJb_M.js} +1 -1
  274. ksadk/server/static/assets/chunk-LCL6LL3I-BcI6ysPT.js +206 -0
  275. ksadk/server/static/assets/chunk-POPQ4Y6H-C030x_Z1.js +1 -0
  276. ksadk/server/static/assets/chunk-RHFEMEQ7-yyGpsz4n.js +168 -0
  277. ksadk/server/static/assets/chunk-SVP7TREG-BLTlmMU7.js +88 -0
  278. ksadk/server/static/assets/chunk-XXDRQBXY-C_32ArgP.js +1 -0
  279. ksadk/server/static/assets/classDiagram-DTDB5LWJ-DLFd9Xi0.js +1 -0
  280. ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-DLFd9Xi0.js +1 -0
  281. ksadk/server/static/assets/{cose-bilkent-S5V4N54A-CX3yotHq.js → cose-bilkent-JH36ORCC-9YQYwdl0.js} +1 -1
  282. ksadk/server/static/assets/cynefin-OW5HDTMX-DjEM48Qp.js +1 -0
  283. ksadk/server/static/assets/cynefinDiagram-5FMLGOSQ-ErLF5N13.js +62 -0
  284. ksadk/server/static/assets/cytoscape.esm-CyCl8rPi.js +321 -0
  285. ksadk/server/static/assets/dagre-3AP2YEHR-DFiQF-6f.js +4 -0
  286. ksadk/server/static/assets/dagre-BuhGnRI1.js +1 -0
  287. ksadk/server/static/assets/diagram-S7CK7UJ4-Bt1v8-GC.js +30 -0
  288. ksadk/server/static/assets/diagram-UQ7AKVKN-DSCxJdBK.js +41 -0
  289. ksadk/server/static/assets/diagram-VSXAHHWV-DHfYwp_9.js +3 -0
  290. ksadk/server/static/assets/diagram-VX7I27RA-DdzD-4Le.js +24 -0
  291. ksadk/server/static/assets/diagram-Z3DM3KII-Du-nAJ9F.js +24 -0
  292. ksadk/server/static/assets/{dist-D82mtD2s.js → dist-8hqcx0sU.js} +1 -1
  293. ksadk/server/static/assets/dist-B7seoj3d.js +1 -0
  294. ksadk/server/static/assets/{dist-DHn6nBzr.js → dist-BE7bu-DL.js} +1 -1
  295. ksadk/server/static/assets/{dist-eoW6tZXA.js → dist-BVDffZ0U.js} +1 -1
  296. ksadk/server/static/assets/{dist-C2xjE7cO.js → dist-BX8z72IC.js} +1 -1
  297. ksadk/server/static/assets/{dist-CgF3fj15.js → dist-Bf2M8m3N.js} +1 -1
  298. ksadk/server/static/assets/{dist-Bs-jQcRX.js → dist-BjU6y-A2.js} +1 -1
  299. ksadk/server/static/assets/{dist-Djgl2wVZ.js → dist-BytlxIDT.js} +1 -1
  300. ksadk/server/static/assets/{dist-CVQRZjPu.js → dist-CSh_DE14.js} +1 -1
  301. ksadk/server/static/assets/{dist-6CVKuTAe.js → dist-CXYYBw3_.js} +1 -1
  302. ksadk/server/static/assets/{dist-DlzCRy0S.js → dist-C_wsv-Qd.js} +1 -1
  303. ksadk/server/static/assets/{dist-DF06ORQo.js → dist-ClxGviKw.js} +1 -1
  304. ksadk/server/static/assets/{dist-Cf23NY4L.js → dist-CttYUqzS.js} +1 -1
  305. ksadk/server/static/assets/{dist-ivOya1LD.js → dist-DNp6rLRA.js} +1 -1
  306. ksadk/server/static/assets/{dist-BibcG1V9.js → dist-DUX-id_F.js} +1 -1
  307. ksadk/server/static/assets/{dist-BUHRxvDG.js → dist-DmldrHd_.js} +1 -1
  308. ksadk/server/static/assets/{dist-BZ9n9AjO.js → dist-DnfXs8Vn.js} +2 -2
  309. ksadk/server/static/assets/{dist-DqNnNFrw.js → dist-Dq9gLD7L.js} +1 -1
  310. ksadk/server/static/assets/{dist-CGcp2ex8.js → dist-W-TIVntx.js} +1 -1
  311. ksadk/server/static/assets/{dist-Cwt7koer.js → dist-dDdADKFA.js} +1 -1
  312. ksadk/server/static/assets/{dist-C7-3jInC.js → dist-dydu68Fl.js} +1 -1
  313. ksadk/server/static/assets/{dist-C99THtIA.js → dist-ngtN0DJB.js} +1 -1
  314. ksadk/server/static/assets/ebnfDiagram-PWID7BFC-Da9C9NO1.js +1 -0
  315. ksadk/server/static/assets/erDiagram-SSCWMZ5O-EAGqqR79.js +99 -0
  316. ksadk/server/static/assets/eventmodeling-NTZA5JFV-CMgMaJrC.js +1 -0
  317. ksadk/server/static/assets/flowDiagram-A5DVABFB-BBKL0x3P.js +1 -0
  318. ksadk/server/static/assets/ganttDiagram-EL5Y4UJY-DqJsKb59.js +292 -0
  319. ksadk/server/static/assets/gitGraph-4MIJSDKK-Dw63mrhw.js +1 -0
  320. ksadk/server/static/assets/gitGraphDiagram-WWUBYQGX-B6g4dtDi.js +106 -0
  321. ksadk/server/static/assets/index-8ipRcQ-M.js +240 -0
  322. ksadk/server/static/assets/index-ByFCqjlh.css +2 -0
  323. ksadk/server/static/assets/info-A6RAGUB7-B6iAblII.js +1 -0
  324. ksadk/server/static/assets/infoDiagram-RXCK75RN-Djm_nbd5.js +2 -0
  325. ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-DLOHGvWw.js → ishikawaDiagram-5VMMS53U-DWya9SG2.js} +6 -6
  326. ksadk/server/static/assets/{journeyDiagram-JHISSGLW-BIzKeejT.js → journeyDiagram-EYS64GPL-DutxhSNI.js} +6 -6
  327. ksadk/server/static/assets/{kanban-definition-UN3LZRKU-BWHoFSw2.js → kanban-definition-3QL26DDD-DJdOF8Fm.js} +10 -10
  328. ksadk/server/static/assets/katex-vFWytM5c.js +257 -0
  329. ksadk/server/static/assets/{linear-CvMBw9cA.js → linear-BHjG8b-J.js} +1 -1
  330. ksadk/server/static/assets/mermaid-parser.core-KGSy4jWT.js +166 -0
  331. ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-CSAQxEAe.js → mindmap-definition-FBJOCRG2-9-HC88D4.js} +29 -29
  332. ksadk/server/static/assets/packet-AYTQ26CC-BOYAipuq.js +1 -0
  333. ksadk/server/static/assets/pegDiagram-XKGWAZYB-DrEZd4fD.js +1 -0
  334. ksadk/server/static/assets/pie-WAS4IAKB-Ctq0Kbku.js +1 -0
  335. ksadk/server/static/assets/pieDiagram-E7YTZNPT-DpjqOFFp.js +39 -0
  336. ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-CxIR4JXI.js → quadrantDiagram-AXDQQJYC-BiNhgOXX.js} +6 -6
  337. ksadk/server/static/assets/radar-RG4KPBEZ-DN0-ul_0.js +1 -0
  338. ksadk/server/static/assets/railroad-74A4TZTK-C6cC_to0.js +1 -0
  339. ksadk/server/static/assets/railroad-abnf-HS5TGJTU-BimjIuRc.js +1 -0
  340. ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-CvY65ABq.js +1 -0
  341. ksadk/server/static/assets/railroad-peg-WCYAUIDC-D72Iq_Tv.js +1 -0
  342. ksadk/server/static/assets/railroadDiagram-O6MQD6OU-_Y3Ojg6G.js +1 -0
  343. ksadk/server/static/assets/requirementDiagram-EFPCY7ZU-DeCFdg9K.js +84 -0
  344. ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-DpGa6EAV.js → sankeyDiagram-P5KCCOFB-CPH75rhw.js} +7 -7
  345. ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-NTKNGGe5.js → sequenceDiagram-WJ2MYXX4-OEpQQdr4.js} +10 -10
  346. ksadk/server/static/assets/sizeCapture-X5ZJPWSS-heUErzhI.js +1 -0
  347. ksadk/server/static/assets/stateDiagram-HBIQ2CUA-DvA3jSMB.js +1 -0
  348. ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BgQY03nz.js +1 -0
  349. ksadk/server/static/assets/swimlanes-XN3QIQJK-CR-dfN_t.js +1 -0
  350. ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-DLVw3q5Q.js +8 -0
  351. ksadk/server/static/assets/{timeline-definition-PNZ67QCA-jqoYs3jQ.js → timeline-definition-24CTP7MA-DAVJf1TX.js} +7 -7
  352. ksadk/server/static/assets/treeView-Q6P3EWNA-BMpO0wsB.js +1 -0
  353. ksadk/server/static/assets/treemap-WGGIJYW6-Et0aqzU3.js +1 -0
  354. ksadk/server/static/assets/vennDiagram-4TSXK5OY-DwM8eDkh.js +34 -0
  355. ksadk/server/static/assets/wardley-WFR3VGLG-CbY0mhpl.js +1 -0
  356. ksadk/server/static/assets/wardleyDiagram-VM6X3IG4-DZL-Zz2t.js +78 -0
  357. ksadk/server/static/assets/xychartDiagram-S5SC5T6Z-Dy6Yh_hu.js +7 -0
  358. ksadk/server/static/index.html +2 -2
  359. ksadk/sessions/__init__.py +26 -9
  360. ksadk/sessions/_local_service_sync.py +688 -0
  361. ksadk/sessions/_local_tables.py +21 -0
  362. ksadk/sessions/_postgres_schema.py +305 -0
  363. ksadk/sessions/_postgres_tables.py +15 -0
  364. ksadk/sessions/base.py +80 -1
  365. ksadk/sessions/in_memory.py +53 -2
  366. ksadk/sessions/local_service.py +63 -604
  367. ksadk/sessions/postgres_service.py +58 -148
  368. ksadk/sessions/resilient.py +35 -1
  369. ksadk/skills/runtime/backends/e2b.py +1 -1
  370. ksadk/skills/service_client.py +13 -3
  371. ksadk/studio/__init__.py +5 -0
  372. ksadk/studio/agent_avatar_assets.py +120 -0
  373. ksadk/studio/agent_lifecycle.py +51 -0
  374. ksadk/studio/api.py +1598 -0
  375. ksadk/studio/api_catalog_routes.py +324 -0
  376. ksadk/studio/api_contracts.py +286 -0
  377. ksadk/studio/api_helpers.py +132 -0
  378. ksadk/studio/api_memory_routes.py +82 -0
  379. ksadk/studio/authoring.py +749 -0
  380. ksadk/studio/authoring_coordinator.py +1017 -0
  381. ksadk/studio/builder.py +397 -0
  382. ksadk/studio/capabilities.py +368 -0
  383. ksadk/studio/cloud.py +2099 -0
  384. ksadk/studio/codex_agent_service.py +694 -0
  385. ksadk/studio/codex_authoring.py +469 -0
  386. ksadk/studio/codex_builder.py +437 -0
  387. ksadk/studio/codex_manifest.py +307 -0
  388. ksadk/studio/codex_run.py +395 -0
  389. ksadk/studio/compiler.py +190 -0
  390. ksadk/studio/contracts.py +755 -0
  391. ksadk/studio/errors.py +47 -0
  392. ksadk/studio/event_store.py +214 -0
  393. ksadk/studio/framework_run.py +342 -0
  394. ksadk/studio/hosted_kernel.py +317 -0
  395. ksadk/studio/identifiers.py +33 -0
  396. ksadk/studio/manifest_resolver.py +131 -0
  397. ksadk/studio/mcp_runtime.py +229 -0
  398. ksadk/studio/model_client.py +629 -0
  399. ksadk/studio/model_profile_service.py +214 -0
  400. ksadk/studio/operations.py +275 -0
  401. ksadk/studio/otel_trace.py +1060 -0
  402. ksadk/studio/pagination.py +182 -0
  403. ksadk/studio/pcm_memory.py +85 -0
  404. ksadk/studio/python_tool_inspection.py +183 -0
  405. ksadk/studio/repository.py +327 -0
  406. ksadk/studio/resource_catalog.py +1260 -0
  407. ksadk/studio/run_service.py +1133 -0
  408. ksadk/studio/runtime_catalog.py +74 -0
  409. ksadk/studio/runtime_source.py +365 -0
  410. ksadk/studio/service.py +2249 -0
  411. ksadk/studio/shared_web.py +1004 -0
  412. ksadk/studio/skill_discovery.py +628 -0
  413. ksadk/studio/static/assets/PrismRenderer-IYN-ffww.js +3 -0
  414. ksadk/studio/static/assets/index-BLpcdrgW.css +1 -0
  415. ksadk/studio/static/assets/index-CYekR2Xv.js +260 -0
  416. ksadk/studio/static/assets/react-vendor-BNmTiJ-I.js +9 -0
  417. ksadk/studio/static/assets/rolldown-runtime-hePW80VL.js +1 -0
  418. ksadk/studio/static/index.html +15 -0
  419. ksadk/studio/templates.py +722 -0
  420. ksadk/studio/validator.py +265 -0
  421. ksadk/studio/workspace.py +135 -0
  422. ksadk/tools/gateway.py +9 -0
  423. ksadk/toolsets/__init__.py +27 -1
  424. ksadk/toolsets/a2a.py +426 -0
  425. ksadk/tracing/__init__.py +19 -21
  426. ksadk/tracing/setup.py +317 -403
  427. ksadk/tracing/span_utils.py +0 -44
  428. ksadk/version.py +1 -1
  429. ksadk_runtime_common/memory_backend/registry.py +56 -56
  430. ksadk_runtime_common/schemas/runtime_event_v1.json +37 -0
  431. ksadk_runtime_common/schemas/runtime_event_v2.json +271 -0
  432. ksadk_runtime_common/workspace_files/router.py +8 -2
  433. agentengine_sdk_python-0.8.0.dist-info/RECORD +0 -578
  434. ksadk/a2a/card_builder.py +0 -64
  435. ksadk/a2a/client.py +0 -309
  436. ksadk/events/parser.py +0 -191
  437. ksadk/harness/runner.py +0 -211
  438. ksadk/runners/codex_runner.py +0 -132
  439. ksadk/runners/utils/langfuse.py +0 -230
  440. ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +0 -1
  441. ksadk/server/static/assets/MermaidBlock-DWuxETaJ.js +0 -303
  442. ksadk/server/static/assets/NativeTerminalPanel-MDTopWMO.js +0 -1
  443. ksadk/server/static/assets/arc-CFKwSk_B.js +0 -1
  444. ksadk/server/static/assets/architecture-7EHR7CIX-CynqOWjZ.js +0 -1
  445. ksadk/server/static/assets/architectureDiagram-3BPJPVTR-Dej8jX8c.js +0 -36
  446. ksadk/server/static/assets/blockDiagram-GPEHLZMM-1F64jqvy.js +0 -132
  447. ksadk/server/static/assets/c4Diagram-AAUBKEIU-9q7AUAKb.js +0 -10
  448. ksadk/server/static/assets/channel-iUGv2N24.js +0 -1
  449. ksadk/server/static/assets/chunk-2J33WTMH-6qgwJm7Q.js +0 -1
  450. ksadk/server/static/assets/chunk-55IACEB6-BK8paA20.js +0 -1
  451. ksadk/server/static/assets/chunk-727SXJPM-B-JEhy7h.js +0 -206
  452. ksadk/server/static/assets/chunk-AQP2D5EJ-DYBvMHAa.js +0 -231
  453. ksadk/server/static/assets/chunk-QZHKN3VN-YQIEXeF6.js +0 -1
  454. ksadk/server/static/assets/classDiagram-4FO5ZUOK-CDAUuoXK.js +0 -1
  455. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-CDAUuoXK.js +0 -1
  456. ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +0 -321
  457. ksadk/server/static/assets/dagre-BM42HDAG-C7TguAWo.js +0 -4
  458. ksadk/server/static/assets/dagre-Bx709z4p.js +0 -1
  459. ksadk/server/static/assets/diagram-2AECGRRQ-Dd17_kws.js +0 -43
  460. ksadk/server/static/assets/diagram-5GNKFQAL-DVIClIil.js +0 -10
  461. ksadk/server/static/assets/diagram-KO2AKTUF-Df_3gYQa.js +0 -3
  462. ksadk/server/static/assets/diagram-LMA3HP47-BhLk6ZtA.js +0 -24
  463. ksadk/server/static/assets/diagram-OG6HWLK6-D4GNGvuu.js +0 -24
  464. ksadk/server/static/assets/dist-DyvYLMW9.js +0 -1
  465. ksadk/server/static/assets/erDiagram-TEJ5UH35-CnfRcY2M.js +0 -85
  466. ksadk/server/static/assets/eventmodeling-FCH6USID-ChYO-ijM.js +0 -1
  467. ksadk/server/static/assets/flowDiagram-I6XJVG4X-DGaJjGNJ.js +0 -162
  468. ksadk/server/static/assets/ganttDiagram-6RSMTGT7-DpCoB2g9.js +0 -292
  469. ksadk/server/static/assets/gitGraph-WXDBUCRP-BN_0Fk-j.js +0 -1
  470. ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-9IKVFBVT.js +0 -106
  471. ksadk/server/static/assets/graphlib-B8gBHxth.js +0 -1
  472. ksadk/server/static/assets/index-QSwCHuB8.js +0 -177
  473. ksadk/server/static/assets/index-jOPmTOZH.css +0 -2
  474. ksadk/server/static/assets/info-J43DQDTF-Cp6o3vId.js +0 -1
  475. ksadk/server/static/assets/infoDiagram-5YYISTIA-CYIIsb8y.js +0 -2
  476. ksadk/server/static/assets/katex-DATssjp1.js +0 -265
  477. ksadk/server/static/assets/mermaid-parser.core-mGAcEwcq.js +0 -161
  478. ksadk/server/static/assets/packet-YPE3B663-wQzct3cT.js +0 -1
  479. ksadk/server/static/assets/pie-LRSECV5Y-BgTQiv-9.js +0 -1
  480. ksadk/server/static/assets/pieDiagram-4H26LBE5-By-ra07M.js +0 -30
  481. ksadk/server/static/assets/radar-GUYGQ44K-BIq5fXsn.js +0 -1
  482. ksadk/server/static/assets/requirementDiagram-4Y6WPE33-DpW90iu3.js +0 -84
  483. ksadk/server/static/assets/stateDiagram-AJRCARHV-DGpd9yFG.js +0 -1
  484. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-B3o579IQ.js +0 -1
  485. ksadk/server/static/assets/treeView-BLDUP644-Dqi-E0EM.js +0 -1
  486. ksadk/server/static/assets/treemap-LRROVOQU-ClQP9CJ9.js +0 -1
  487. ksadk/server/static/assets/vennDiagram-CIIHVFJN-l3Z7-wbP.js +0 -34
  488. ksadk/server/static/assets/wardley-L42UT6IY-8XLvnNIs.js +0 -1
  489. ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-BR54GPmr.js +0 -78
  490. ksadk/server/static/assets/xychartDiagram-2RQKCTM6-SkiH8GsK.js +0 -7
  491. ksadk/tracing/exporters/langfuse_exporter.py +0 -460
  492. {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/entry_points.txt +0 -0
  493. {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/licenses/LICENSE +0 -0
  494. {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1004 @@
1
+ """Project AgentKit Studio runs onto Responses and compatibility API contracts."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import json
7
+ from collections.abc import AsyncIterator
8
+ from datetime import datetime, timezone
9
+ from typing import Any
10
+ from uuid import uuid4
11
+
12
+ from ksadk.studio.contracts import BuildStatus, OperationStatus, RunRecord, RunStatus
13
+ from ksadk.studio.errors import StudioError, not_found
14
+ from ksadk.studio.service import StudioService
15
+ from ksadk.tools.gateway import tool_approval_capability
16
+
17
+ _TERMINAL_OPERATIONS = {
18
+ OperationStatus.SUCCEEDED,
19
+ OperationStatus.FAILED,
20
+ OperationStatus.CANCELLED,
21
+ OperationStatus.INTERRUPTED,
22
+ }
23
+
24
+
25
+ class StudioSharedWebBridge:
26
+ """Project Studio repositories onto Responses and legacy action surfaces."""
27
+
28
+ def __init__(self, studio: StudioService) -> None:
29
+ self.studio = studio
30
+ self._operations_by_invocation: dict[str, str] = {}
31
+ self._response_runs: dict[str, str] = {}
32
+ self._run_ids_by_invocation: dict[str, str] = {}
33
+
34
+ def resolve_agent_id(self, requested: str | None = None) -> str:
35
+ if requested:
36
+ self.studio.agent_detail(requested)
37
+ return requested
38
+ agents = self.studio.list_agents(limit=1)
39
+ if not agents:
40
+ raise not_found("agent", "")
41
+ return agents[0].metadata.id
42
+
43
+ def bootstrap(self, agent_id: str) -> dict[str, Any]:
44
+ draft = self._draft(agent_id)
45
+ model = self._model_descriptor(agent_id)
46
+ return {
47
+ "Agent": {
48
+ "AgentId": agent_id,
49
+ "Name": draft.metadata.name,
50
+ "Framework": self.studio.agent_runtime_type(agent_id),
51
+ },
52
+ "AccessMode": "Owner",
53
+ "ApiFormats": ["responses"],
54
+ "Model": model,
55
+ "Capabilities": {
56
+ "HostedChat": {
57
+ "Enabled": True,
58
+ "ApiFormats": ["responses"],
59
+ "PreferredTransport": "responses",
60
+ "Transports": [
61
+ {
62
+ "Protocol": "responses",
63
+ "Runtime": "agentkit-studio",
64
+ "Endpoint": "/agentengine/api/v1/RunAgent",
65
+ "Version": "v1",
66
+ "Capabilities": {
67
+ "A2UI": True,
68
+ "Interrupt": True,
69
+ "Cancel": True,
70
+ },
71
+ }
72
+ ],
73
+ },
74
+ "RunLifecycle": {
75
+ "Enabled": True,
76
+ "Resume": True,
77
+ "Abort": True,
78
+ "Checkpoints": False,
79
+ "CheckpointResume": False,
80
+ "CheckpointResumePreview": False,
81
+ },
82
+ "ApprovalPolicy": tool_approval_capability(),
83
+ "WorkspaceFiles": {"Enabled": False},
84
+ "NativeDashboard": {"Enabled": False},
85
+ "NativeTerminal": {"Enabled": False},
86
+ "Thinking": False,
87
+ },
88
+ }
89
+
90
+ def list_models(self, agent_id: str) -> dict[str, Any]:
91
+ models = self._model_descriptors(agent_id)
92
+ model = self._model_descriptor(agent_id, models)
93
+ return {
94
+ "Models": models,
95
+ "Current": model["id"],
96
+ "Source": "agentkit-studio",
97
+ }
98
+
99
+ def select_model(self, agent_id: str, requested: str | None = None) -> str:
100
+ """Resolve and authorize the actual model used by the next turn."""
101
+
102
+ return self._select_model(agent_id, str(requested or ""))
103
+
104
+ def list_sessions(
105
+ self,
106
+ agent_id: str,
107
+ *,
108
+ page: int = 1,
109
+ page_size: int = 30,
110
+ ) -> dict[str, Any]:
111
+ sessions = self._sessions(agent_id)
112
+ safe_page = max(1, page)
113
+ safe_size = min(100, max(1, page_size))
114
+ start = (safe_page - 1) * safe_size
115
+ return {
116
+ "Sessions": sessions[start : start + safe_size],
117
+ "Total": len(sessions),
118
+ "Page": safe_page,
119
+ "PageSize": safe_size,
120
+ }
121
+
122
+ def create_session(self, agent_id: str) -> dict[str, Any]:
123
+ self._draft(agent_id)
124
+ session_id = f"ses_{uuid4().hex}"
125
+ now = datetime.now(timezone.utc).isoformat()
126
+ return {
127
+ "Session": {
128
+ "SessionId": session_id,
129
+ "AgentId": agent_id,
130
+ "UserId": "local-user",
131
+ "Title": "新会话",
132
+ "CreatedAt": now,
133
+ "UpdatedAt": now,
134
+ }
135
+ }
136
+
137
+ def get_session(self, session_id: str) -> dict[str, Any]:
138
+ runs = self.studio.event_store.list_runs(session_id=session_id)
139
+ if not runs:
140
+ raise not_found("session", session_id)
141
+ return {"Session": self._session_record(runs)}
142
+
143
+ def delete_session(self, session_id: str) -> dict[str, Any]:
144
+ self.studio.event_store.delete_session(session_id)
145
+ return {}
146
+
147
+ async def list_messages(
148
+ self,
149
+ session_id: str,
150
+ *,
151
+ after_seq_id: int | None = None,
152
+ before_seq_id: int | None = None,
153
+ limit: int = 50,
154
+ ) -> dict[str, Any]:
155
+ runs = self.studio.event_store.list_runs(session_id=session_id)
156
+ messages: list[dict[str, Any]] = []
157
+ sequence = 0
158
+ for run in runs:
159
+ sequence += 1
160
+ messages.append(
161
+ {
162
+ "MessageId": f"{run.id}:user",
163
+ "Role": "user",
164
+ "Content": {"text": run.input},
165
+ "Timestamp": self._timestamp(run.started_at),
166
+ "SeqId": sequence,
167
+ "InvocationId": run.id,
168
+ }
169
+ )
170
+ sequence += 1
171
+ messages.append(
172
+ {
173
+ "MessageId": f"{run.id}:assistant",
174
+ "Role": "assistant",
175
+ "Content": {"text": self._run_output(run)},
176
+ "Timestamp": self._timestamp(run.completed_at or run.started_at),
177
+ "SeqId": sequence,
178
+ "InvocationId": run.id,
179
+ "Activities": await self._run_activities(run),
180
+ }
181
+ )
182
+
183
+ latest_seq_id = sequence
184
+ if after_seq_id is not None:
185
+ messages = [item for item in messages if item["SeqId"] > after_seq_id]
186
+ if before_seq_id is not None:
187
+ messages = [item for item in messages if item["SeqId"] < before_seq_id]
188
+ safe_limit = min(200, max(1, limit))
189
+ has_more = len(messages) > safe_limit
190
+ selected = messages[-safe_limit:]
191
+ return {
192
+ "Messages": selected,
193
+ "LatestSeqId": latest_seq_id,
194
+ "HasMore": has_more,
195
+ "NextCursor": selected[0]["SeqId"] if has_more and selected else None,
196
+ }
197
+
198
+ async def list_session_events(self, session_id: str) -> dict[str, Any]:
199
+ runs = self.studio.event_store.list_runs(session_id=session_id)
200
+ events: list[dict[str, Any]] = []
201
+ sequence = 0
202
+ for run in runs:
203
+ for event in await self.studio.run_service.events(run.id):
204
+ sequence += 1
205
+ events.append(
206
+ {
207
+ "SeqId": sequence,
208
+ "EventType": event.type,
209
+ "InvocationId": run.id,
210
+ "Content": event.data,
211
+ "Timestamp": self._timestamp(event.created_at),
212
+ }
213
+ )
214
+ return {
215
+ "Events": events,
216
+ "Total": len(events),
217
+ "Offset": 0,
218
+ "Limit": len(events),
219
+ }
220
+
221
+ def cancel_run(self, invocation_id: str) -> dict[str, Any]:
222
+ operation_id = self._operations_by_invocation.get(invocation_id)
223
+ if operation_id:
224
+ self.studio.operations.cancel(operation_id)
225
+ return {"InvocationId": invocation_id, "Cancelled": bool(operation_id)}
226
+
227
+ async def pause_run(self, invocation_id: str) -> dict[str, Any]:
228
+ run_id = self._run_ids_by_invocation.get(invocation_id)
229
+ if not run_id:
230
+ raise StudioError("RUN_NOT_READY", "运行尚未创建,请稍后重试", status_code=409)
231
+ return await self.studio.run_service.pause_run(run_id)
232
+
233
+ async def resume_run(self, invocation_id: str) -> dict[str, Any]:
234
+ run_id = self._run_ids_by_invocation.get(invocation_id)
235
+ if not run_id:
236
+ raise StudioError("RUN_NOT_FOUND", "未找到可继续的运行", status_code=404)
237
+ return await self.studio.run_service.resume_run(run_id)
238
+
239
+ def response_session_id(self, response_id: str) -> str:
240
+ run_id = self._response_runs.get(response_id, response_id)
241
+ return self.studio.event_store.get(run_id).session_id
242
+
243
+ async def stream_run(self, payload: dict[str, Any]) -> AsyncIterator[str]:
244
+ agent_id = self.resolve_agent_id(str(payload.get("AgentId") or "") or None)
245
+ session_id = str(payload.get("SessionId") or f"ses_{uuid4().hex}")
246
+ invocation_id = str(payload.get("InvocationId") or f"resp_{uuid4().hex}")
247
+ prompt = self._input_text(payload)
248
+ runtime_input = self._runtime_input(payload)
249
+ model = self._select_model(agent_id, str(payload.get("Model") or ""))
250
+ approval_mode = str(payload.get("ApprovalMode") or "")
251
+ collaboration_mode = str(payload.get("CollaborationMode") or "")
252
+ goal_objective = str(payload.get("GoalObjective") or "")
253
+ reasoning_effort = str(payload.get("ReasoningEffort") or "")
254
+ execution = asyncio.create_task(
255
+ self._execute_run(
256
+ agent_id=agent_id,
257
+ session_id=session_id,
258
+ invocation_id=invocation_id,
259
+ prompt=prompt,
260
+ runtime_input=runtime_input,
261
+ model=model,
262
+ approval_mode=approval_mode,
263
+ collaboration_mode=collaboration_mode,
264
+ goal_objective=goal_objective,
265
+ reasoning_effort=reasoning_effort,
266
+ )
267
+ )
268
+
269
+ def release_operation(task: asyncio.Task[RunRecord]) -> None:
270
+ self._operations_by_invocation.pop(invocation_id, None)
271
+ if not task.cancelled():
272
+ task.exception()
273
+
274
+ execution.add_done_callback(release_operation)
275
+
276
+ yield self._sse(
277
+ "response.created",
278
+ {
279
+ "type": "response.created",
280
+ "response": self._response_shell(invocation_id, model=model),
281
+ },
282
+ )
283
+ try:
284
+ yield self._sse(
285
+ "response.in_progress",
286
+ {
287
+ "type": "response.in_progress",
288
+ "response": self._response_shell(invocation_id, model=model),
289
+ },
290
+ )
291
+ seen_events: set[tuple[str, int]] = set()
292
+ emitted_text = ""
293
+ idle_polls = 0
294
+ while not execution.done():
295
+ projected = await self._project_response_events(
296
+ session_id=session_id,
297
+ agent_id=agent_id,
298
+ invocation_id=invocation_id,
299
+ seen=seen_events,
300
+ )
301
+ if projected:
302
+ idle_polls = 0
303
+ for event_name, event_payload in projected:
304
+ if event_name == "response.output_text.delta":
305
+ emitted_text += str(event_payload.get("delta") or "")
306
+ yield self._sse(event_name, event_payload)
307
+ else:
308
+ idle_polls += 1
309
+ if idle_polls >= 20:
310
+ idle_polls = 0
311
+ yield ": keep-alive\n\n"
312
+ await asyncio.sleep(0.05)
313
+ run = await execution
314
+ for event_name, event_payload in await self._project_response_events(
315
+ session_id=session_id,
316
+ agent_id=agent_id,
317
+ invocation_id=invocation_id,
318
+ seen=seen_events,
319
+ ):
320
+ if event_name == "response.output_text.delta":
321
+ emitted_text += str(event_payload.get("delta") or "")
322
+ yield self._sse(event_name, event_payload)
323
+ remaining = (
324
+ run.output[len(emitted_text) :] if run.output.startswith(emitted_text) else ""
325
+ )
326
+ if remaining:
327
+ yield self._response_delta_sse(
328
+ "response.output_text.delta",
329
+ invocation_id=invocation_id,
330
+ delta=remaining,
331
+ )
332
+ yield self._sse(
333
+ "response.completed",
334
+ {
335
+ "type": "response.completed",
336
+ "response": self._response_payload(
337
+ run,
338
+ model=model,
339
+ response_id=invocation_id,
340
+ ),
341
+ },
342
+ )
343
+ except StudioError as exc:
344
+ yield self._failed_sse(invocation_id, exc.message)
345
+ except Exception:
346
+ yield self._failed_sse(invocation_id, "本地 Agent 运行失败")
347
+ finally:
348
+ if execution.done():
349
+ self._operations_by_invocation.pop(invocation_id, None)
350
+
351
+ async def invoke_response(self, payload: dict[str, Any]) -> dict[str, Any]:
352
+ """Run once and return an OpenAI Responses-compatible JSON object."""
353
+
354
+ agent_id = self.resolve_agent_id(str(payload.get("AgentId") or "") or None)
355
+ session_id = str(payload.get("SessionId") or f"ses_{uuid4().hex}")
356
+ invocation_id = str(payload.get("InvocationId") or f"resp_{uuid4().hex}")
357
+ model = self._select_model(agent_id, str(payload.get("Model") or ""))
358
+ approval_mode = str(payload.get("ApprovalMode") or "")
359
+ collaboration_mode = str(payload.get("CollaborationMode") or "")
360
+ goal_objective = str(payload.get("GoalObjective") or "")
361
+ reasoning_effort = str(payload.get("ReasoningEffort") or "")
362
+ try:
363
+ run = await self._execute_run(
364
+ agent_id=agent_id,
365
+ session_id=session_id,
366
+ invocation_id=invocation_id,
367
+ prompt=self._input_text(payload),
368
+ runtime_input=self._runtime_input(payload),
369
+ model=model,
370
+ approval_mode=approval_mode,
371
+ collaboration_mode=collaboration_mode,
372
+ goal_objective=goal_objective,
373
+ reasoning_effort=reasoning_effort,
374
+ )
375
+ return self._response_payload(
376
+ run,
377
+ model=model,
378
+ response_id=invocation_id,
379
+ )
380
+ finally:
381
+ self._operations_by_invocation.pop(invocation_id, None)
382
+
383
+ async def _execute_run(
384
+ self,
385
+ *,
386
+ agent_id: str,
387
+ session_id: str,
388
+ invocation_id: str,
389
+ prompt: str,
390
+ runtime_input: Any,
391
+ model: str,
392
+ approval_mode: str = "",
393
+ collaboration_mode: str = "",
394
+ goal_objective: str = "",
395
+ reasoning_effort: str = "",
396
+ ) -> RunRecord:
397
+ build = await self._ensure_build(agent_id)
398
+
399
+ def observe(event: Any) -> None:
400
+ if event.type == "run.created":
401
+ run_id = str(event.data.get("runId") or "")
402
+ if run_id:
403
+ self._run_ids_by_invocation[invocation_id] = run_id
404
+
405
+ operation = self.studio.submit_studio_run(
406
+ build.id,
407
+ prompt,
408
+ session_id=session_id,
409
+ model=model,
410
+ approval_mode=approval_mode or None,
411
+ collaboration_mode=collaboration_mode or None,
412
+ goal_objective=goal_objective or None,
413
+ reasoning_effort=reasoning_effort or None,
414
+ runtime_input=runtime_input or None,
415
+ idempotency_key=f"responses:{invocation_id}",
416
+ on_event=observe,
417
+ )
418
+ self._operations_by_invocation[invocation_id] = operation.id
419
+ while True:
420
+ current = self.studio.operations.get(operation.id)
421
+ if current.status in _TERMINAL_OPERATIONS:
422
+ break
423
+ await asyncio.sleep(0.05)
424
+ if current.status != OperationStatus.SUCCEEDED:
425
+ message = str((current.error or {}).get("message") or "Agent 运行失败")
426
+ raise StudioError("RUN_FAILED", message, status_code=500)
427
+ run = self.studio.event_store.get(current.resource_id)
428
+ if run.status != RunStatus.COMPLETED:
429
+ raise StudioError("RUN_FAILED", self._run_output(run), status_code=500)
430
+ self._response_runs[invocation_id] = run.id
431
+ return run
432
+
433
+ @staticmethod
434
+ def _response_shell(response_id: str, *, model: str) -> dict[str, Any]:
435
+ return {
436
+ "id": response_id,
437
+ "object": "response",
438
+ "status": "in_progress",
439
+ "model": model,
440
+ "output": [],
441
+ }
442
+
443
+ async def _project_response_events(
444
+ self,
445
+ *,
446
+ session_id: str,
447
+ agent_id: str,
448
+ invocation_id: str,
449
+ seen: set[tuple[str, int]],
450
+ ) -> list[tuple[str, dict[str, Any]]]:
451
+ projected: list[tuple[str, dict[str, Any]]] = []
452
+ current_run_id = self._run_ids_by_invocation.get(invocation_id)
453
+ if not current_run_id:
454
+ return projected
455
+ for run in self.studio.event_store.list_runs(session_id=session_id):
456
+ if run.agent_id != agent_id or run.id != current_run_id:
457
+ continue
458
+ events = await self.studio.run_service.events(run.id)
459
+ starts = {
460
+ str(event.data.get("callId") or event.data.get("call_id") or ""): event.data
461
+ for event in events
462
+ if event.type in {"command.started", "tool.started", "tool.requested"}
463
+ }
464
+ for event in events:
465
+ key = (run.id, event.id)
466
+ if key in seen:
467
+ continue
468
+ seen.add(key)
469
+ if event.type in {"message.delta", "thinking.delta"}:
470
+ delta = str(event.data.get("text") or event.data.get("delta") or "")
471
+ if not delta:
472
+ continue
473
+ event_name = (
474
+ "response.output_text.delta"
475
+ if event.type == "message.delta"
476
+ else "response.reasoning_summary_text.delta"
477
+ )
478
+ projected.append(
479
+ (
480
+ event_name,
481
+ self._response_delta_payload(
482
+ event_name,
483
+ invocation_id=invocation_id,
484
+ delta=delta,
485
+ ),
486
+ )
487
+ )
488
+ continue
489
+ if event.type.startswith("a2ui."):
490
+ projected.append(
491
+ (
492
+ event.type,
493
+ {
494
+ "type": event.type,
495
+ "runId": run.id,
496
+ **event.data,
497
+ },
498
+ )
499
+ )
500
+ continue
501
+ if event.type == "run.paused":
502
+ projected.append(
503
+ (
504
+ "response.paused",
505
+ {
506
+ "type": "response.paused",
507
+ "response_id": invocation_id,
508
+ "runId": run.id,
509
+ },
510
+ )
511
+ )
512
+ continue
513
+ if event.type == "run.resumed":
514
+ projected.append(
515
+ (
516
+ "response.resumed",
517
+ {
518
+ "type": "response.resumed",
519
+ "response_id": invocation_id,
520
+ "runId": run.id,
521
+ },
522
+ )
523
+ )
524
+ continue
525
+ item_event = self._response_item_event(event.type, event.data, starts)
526
+ if item_event is not None:
527
+ projected.append(item_event)
528
+ return projected
529
+
530
+ @staticmethod
531
+ def _response_item_event(
532
+ event_type: str,
533
+ data: dict[str, Any],
534
+ starts: dict[str, dict[str, Any]],
535
+ ) -> tuple[str, dict[str, Any]] | None:
536
+ call_id = str(data.get("callId") or data.get("call_id") or "")
537
+ started = starts.get(call_id, {})
538
+ done = event_type in {
539
+ "command.completed",
540
+ "tool.completed",
541
+ "command.failed",
542
+ "tool.failed",
543
+ }
544
+ if event_type.startswith("command."):
545
+ command = str(data.get("command") or started.get("command") or "执行命令")
546
+ item = {
547
+ "id": call_id or f"shell_{uuid4().hex}",
548
+ "call_id": call_id,
549
+ "type": "shell_call",
550
+ "status": (
551
+ "failed"
552
+ if event_type.endswith("failed") or data.get("exitCode") not in {None, 0}
553
+ else "completed" if done else "in_progress"
554
+ ),
555
+ "action": {
556
+ "commands": [command],
557
+ "cwd": data.get("cwd") or started.get("cwd") or "",
558
+ },
559
+ "exit_code": data.get("exitCode"),
560
+ "output": data.get("output") or "",
561
+ }
562
+ elif event_type.startswith("tool."):
563
+ name = str(data.get("tool") or data.get("name") or started.get("tool") or "调用工具")
564
+ args = data.get("args", started.get("args"))
565
+ item = {
566
+ "id": call_id or f"tool_{uuid4().hex}",
567
+ "call_id": call_id,
568
+ "type": "function_call",
569
+ "name": name,
570
+ "arguments": json.dumps(args, ensure_ascii=False) if args is not None else "",
571
+ "status": (
572
+ "failed"
573
+ if event_type.endswith("failed")
574
+ else "completed" if done else "in_progress"
575
+ ),
576
+ "output": data.get("output") or data.get("result") or "",
577
+ }
578
+ elif event_type == "approval.requested":
579
+ item = {
580
+ "id": call_id or f"approval_{uuid4().hex}",
581
+ "call_id": call_id,
582
+ "type": "approval_request",
583
+ "status": "in_progress",
584
+ "action": data,
585
+ }
586
+ else:
587
+ return None
588
+ response_event = "response.output_item.done" if done else "response.output_item.added"
589
+ return response_event, {"type": response_event, "item": item}
590
+
591
+ @classmethod
592
+ def _response_delta_sse(
593
+ cls,
594
+ event_name: str,
595
+ *,
596
+ invocation_id: str,
597
+ delta: str,
598
+ ) -> str:
599
+ payload = cls._response_delta_payload(
600
+ event_name,
601
+ invocation_id=invocation_id,
602
+ delta=delta,
603
+ )
604
+ return cls._sse(event_name, payload)
605
+
606
+ @staticmethod
607
+ def _response_delta_payload(
608
+ event_name: str,
609
+ *,
610
+ invocation_id: str,
611
+ delta: str,
612
+ ) -> dict[str, Any]:
613
+ return {
614
+ "type": event_name,
615
+ "item_id": f"msg_{invocation_id}",
616
+ "output_index": 0,
617
+ "content_index": 0,
618
+ "delta": delta,
619
+ }
620
+
621
+ @staticmethod
622
+ def _response_payload(
623
+ run: RunRecord,
624
+ *,
625
+ model: str,
626
+ response_id: str | None = None,
627
+ ) -> dict[str, Any]:
628
+ return {
629
+ "id": response_id or run.id,
630
+ "object": "response",
631
+ "status": "completed",
632
+ "model": run.model or model,
633
+ "output": [
634
+ {
635
+ "id": f"msg_{response_id or run.id}",
636
+ "type": "message",
637
+ "status": "completed",
638
+ "role": "assistant",
639
+ "content": [
640
+ {
641
+ "type": "output_text",
642
+ "text": run.output,
643
+ "annotations": [],
644
+ }
645
+ ],
646
+ }
647
+ ],
648
+ "usage": {
649
+ "input_tokens": run.usage.input_tokens,
650
+ "output_tokens": run.usage.output_tokens,
651
+ "total_tokens": run.usage.total_tokens,
652
+ },
653
+ "metadata": {
654
+ "session_id": run.session_id,
655
+ "trace_id": run.trace_id,
656
+ "agent_id": run.agent_id,
657
+ "runtime_run_id": run.id,
658
+ },
659
+ }
660
+
661
+ async def _ensure_build(self, agent_id: str):
662
+ if self.studio.is_codex_agent(agent_id):
663
+ self.studio.codex_agent_detail(agent_id)
664
+ builds = [
665
+ record
666
+ for record in self.studio.codex_builds.list()
667
+ if record.agent_name == agent_id and self.studio.codex_builder.is_current(record)
668
+ ]
669
+ if builds:
670
+ return builds[0]
671
+ return await asyncio.to_thread(self.studio.codex_builder.build, agent_id)
672
+ for record in self.studio.builds.list_for_agent(agent_id):
673
+ if record.status == BuildStatus.SUCCEEDED:
674
+ return record
675
+ draft = self.studio.drafts.get(agent_id)
676
+ if draft.spec.model is None and not draft.spec.bindings.model_profile_id:
677
+ raise StudioError(
678
+ "AGENT_MODEL_REQUIRED",
679
+ "当前 Agent 未绑定 Model Profile,请先在 Agent 配置中选择模型;"
680
+ "API Key 只提供访问凭证,不会自动绑定模型。",
681
+ status_code=422,
682
+ field="spec.bindings.modelProfileId",
683
+ )
684
+ return await asyncio.to_thread(self.studio.builder.build, draft)
685
+
686
+ def _sessions(self, agent_id: str) -> list[dict[str, Any]]:
687
+ grouped: dict[str, list[RunRecord]] = {}
688
+ for run in self.studio.event_store.list_runs():
689
+ if run.agent_id != agent_id:
690
+ continue
691
+ grouped.setdefault(run.session_id, []).append(run)
692
+ records = [self._session_record(runs) for runs in grouped.values()]
693
+ records.sort(key=lambda item: item["UpdatedAt"], reverse=True)
694
+ return records
695
+
696
+ def _session_record(self, runs: list[RunRecord]) -> dict[str, Any]:
697
+ ordered = sorted(
698
+ runs,
699
+ key=lambda run: (
700
+ run.started_at or run.completed_at or datetime.min.replace(tzinfo=timezone.utc)
701
+ ),
702
+ )
703
+ first = ordered[0]
704
+ latest = ordered[-1]
705
+ usage = {
706
+ "input_tokens": sum(run.usage.input_tokens for run in ordered),
707
+ "output_tokens": sum(run.usage.output_tokens for run in ordered),
708
+ "total_tokens": sum(run.usage.total_tokens for run in ordered),
709
+ "turns": len(ordered),
710
+ "last_response_id": latest.id,
711
+ }
712
+ return {
713
+ "SessionId": first.session_id,
714
+ "AgentId": first.agent_id,
715
+ "UserId": "local-user",
716
+ "Title": self._short_title(first.input),
717
+ "FirstPrompt": first.input,
718
+ "LastPrompt": latest.input,
719
+ "CreatedAt": self._timestamp(first.started_at),
720
+ "UpdatedAt": self._timestamp(latest.completed_at or latest.started_at),
721
+ "ActiveRunStatus": self._active_status(latest.status),
722
+ "ActiveInvocationId": latest.id if latest.status == RunStatus.RUNNING else "",
723
+ "TokenUsage": usage,
724
+ }
725
+
726
+ def _model_descriptor(
727
+ self, agent_id: str, models: list[dict[str, Any]] | None = None
728
+ ) -> dict[str, Any]:
729
+ if models is None:
730
+ models = self._model_descriptors(agent_id)
731
+ if self.studio.is_codex_agent(agent_id):
732
+ default_model = self.studio.codex_manifests.load(agent_id).manifest.model
733
+ return next(
734
+ (item for item in models if item["id"] == default_model),
735
+ models[0],
736
+ )
737
+ return models[0]
738
+
739
+ def _model_descriptors(self, agent_id: str) -> list[dict[str, Any]]:
740
+ draft = self._draft(agent_id)
741
+ if self.studio.is_codex_agent(agent_id):
742
+ manifest = self.studio.codex_manifests.load(agent_id).manifest
743
+ # catalog.list 每次调用都要全量扫描(内建工具/持久化目录/Skill),
744
+ # 按 allowed_models 逐个扫会成倍放大,这里一次取出后复用。
745
+ catalog_models = self.studio.catalog.list(kind="model", limit=500)
746
+ codex_descriptors = [
747
+ self._model_descriptor_for_name(draft, model, catalog_models)
748
+ for model in manifest.allowed_models
749
+ ]
750
+ return codex_descriptors
751
+
752
+ binding_ids = list(draft.spec.bindings.model_profile_ids)
753
+ if not binding_ids and draft.spec.bindings.model_profile_id:
754
+ binding_ids = [draft.spec.bindings.model_profile_id]
755
+ descriptors: list[dict[str, Any]] = []
756
+ for binding_id in binding_ids:
757
+ spec = self.studio.catalog.resolve_model(
758
+ draft.spec.bindings.model_copy(
759
+ update={"model_profile_id": binding_id, "model_profile_ids": []}
760
+ )
761
+ )
762
+ if spec is not None:
763
+ descriptors.append(
764
+ self._model_descriptor_from_spec(
765
+ draft,
766
+ spec,
767
+ display_name=self.studio.catalog.get(binding_id).display_name,
768
+ )
769
+ )
770
+ if descriptors:
771
+ default_id = draft.spec.bindings.model_profile_id
772
+ if default_id and default_id in binding_ids:
773
+ default_index = binding_ids.index(default_id)
774
+ descriptors.insert(0, descriptors.pop(default_index))
775
+ return descriptors
776
+ if draft.spec.model is not None:
777
+ return [self._model_descriptor_from_spec(draft, draft.spec.model)]
778
+ return [self._unconfigured_model_descriptor(draft)]
779
+
780
+ def _model_descriptor_for_name(
781
+ self, draft, model_name: str, catalog_models: list | None = None
782
+ ) -> dict[str, Any]:
783
+ if catalog_models is None:
784
+ catalog_models = self.studio.catalog.list(kind="model", limit=500)
785
+ for descriptor in catalog_models:
786
+ try:
787
+ from ksadk.studio.contracts import ModelSpec
788
+
789
+ spec = ModelSpec.model_validate(descriptor.contract)
790
+ except ValueError:
791
+ continue
792
+ if spec.model == model_name:
793
+ return self._model_descriptor_from_spec(
794
+ draft,
795
+ spec,
796
+ display_name=descriptor.display_name,
797
+ )
798
+ return {
799
+ "id": model_name,
800
+ "display_name": model_name,
801
+ "source": "agentkit-studio",
802
+ "context_window_tokens": draft.spec.context.max_input_tokens,
803
+ "max_output_tokens": 2048,
804
+ "capabilities": {
805
+ "function_calling": True,
806
+ "structured_output": True,
807
+ },
808
+ }
809
+
810
+ @staticmethod
811
+ def _model_descriptor_from_spec(
812
+ draft,
813
+ spec,
814
+ *,
815
+ display_name: str | None = None,
816
+ ) -> dict[str, Any]:
817
+ metadata: dict[str, Any] = {}
818
+ model_id = spec.model
819
+ metadata = dict(spec.metadata or {})
820
+ resolved_display_name = display_name or spec.model
821
+ max_output_tokens = spec.parameters.max_tokens or 4096
822
+ return {
823
+ **metadata,
824
+ "id": model_id,
825
+ "display_name": resolved_display_name,
826
+ "source": "agentkit-studio",
827
+ "context_window_tokens": metadata.get("context_window_tokens")
828
+ or draft.spec.context.max_input_tokens,
829
+ "max_output_tokens": max_output_tokens,
830
+ "capabilities": {
831
+ **dict(metadata.get("capabilities") or {}),
832
+ "function_calling": True,
833
+ "structured_output": True,
834
+ },
835
+ }
836
+
837
+ @staticmethod
838
+ def _unconfigured_model_descriptor(draft) -> dict[str, Any]:
839
+ model_id = str(
840
+ draft.metadata.labels.get("agentkit.ksyun.com/model") or "unconfigured-model"
841
+ )
842
+ return {
843
+ "id": model_id,
844
+ "display_name": model_id if model_id != "unconfigured-model" else "未配置模型",
845
+ "source": "agentkit-studio",
846
+ "context_window_tokens": draft.spec.context.max_input_tokens,
847
+ "max_output_tokens": 2048,
848
+ "capabilities": {
849
+ "function_calling": True,
850
+ "structured_output": True,
851
+ },
852
+ }
853
+
854
+ def _select_model(self, agent_id: str, requested: str) -> str:
855
+ models = self._model_descriptors(agent_id)
856
+ allowed = [str(item["id"]) for item in models]
857
+ default = str(self._model_descriptor(agent_id, models)["id"])
858
+ selected = requested.strip() or default
859
+ if selected not in allowed:
860
+ raise StudioError(
861
+ "MODEL_NOT_BOUND",
862
+ "请求模型未绑定到当前 Agent Build",
863
+ status_code=422,
864
+ details={"model": selected, "allowedModels": allowed},
865
+ )
866
+ return selected
867
+
868
+ def _draft(self, agent_id: str):
869
+ return self.studio.agent_detail(agent_id)["draft"]
870
+
871
+ async def _run_activities(self, run: RunRecord) -> list[dict[str, Any]]:
872
+ activities: list[dict[str, Any]] = []
873
+ for event in await self.studio.run_service.events(run.id):
874
+ operations = (
875
+ event.data.get("a2uiOperations")
876
+ or event.data.get("a2ui_operations")
877
+ or event.data.get("operations")
878
+ )
879
+ if not isinstance(operations, list):
880
+ continue
881
+ surface_id = str(
882
+ event.data.get("surfaceId") or event.data.get("surface_id") or f"{run.id}-surface"
883
+ )
884
+ activities.append(
885
+ {
886
+ "SeqId": event.id,
887
+ "Type": event.type,
888
+ "MessageId": f"{run.id}:assistant",
889
+ "SurfaceId": surface_id,
890
+ "Content": {"a2ui_operations": operations},
891
+ }
892
+ )
893
+ return activities
894
+
895
+ @staticmethod
896
+ def _input_text(payload: dict[str, Any]) -> str:
897
+ sources = payload.get("ResponsesInput") or payload.get("Messages") or []
898
+ if not isinstance(sources, list):
899
+ raise StudioError(
900
+ "RUN_INPUT_INVALID",
901
+ "会话输入格式无效",
902
+ status_code=422,
903
+ )
904
+ for message in reversed(sources):
905
+ if not isinstance(message, dict):
906
+ continue
907
+ content = message.get("content")
908
+ if isinstance(content, str) and content.strip():
909
+ return content.strip()
910
+ if not isinstance(content, list):
911
+ continue
912
+ texts = [
913
+ str(part.get("text") or "")
914
+ for part in content
915
+ if isinstance(part, dict) and str(part.get("type") or "") in {"input_text", "text"}
916
+ ]
917
+ value = "\n".join(text.strip() for text in texts if text.strip())
918
+ if value:
919
+ return value
920
+ raise StudioError(
921
+ "RUN_INPUT_REQUIRED",
922
+ "请输入消息后再发送",
923
+ status_code=422,
924
+ )
925
+
926
+ @staticmethod
927
+ def _runtime_input(payload: dict[str, Any]) -> list[dict[str, str]]:
928
+ """Project the latest Responses user message into native multimodal input."""
929
+
930
+ sources = payload.get("ResponsesInput") or payload.get("Messages") or []
931
+ if not isinstance(sources, list):
932
+ return []
933
+ for message in reversed(sources):
934
+ if not isinstance(message, dict) or str(message.get("role") or "user") != "user":
935
+ continue
936
+ content = message.get("content")
937
+ if isinstance(content, str) and content.strip():
938
+ return []
939
+ if not isinstance(content, list):
940
+ continue
941
+ items: list[dict[str, str]] = []
942
+ has_image = False
943
+ for part in content:
944
+ if not isinstance(part, dict):
945
+ continue
946
+ kind = str(part.get("type") or "")
947
+ if kind in {"input_text", "text"}:
948
+ text = str(part.get("text") or "")
949
+ if text:
950
+ items.append({"type": "text", "text": text})
951
+ elif kind in {"input_image", "image"}:
952
+ url = str(
953
+ part.get("image_url") or part.get("imageUrl") or part.get("url") or ""
954
+ )
955
+ if url:
956
+ items.append({"type": "image", "url": url})
957
+ has_image = True
958
+ if items and has_image:
959
+ return items
960
+ return []
961
+
962
+ @staticmethod
963
+ def _active_status(status: RunStatus) -> str:
964
+ return "running" if status == RunStatus.RUNNING else ""
965
+
966
+ @staticmethod
967
+ def _run_output(run: RunRecord) -> str:
968
+ if run.output:
969
+ return run.output
970
+ if run.error:
971
+ return str(run.error.get("message") or "Agent 运行失败")
972
+ return f"运行状态:{run.status.value}"
973
+
974
+ @staticmethod
975
+ def _short_title(value: str, limit: int = 36) -> str:
976
+ text = " ".join(value.split())
977
+ return text if len(text) <= limit else f"{text[:limit]}..."
978
+
979
+ @staticmethod
980
+ def _timestamp(value: datetime | None) -> str:
981
+ return (value or datetime.now(timezone.utc)).isoformat()
982
+
983
+ @staticmethod
984
+ def _sse(event: str, payload: dict[str, Any]) -> str:
985
+ data = json.dumps(payload, ensure_ascii=False, separators=(",", ":"))
986
+ return f"event: {event}\ndata: {data}\n\n"
987
+
988
+ @classmethod
989
+ def _failed_sse(cls, invocation_id: str, message: str) -> str:
990
+ return cls._sse(
991
+ "response.failed",
992
+ {
993
+ "type": "response.failed",
994
+ "response": {
995
+ "id": invocation_id,
996
+ "status": "failed",
997
+ "error": {"message": message},
998
+ },
999
+ "error": {"message": message},
1000
+ },
1001
+ )
1002
+
1003
+
1004
+ __all__ = ["StudioSharedWebBridge"]