agentengine-sdk-python 0.8.1__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 (411) hide show
  1. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/METADATA +22 -2
  2. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/RECORD +356 -222
  3. ksadk/a2a/_space_client_events.py +381 -0
  4. ksadk/a2a/event_adapter.py +156 -64
  5. ksadk/a2a/executor.py +179 -63
  6. ksadk/a2a/space_client.py +6 -333
  7. ksadk/a2ui/core.py +222 -53
  8. ksadk/agui/_agent_helpers.py +243 -0
  9. ksadk/agui/a2ui_projection.py +12 -5
  10. ksadk/agui/agent.py +443 -259
  11. ksadk/api/client.py +525 -27
  12. ksadk/builders/code_builder.py +284 -8
  13. ksadk/builders/container_builder.py +8 -1
  14. ksadk/builders/managed_runtime_builder.py +39 -35
  15. ksadk/cli/__init__.py +17 -0
  16. ksadk/cli/cmd_dashboard.py +15 -1
  17. ksadk/cli/cmd_deploy.py +10 -2
  18. ksadk/cli/cmd_eval.py +66 -9
  19. ksadk/cli/cmd_evalset.py +284 -0
  20. ksadk/cli/cmd_files.py +5 -1
  21. ksadk/cli/cmd_hermes.py +1 -126
  22. ksadk/cli/cmd_invoke.py +28 -38
  23. ksadk/cli/cmd_managed_runtime.py +95 -0
  24. ksadk/cli/cmd_observe.py +85 -0
  25. ksadk/cli/cmd_openclaw.py +1 -435
  26. ksadk/cli/cmd_replay.py +28 -4
  27. ksadk/cli/cmd_studio.py +99 -8
  28. ksadk/cli/cmd_web.py +10 -2
  29. ksadk/cli/hermes_env.py +151 -0
  30. ksadk/cli/invoke_payload.py +49 -0
  31. ksadk/cli/openclaw_env.py +458 -0
  32. ksadk/codex/client.py +180 -30
  33. ksadk/codex/runtime.py +428 -326
  34. ksadk/configs/env_registry.py +66 -12
  35. ksadk/configs/env_registry_pcm.py +181 -0
  36. ksadk/configs/env_var_spec.py +18 -0
  37. ksadk/configs/global_config.py +5 -0
  38. ksadk/context_engine/__init__.py +98 -0
  39. ksadk/context_engine/assembler.py +176 -0
  40. ksadk/context_engine/baseline.py +419 -0
  41. ksadk/context_engine/cache_observability.py +228 -0
  42. ksadk/context_engine/capabilities.py +443 -0
  43. ksadk/context_engine/contributors.py +344 -0
  44. ksadk/context_engine/hosted_pipeline.py +417 -0
  45. ksadk/context_engine/models.py +117 -0
  46. ksadk/context_engine/planner.py +698 -0
  47. ksadk/context_engine/policies.py +319 -0
  48. ksadk/context_engine/projection.py +31 -0
  49. ksadk/context_engine/shadow_plan.py +266 -0
  50. ksadk/context_engine/tokenizer.py +164 -0
  51. ksadk/conversations/context.py +126 -6
  52. ksadk/conversations/message_projection.py +292 -28
  53. ksadk/conversations/model_context.py +49 -3
  54. ksadk/conversations/runtime_compaction.py +207 -5
  55. ksadk/conversations/runtime_input.py +184 -2
  56. ksadk/conversations/runtime_invocation.py +81 -3
  57. ksadk/conversations/runtime_observability.py +141 -0
  58. ksadk/conversations/runtime_payloads.py +42 -0
  59. ksadk/conversations/runtime_preparation.py +272 -2
  60. ksadk/conversations/runtime_resume.py +20 -1
  61. ksadk/conversations/runtime_stream_events.py +94 -9
  62. ksadk/conversations/semantic_summary.py +311 -1
  63. ksadk/conversations/session_lock.py +51 -0
  64. ksadk/deployment/env_forward.py +72 -0
  65. ksadk/deployment/managed_runtime.py +5 -0
  66. ksadk/deployment/providers/serverless.py +143 -69
  67. ksadk/evaluation/__init__.py +32 -0
  68. ksadk/evaluation/a2a_adapter.py +9 -1
  69. ksadk/evaluation/adapters.py +18 -5
  70. ksadk/evaluation/agent_eval_client.py +395 -0
  71. ksadk/evaluation/cloud_binding.py +80 -0
  72. ksadk/evaluation/cloud_converter.py +194 -0
  73. ksadk/evaluation/cloud_service.py +199 -0
  74. ksadk/evaluation/contracts.py +73 -4
  75. ksadk/evaluation/evaluators.py +183 -17
  76. ksadk/evaluation/evidence.py +258 -0
  77. ksadk/evaluation/executor.py +31 -6
  78. ksadk/evaluation/local_adapter.py +550 -0
  79. ksadk/evaluation/service_env.py +27 -0
  80. ksadk/evaluation/studio_build_adapter.py +253 -0
  81. ksadk/evaluation/target.py +15 -6
  82. ksadk/events/__init__.py +13 -13
  83. ksadk/events/_v1_compat/__init__.py +1 -0
  84. ksadk/events/_v1_compat/models.py +299 -0
  85. ksadk/events/_v1_compat/parser.py +247 -0
  86. ksadk/events/_v1_compat/projection.py +753 -0
  87. ksadk/events/adapters/__init__.py +34 -0
  88. ksadk/events/adapters/_a2a_snapshot.py +622 -0
  89. ksadk/events/adapters/_a2a_support.py +275 -0
  90. ksadk/events/adapters/_codex_interactions.py +375 -0
  91. ksadk/events/adapters/_codex_items.py +801 -0
  92. ksadk/events/adapters/_codex_validators.py +311 -0
  93. ksadk/events/adapters/_langgraph_support.py +785 -0
  94. ksadk/events/adapters/a2a.py +841 -0
  95. ksadk/events/adapters/adk.py +503 -0
  96. ksadk/events/adapters/codex.py +717 -0
  97. ksadk/events/adapters/langgraph.py +745 -0
  98. ksadk/events/canonical.py +440 -0
  99. ksadk/events/canonical_replay.py +497 -0
  100. ksadk/events/canonical_store.py +468 -0
  101. ksadk/events/cold_recovery.py +257 -0
  102. ksadk/events/content.py +89 -0
  103. ksadk/events/identity.py +86 -0
  104. ksadk/events/pipeline.py +455 -0
  105. ksadk/events/projections.py +66 -0
  106. ksadk/events/reducer.py +661 -0
  107. ksadk/events/replay.py +3 -36
  108. ksadk/events/runtime_event.py +20 -265
  109. ksadk/events/session_event.py +324 -0
  110. ksadk/events/store.py +3 -316
  111. ksadk/events/v1_compat.py +41 -0
  112. ksadk/harness/runtime.py +160 -57
  113. ksadk/identity/resolver.py +4 -3
  114. ksadk/interaction/__init__.py +28 -0
  115. ksadk/interaction/contracts.py +136 -0
  116. ksadk/interaction/ledger.py +189 -0
  117. ksadk/interaction/provider.py +122 -0
  118. ksadk/interaction/providers/__init__.py +52 -0
  119. ksadk/interaction/providers/adk.py +40 -0
  120. ksadk/interaction/providers/codex.py +94 -0
  121. ksadk/interaction/providers/langgraph.py +58 -0
  122. ksadk/kernel/__init__.py +117 -0
  123. ksadk/kernel/authorization.py +258 -0
  124. ksadk/kernel/bootstrap.py +1074 -0
  125. ksadk/kernel/contract_fingerprints.py +61 -0
  126. ksadk/kernel/contracts.py +362 -0
  127. ksadk/kernel/control.py +219 -0
  128. ksadk/kernel/errors.py +79 -0
  129. ksadk/kernel/ingress.py +1102 -0
  130. ksadk/kernel/mapping.py +111 -0
  131. ksadk/kernel/memory_store.py +1133 -0
  132. ksadk/kernel/postgres_store.py +1756 -0
  133. ksadk/kernel/recovery.py +452 -0
  134. ksadk/kernel/runtime_identity.py +103 -0
  135. ksadk/kernel/sql/001_agent_kernel.sql +120 -0
  136. ksadk/kernel/sqlite_store.py +1410 -0
  137. ksadk/kernel/state.py +145 -0
  138. ksadk/kernel/store.py +232 -0
  139. ksadk/kernel/worker.py +953 -0
  140. ksadk/knowledge_base/client.py +7 -0
  141. ksadk/knowledge_base/service.py +32 -5
  142. ksadk/memory/__init__.py +61 -0
  143. ksadk/memory/adk/backends/base_ltm_backend.py +96 -1
  144. ksadk/memory/adk/backends/http_ltm_backend.py +14 -5
  145. ksadk/memory/adk/backends/inmemory_ltm_backend.py +136 -3
  146. ksadk/memory/adk/backends/sdk_ltm_backend.py +384 -2
  147. ksadk/memory/adk/backends/sqlite_ltm_backend.py +95 -0
  148. ksadk/memory/coordinator.py +461 -0
  149. ksadk/memory/events.py +202 -0
  150. ksadk/memory/extraction.py +228 -0
  151. ksadk/memory/ltm_backend_factory.py +16 -3
  152. ksadk/memory/models.py +316 -0
  153. ksadk/memory/policy.py +215 -0
  154. ksadk/memory/provider.py +47 -0
  155. ksadk/memory/provider_adapter.py +159 -0
  156. ksadk/memory/provider_resolver.py +71 -0
  157. ksadk/memory/providers/__init__.py +5 -0
  158. ksadk/memory/providers/local_sqlite.py +490 -0
  159. ksadk/memory/resolved_policy.py +145 -0
  160. ksadk/memory/service.py +188 -6
  161. ksadk/model_proxy/bootstrap.py +16 -9
  162. ksadk/model_proxy/detect.py +202 -9
  163. ksadk/model_proxy/namespace.py +39 -15
  164. ksadk/model_proxy/server.py +24 -5
  165. ksadk/model_proxy/transform.py +60 -2
  166. ksadk/observability/__init__.py +25 -0
  167. ksadk/observability/session_log.py +420 -0
  168. ksadk/observability/trajectory.py +105 -0
  169. ksadk/prompts/__init__.py +71 -0
  170. ksadk/prompts/compiler.py +215 -0
  171. ksadk/prompts/models.py +82 -0
  172. ksadk/prompts/projection.py +89 -0
  173. ksadk/prompts/resolved.py +137 -0
  174. ksadk/prompts/sources.py +233 -0
  175. ksadk/runners/_langgraph_runner_streams.py +822 -0
  176. ksadk/runners/adk_runner.py +5 -5
  177. ksadk/runners/base_runner.py +16 -0
  178. ksadk/runners/factory.py +1 -2
  179. ksadk/runners/langgraph_runner.py +198 -437
  180. ksadk/runtime/_runner_adapter/__init__.py +1 -0
  181. ksadk/runtime/_runner_adapter/stream_mapping.py +788 -0
  182. ksadk/runtime/adapter.py +118 -2
  183. ksadk/runtime/conversation_execution.py +316 -182
  184. ksadk/runtime/executor.py +144 -3
  185. ksadk/runtime/factory.py +193 -7
  186. ksadk/runtime/hosted_finalizer.py +268 -0
  187. ksadk/runtime/launch.py +14 -1
  188. ksadk/runtime/preprocessing.py +25 -3
  189. ksadk/runtime/runner_adapter.py +288 -508
  190. ksadk/runtime/usage.py +33 -0
  191. ksadk/sandbox/backends/e2b.py +35 -2
  192. ksadk/server/composition.py +8 -0
  193. ksadk/server/factory.py +69 -6
  194. ksadk/server/routes/kernel_ingress.py +258 -0
  195. ksadk/server/routes/openai_compat.py +88 -8
  196. ksadk/server/routes/projection.py +35 -20
  197. ksadk/server/routes/run.py +94 -0
  198. ksadk/server/routes/sessions.py +2 -0
  199. ksadk/server/routes/streaming.py +5 -2
  200. ksadk/server/static/assets/{ArtifactsPanel-CakCqxEN.js → ArtifactsPanel-DSU1sWD_.js} +1 -1
  201. ksadk/server/static/assets/{CodeBlock-CynLknyO.js → CodeBlock-DOH6MVcn.js} +4 -4
  202. ksadk/server/static/assets/{MathMessageMarkdown-3sQdOysS.js → MathMessageMarkdown-BL8my1R2.js} +4 -4
  203. ksadk/server/static/assets/MathMessageMarkdown-BorAY7qD.css +1 -0
  204. ksadk/server/static/assets/MermaidBlock-Dz4IP-Tx.js +312 -0
  205. ksadk/server/static/assets/NativeTerminalPanel-DUrK0JpZ.js +1 -0
  206. ksadk/server/static/assets/abnfDiagram-VCTEODGH-g20pFzNV.js +1 -0
  207. ksadk/server/static/assets/arc-C4FzinUA.js +1 -0
  208. ksadk/server/static/assets/architecture-7GRP2DOG-DjKratdf.js +1 -0
  209. ksadk/server/static/assets/architectureDiagram-5GKGNRK7-DpIqL5h4.js +36 -0
  210. ksadk/server/static/assets/blockDiagram-NRAW4CY4-BjkljTNz.js +129 -0
  211. ksadk/server/static/assets/c4Diagram-UCG6FXSJ-Dk_ieq2X.js +38 -0
  212. ksadk/server/static/assets/channel-4cQHKtx1.js +1 -0
  213. ksadk/server/static/assets/chunk-2Q5K7J3B-DmgWkESh.js +1 -0
  214. ksadk/server/static/assets/{chunk-FMBD7UC4-B_fH2YuH.js → chunk-5VM5RSS4-BVHAchFb.js} +1 -1
  215. ksadk/server/static/assets/{chunk-ND2GUHAM-D6rFLaVV.js → chunk-F27PBJKO-C-ipQzuS.js} +1 -1
  216. ksadk/server/static/assets/chunk-G27WJ6UU-C2EyJbIK.js +231 -0
  217. ksadk/server/static/assets/{chunk-4BX2VUAB-B9drTgOW.js → chunk-JWPE2WC7-vYvVJb_M.js} +1 -1
  218. ksadk/server/static/assets/chunk-LCL6LL3I-BcI6ysPT.js +206 -0
  219. ksadk/server/static/assets/chunk-POPQ4Y6H-C030x_Z1.js +1 -0
  220. ksadk/server/static/assets/chunk-RHFEMEQ7-yyGpsz4n.js +168 -0
  221. ksadk/server/static/assets/chunk-SVP7TREG-BLTlmMU7.js +88 -0
  222. ksadk/server/static/assets/chunk-XXDRQBXY-C_32ArgP.js +1 -0
  223. ksadk/server/static/assets/classDiagram-DTDB5LWJ-DLFd9Xi0.js +1 -0
  224. ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-DLFd9Xi0.js +1 -0
  225. ksadk/server/static/assets/{cose-bilkent-S5V4N54A-ChNs_Mdk.js → cose-bilkent-JH36ORCC-9YQYwdl0.js} +1 -1
  226. ksadk/server/static/assets/cynefin-OW5HDTMX-DjEM48Qp.js +1 -0
  227. ksadk/server/static/assets/cynefinDiagram-5FMLGOSQ-ErLF5N13.js +62 -0
  228. ksadk/server/static/assets/cytoscape.esm-CyCl8rPi.js +321 -0
  229. ksadk/server/static/assets/dagre-3AP2YEHR-DFiQF-6f.js +4 -0
  230. ksadk/server/static/assets/dagre-BuhGnRI1.js +1 -0
  231. ksadk/server/static/assets/diagram-S7CK7UJ4-Bt1v8-GC.js +30 -0
  232. ksadk/server/static/assets/diagram-UQ7AKVKN-DSCxJdBK.js +41 -0
  233. ksadk/server/static/assets/diagram-VSXAHHWV-DHfYwp_9.js +3 -0
  234. ksadk/server/static/assets/diagram-VX7I27RA-DdzD-4Le.js +24 -0
  235. ksadk/server/static/assets/diagram-Z3DM3KII-Du-nAJ9F.js +24 -0
  236. ksadk/server/static/assets/{dist-Dli7qW-B.js → dist-8hqcx0sU.js} +1 -1
  237. ksadk/server/static/assets/dist-B7seoj3d.js +1 -0
  238. ksadk/server/static/assets/{dist-C1sz5Eo3.js → dist-BE7bu-DL.js} +1 -1
  239. ksadk/server/static/assets/{dist-fhOuWds5.js → dist-BVDffZ0U.js} +1 -1
  240. ksadk/server/static/assets/{dist-BjOdlDjj.js → dist-BX8z72IC.js} +1 -1
  241. ksadk/server/static/assets/{dist-DsHPdk3W.js → dist-Bf2M8m3N.js} +1 -1
  242. ksadk/server/static/assets/{dist-o2OVmKvt.js → dist-BjU6y-A2.js} +1 -1
  243. ksadk/server/static/assets/{dist-DWKByxWV.js → dist-BytlxIDT.js} +1 -1
  244. ksadk/server/static/assets/{dist-DJN_6Y39.js → dist-CSh_DE14.js} +1 -1
  245. ksadk/server/static/assets/{dist-DQU9EWw-.js → dist-CXYYBw3_.js} +1 -1
  246. ksadk/server/static/assets/{dist-4N2NMVYg.js → dist-C_wsv-Qd.js} +1 -1
  247. ksadk/server/static/assets/{dist-gLnrGsoU.js → dist-ClxGviKw.js} +1 -1
  248. ksadk/server/static/assets/{dist-j6Dxmf_j.js → dist-CttYUqzS.js} +1 -1
  249. ksadk/server/static/assets/{dist-D6MpCZtz.js → dist-DNp6rLRA.js} +1 -1
  250. ksadk/server/static/assets/{dist-BB3-OwTQ.js → dist-DUX-id_F.js} +1 -1
  251. ksadk/server/static/assets/{dist-_pHPTyZ-.js → dist-DmldrHd_.js} +1 -1
  252. ksadk/server/static/assets/{dist-CWV4lDr1.js → dist-DnfXs8Vn.js} +2 -2
  253. ksadk/server/static/assets/{dist-B5oS1zcj.js → dist-Dq9gLD7L.js} +1 -1
  254. ksadk/server/static/assets/{dist-B-jNR6Vn.js → dist-W-TIVntx.js} +1 -1
  255. ksadk/server/static/assets/{dist-BIlKoW0k.js → dist-dDdADKFA.js} +1 -1
  256. ksadk/server/static/assets/{dist-X4TnqwOK.js → dist-dydu68Fl.js} +1 -1
  257. ksadk/server/static/assets/{dist-CmZHzlwZ.js → dist-ngtN0DJB.js} +1 -1
  258. ksadk/server/static/assets/ebnfDiagram-PWID7BFC-Da9C9NO1.js +1 -0
  259. ksadk/server/static/assets/erDiagram-SSCWMZ5O-EAGqqR79.js +99 -0
  260. ksadk/server/static/assets/eventmodeling-NTZA5JFV-CMgMaJrC.js +1 -0
  261. ksadk/server/static/assets/flowDiagram-A5DVABFB-BBKL0x3P.js +1 -0
  262. ksadk/server/static/assets/ganttDiagram-EL5Y4UJY-DqJsKb59.js +292 -0
  263. ksadk/server/static/assets/gitGraph-4MIJSDKK-Dw63mrhw.js +1 -0
  264. ksadk/server/static/assets/gitGraphDiagram-WWUBYQGX-B6g4dtDi.js +106 -0
  265. ksadk/server/static/assets/index-8ipRcQ-M.js +240 -0
  266. ksadk/server/static/assets/index-ByFCqjlh.css +2 -0
  267. ksadk/server/static/assets/info-A6RAGUB7-B6iAblII.js +1 -0
  268. ksadk/server/static/assets/infoDiagram-RXCK75RN-Djm_nbd5.js +2 -0
  269. ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-K0OOfVna.js → ishikawaDiagram-5VMMS53U-DWya9SG2.js} +6 -6
  270. ksadk/server/static/assets/{journeyDiagram-JHISSGLW-8LeOJaZp.js → journeyDiagram-EYS64GPL-DutxhSNI.js} +6 -6
  271. ksadk/server/static/assets/{kanban-definition-UN3LZRKU-C6uCgZrW.js → kanban-definition-3QL26DDD-DJdOF8Fm.js} +10 -10
  272. ksadk/server/static/assets/katex-vFWytM5c.js +257 -0
  273. ksadk/server/static/assets/{linear-hdBcYzM8.js → linear-BHjG8b-J.js} +1 -1
  274. ksadk/server/static/assets/mermaid-parser.core-KGSy4jWT.js +166 -0
  275. ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-Bjy_2v4_.js → mindmap-definition-FBJOCRG2-9-HC88D4.js} +29 -29
  276. ksadk/server/static/assets/packet-AYTQ26CC-BOYAipuq.js +1 -0
  277. ksadk/server/static/assets/pegDiagram-XKGWAZYB-DrEZd4fD.js +1 -0
  278. ksadk/server/static/assets/pie-WAS4IAKB-Ctq0Kbku.js +1 -0
  279. ksadk/server/static/assets/pieDiagram-E7YTZNPT-DpjqOFFp.js +39 -0
  280. ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-BmkRbwCg.js → quadrantDiagram-AXDQQJYC-BiNhgOXX.js} +6 -6
  281. ksadk/server/static/assets/radar-RG4KPBEZ-DN0-ul_0.js +1 -0
  282. ksadk/server/static/assets/railroad-74A4TZTK-C6cC_to0.js +1 -0
  283. ksadk/server/static/assets/railroad-abnf-HS5TGJTU-BimjIuRc.js +1 -0
  284. ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-CvY65ABq.js +1 -0
  285. ksadk/server/static/assets/railroad-peg-WCYAUIDC-D72Iq_Tv.js +1 -0
  286. ksadk/server/static/assets/railroadDiagram-O6MQD6OU-_Y3Ojg6G.js +1 -0
  287. ksadk/server/static/assets/requirementDiagram-EFPCY7ZU-DeCFdg9K.js +84 -0
  288. ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-l6iXmU65.js → sankeyDiagram-P5KCCOFB-CPH75rhw.js} +7 -7
  289. ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-Cq6UBeQB.js → sequenceDiagram-WJ2MYXX4-OEpQQdr4.js} +10 -10
  290. ksadk/server/static/assets/sizeCapture-X5ZJPWSS-heUErzhI.js +1 -0
  291. ksadk/server/static/assets/stateDiagram-HBIQ2CUA-DvA3jSMB.js +1 -0
  292. ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BgQY03nz.js +1 -0
  293. ksadk/server/static/assets/swimlanes-XN3QIQJK-CR-dfN_t.js +1 -0
  294. ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-DLVw3q5Q.js +8 -0
  295. ksadk/server/static/assets/{timeline-definition-PNZ67QCA-DEz24vV0.js → timeline-definition-24CTP7MA-DAVJf1TX.js} +7 -7
  296. ksadk/server/static/assets/treeView-Q6P3EWNA-BMpO0wsB.js +1 -0
  297. ksadk/server/static/assets/treemap-WGGIJYW6-Et0aqzU3.js +1 -0
  298. ksadk/server/static/assets/vennDiagram-4TSXK5OY-DwM8eDkh.js +34 -0
  299. ksadk/server/static/assets/wardley-WFR3VGLG-CbY0mhpl.js +1 -0
  300. ksadk/server/static/assets/wardleyDiagram-VM6X3IG4-DZL-Zz2t.js +78 -0
  301. ksadk/server/static/assets/xychartDiagram-S5SC5T6Z-Dy6Yh_hu.js +7 -0
  302. ksadk/server/static/index.html +2 -2
  303. ksadk/sessions/__init__.py +26 -9
  304. ksadk/sessions/_local_service_sync.py +688 -0
  305. ksadk/sessions/_local_tables.py +21 -0
  306. ksadk/sessions/_postgres_schema.py +305 -0
  307. ksadk/sessions/_postgres_tables.py +15 -0
  308. ksadk/sessions/base.py +80 -1
  309. ksadk/sessions/in_memory.py +53 -2
  310. ksadk/sessions/local_service.py +63 -604
  311. ksadk/sessions/postgres_service.py +58 -148
  312. ksadk/sessions/resilient.py +35 -1
  313. ksadk/studio/api.py +577 -25
  314. ksadk/studio/api_contracts.py +114 -5
  315. ksadk/studio/api_memory_routes.py +82 -0
  316. ksadk/studio/authoring.py +252 -27
  317. ksadk/studio/authoring_coordinator.py +799 -67
  318. ksadk/studio/builder.py +88 -11
  319. ksadk/studio/capabilities.py +17 -0
  320. ksadk/studio/cloud.py +1916 -95
  321. ksadk/studio/codex_agent_service.py +125 -14
  322. ksadk/studio/codex_authoring.py +469 -0
  323. ksadk/studio/codex_builder.py +138 -25
  324. ksadk/studio/codex_manifest.py +51 -22
  325. ksadk/studio/codex_run.py +46 -3
  326. ksadk/studio/compiler.py +1 -0
  327. ksadk/studio/contracts.py +114 -3
  328. ksadk/studio/event_store.py +25 -95
  329. ksadk/studio/framework_run.py +231 -3
  330. ksadk/studio/hosted_kernel.py +317 -0
  331. ksadk/studio/manifest_resolver.py +131 -0
  332. ksadk/studio/model_client.py +95 -9
  333. ksadk/studio/model_profile_service.py +1 -1
  334. ksadk/studio/operations.py +37 -5
  335. ksadk/studio/otel_trace.py +348 -42
  336. ksadk/studio/pcm_memory.py +85 -0
  337. ksadk/studio/repository.py +23 -2
  338. ksadk/studio/resource_catalog.py +43 -0
  339. ksadk/studio/run_service.py +563 -137
  340. ksadk/studio/runtime_source.py +42 -16
  341. ksadk/studio/service.py +1180 -72
  342. ksadk/studio/shared_web.py +32 -22
  343. ksadk/studio/static/assets/index-BLpcdrgW.css +1 -0
  344. ksadk/studio/static/assets/index-CYekR2Xv.js +260 -0
  345. ksadk/studio/static/index.html +2 -2
  346. ksadk/studio/templates.py +100 -8
  347. ksadk/studio/validator.py +19 -0
  348. ksadk/studio/workspace.py +30 -32
  349. ksadk/tracing/setup.py +304 -3
  350. ksadk/version.py +1 -1
  351. ksadk_runtime_common/schemas/runtime_event_v1.json +37 -0
  352. ksadk_runtime_common/schemas/runtime_event_v2.json +271 -0
  353. ksadk/events/parser.py +0 -191
  354. ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +0 -1
  355. ksadk/server/static/assets/MermaidBlock-NERD_ef7.js +0 -303
  356. ksadk/server/static/assets/NativeTerminalPanel-CiHWf82q.js +0 -1
  357. ksadk/server/static/assets/arc-DrgjcKhl.js +0 -1
  358. ksadk/server/static/assets/architecture-7EHR7CIX-CpZLRCcY.js +0 -1
  359. ksadk/server/static/assets/architectureDiagram-3BPJPVTR-BXoVRbQ6.js +0 -36
  360. ksadk/server/static/assets/blockDiagram-GPEHLZMM-BnaB3Ywv.js +0 -132
  361. ksadk/server/static/assets/c4Diagram-AAUBKEIU-Dw4weZcp.js +0 -10
  362. ksadk/server/static/assets/channel-Dt3rQTFI.js +0 -1
  363. ksadk/server/static/assets/chunk-2J33WTMH-ChjpLK7j.js +0 -1
  364. ksadk/server/static/assets/chunk-55IACEB6-CK4ROATy.js +0 -1
  365. ksadk/server/static/assets/chunk-727SXJPM-B9KtTTgw.js +0 -206
  366. ksadk/server/static/assets/chunk-AQP2D5EJ-1O1mki3m.js +0 -231
  367. ksadk/server/static/assets/chunk-QZHKN3VN-iXEZtKQc.js +0 -1
  368. ksadk/server/static/assets/classDiagram-4FO5ZUOK-DIveV17Q.js +0 -1
  369. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-DIveV17Q.js +0 -1
  370. ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +0 -321
  371. ksadk/server/static/assets/dagre-BM42HDAG-D0QXg_MQ.js +0 -4
  372. ksadk/server/static/assets/dagre-Bx709z4p.js +0 -1
  373. ksadk/server/static/assets/diagram-2AECGRRQ-DBDDKwBQ.js +0 -43
  374. ksadk/server/static/assets/diagram-5GNKFQAL-BkwDOxmO.js +0 -10
  375. ksadk/server/static/assets/diagram-KO2AKTUF-Bv8waYvY.js +0 -3
  376. ksadk/server/static/assets/diagram-LMA3HP47-e1VECDdu.js +0 -24
  377. ksadk/server/static/assets/diagram-OG6HWLK6-Ds9adfRl.js +0 -24
  378. ksadk/server/static/assets/dist-B5J2uhNk.js +0 -1
  379. ksadk/server/static/assets/erDiagram-TEJ5UH35-BPItFKH6.js +0 -85
  380. ksadk/server/static/assets/eventmodeling-FCH6USID-DYFSxbqr.js +0 -1
  381. ksadk/server/static/assets/flowDiagram-I6XJVG4X-BbYB9w5Y.js +0 -162
  382. ksadk/server/static/assets/ganttDiagram-6RSMTGT7-TQXa0KXv.js +0 -292
  383. ksadk/server/static/assets/gitGraph-WXDBUCRP-DyNYp6BN.js +0 -1
  384. ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-bKblCnpa.js +0 -106
  385. ksadk/server/static/assets/graphlib-B8gBHxth.js +0 -1
  386. ksadk/server/static/assets/index-R52pJJvg.js +0 -177
  387. ksadk/server/static/assets/index-jJr3_955.css +0 -2
  388. ksadk/server/static/assets/info-J43DQDTF-WUx7qTde.js +0 -1
  389. ksadk/server/static/assets/infoDiagram-5YYISTIA-BGVT7J_i.js +0 -2
  390. ksadk/server/static/assets/katex-CYWseY5E.js +0 -265
  391. ksadk/server/static/assets/mermaid-parser.core-BWrDjSZM.js +0 -161
  392. ksadk/server/static/assets/packet-YPE3B663-BN13y_xl.js +0 -1
  393. ksadk/server/static/assets/pie-LRSECV5Y-ftSLXhhc.js +0 -1
  394. ksadk/server/static/assets/pieDiagram-4H26LBE5-GmmqNiVH.js +0 -30
  395. ksadk/server/static/assets/radar-GUYGQ44K-jaICkIeO.js +0 -1
  396. ksadk/server/static/assets/requirementDiagram-4Y6WPE33-DSWFqL7c.js +0 -84
  397. ksadk/server/static/assets/stateDiagram-AJRCARHV-qzTQpg3Y.js +0 -1
  398. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-BYCoYGAN.js +0 -1
  399. ksadk/server/static/assets/treeView-BLDUP644-Cpib_vlz.js +0 -1
  400. ksadk/server/static/assets/treemap-LRROVOQU-DQxfmVHK.js +0 -1
  401. ksadk/server/static/assets/vennDiagram-CIIHVFJN-CMCixki2.js +0 -34
  402. ksadk/server/static/assets/wardley-L42UT6IY-BQYHnnkU.js +0 -1
  403. ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-CVzB8F9K.js +0 -78
  404. ksadk/server/static/assets/xychartDiagram-2RQKCTM6-BFBqBLc1.js +0 -7
  405. ksadk/studio/evaluation.py +0 -192
  406. ksadk/studio/static/assets/index-CaEN0eTR.css +0 -1
  407. ksadk/studio/static/assets/index-DI2eY_NH.js +0 -252
  408. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/WHEEL +0 -0
  409. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/entry_points.txt +0 -0
  410. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/licenses/LICENSE +0 -0
  411. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/top_level.txt +0 -0
@@ -2,26 +2,124 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import copy
6
+ import json
7
+ import logging
5
8
  import os
6
9
  import shutil
7
10
  import threading
11
+ import time
12
+ from collections import OrderedDict
8
13
  from pathlib import Path
9
14
  from typing import Any, cast
10
15
 
11
- from ksadk.studio.authoring import AgentAuthoringService
16
+ from ksadk.studio.authoring import AgentAuthoringService, ConversationProposal
12
17
  from ksadk.studio.codex_manifest import CodexAgentManifest
13
18
  from ksadk.studio.contracts import (
14
19
  AgentBindings,
15
20
  AgentDraft,
16
21
  AgentSpec,
17
- Instructions,
22
+ CapabilitiesSpec,
23
+ CapabilityBinding,
18
24
  ModelSpec,
19
25
  RuntimeRef,
26
+ Usage,
20
27
  )
21
28
  from ksadk.studio.errors import StudioError
22
29
  from ksadk.studio.identifiers import generate_agent_slug, is_generated_agent_slug
23
30
  from ksadk.studio.templates import default_agent_spec
24
31
 
32
+ LOGGER = logging.getLogger(__name__)
33
+
34
+ # 对话创建阶段推进序列。前端只消费阶段名展示两段式文案,不解析内容。
35
+ CONVERSATION_STAGES = (
36
+ "resolving_model",
37
+ "generating",
38
+ "codex_writing",
39
+ "validating",
40
+ "correcting",
41
+ "done",
42
+ "failed",
43
+ )
44
+ # 进度记录只保留最近的少量请求,避免长驻进程无限增长。
45
+ _CONVERSATION_STATUS_LIMIT = 64
46
+ _LOCAL_FALLBACK_MODEL_ERRORS = frozenset(
47
+ {
48
+ "AUTHORING_MODEL_OUTPUT_INVALID",
49
+ "MODEL_EMPTY_RESPONSE",
50
+ "MODEL_RATE_LIMITED",
51
+ "MODEL_REQUEST_FAILED",
52
+ "MODEL_RESPONSE_INVALID",
53
+ "MODEL_RESPONSE_TOO_LARGE",
54
+ }
55
+ )
56
+ _LOCAL_FALLBACK_REASON = {
57
+ "AUTHORING_MODEL_OUTPUT_INVALID": "invalid-model-output",
58
+ "MODEL_EMPTY_RESPONSE": "empty-model-output",
59
+ "MODEL_RATE_LIMITED": "model-rate-limited",
60
+ "MODEL_REQUEST_FAILED": "model-request-failed",
61
+ "MODEL_RESPONSE_INVALID": "invalid-model-response",
62
+ "MODEL_RESPONSE_TOO_LARGE": "model-response-too-large",
63
+ }
64
+
65
+
66
+ def _deduplicated_bindings(resource_ids: list[str]) -> list[CapabilityBinding]:
67
+ """Build a deterministic binding list from Studio-selected resource ids."""
68
+
69
+ seen: set[str] = set()
70
+ result: list[CapabilityBinding] = []
71
+ for resource_id in resource_ids:
72
+ normalized = str(resource_id or "").strip()
73
+ if not normalized or normalized in seen:
74
+ continue
75
+ seen.add(normalized)
76
+ result.append(CapabilityBinding(resource_id=normalized))
77
+ return result
78
+
79
+
80
+ def _inject_managed_bindings(
81
+ proposal: Any,
82
+ *,
83
+ model_spec: ModelSpec,
84
+ bindings: AgentBindings,
85
+ ) -> Any:
86
+ """Replace all model/resource fields invented by the authoring model.
87
+
88
+ The LLM may propose product semantics (name, runtime, instructions and
89
+ execution strategy), but it is never a source of connection information or
90
+ capability identities. Those are selected in Studio and validated before
91
+ this point. The Profile id remains the sole persisted model reference;
92
+ compiler/run materialisation resolves the current endpoint, credential and
93
+ parameters from the catalog. This also prevents provider discovery data,
94
+ limits and pricing from leaking into every Agent Revision. Resetting
95
+ capabilities is intentional: a model-generated inline Tool/MCP/Skill
96
+ contract must not become a deployable capability.
97
+ """
98
+
99
+ spec = proposal.spec
100
+ managed_bindings = bindings
101
+ # Codex Runtime owns its native tool surface. Studio-selected KsADK
102
+ # Tool contracts are meaningful for generic ADK/LangGraph Agents, but
103
+ # serialising them into a Codex Draft Patch advertises a binding the
104
+ # runtime cannot execute. Enforce the same boundary as the UI here so a
105
+ # direct API caller cannot bypass it.
106
+ if proposal.runtimeType == "codex" and bindings.tools:
107
+ managed_bindings = bindings.model_copy(update={"tools": []})
108
+ # Resolution above is still intentional: it validates the selected Profile
109
+ # before a proposal is returned. Do not serialize its catalog contract.
110
+ _ = model_spec
111
+ return proposal.model_copy(
112
+ update={
113
+ "spec": spec.model_copy(
114
+ update={
115
+ "model": None,
116
+ "bindings": managed_bindings.model_copy(deep=True),
117
+ "capabilities": CapabilitiesSpec(),
118
+ }
119
+ )
120
+ }
121
+ )
122
+
25
123
 
26
124
  class StudioAuthoringCoordinator:
27
125
  """Coordinates repositories without expanding the StudioService façade."""
@@ -29,7 +127,140 @@ class StudioAuthoringCoordinator:
29
127
  def __init__(self, studio: Any) -> None:
30
128
  self.studio = studio
31
129
  self.backend = AgentAuthoringService(studio.workspace)
130
+ # Codex authoring 执行器可注入替换(测试);默认惰性探测可用性。
131
+ self.codex_authoring: Any = getattr(studio, "codex_authoring_executor", None)
32
132
  self._id_lock = threading.Lock()
133
+ self._status_lock = threading.Lock()
134
+ self._conversation_status: OrderedDict[str, dict[str, Any]] = OrderedDict()
135
+
136
+ def _codex_authoring_executor(self) -> Any:
137
+ if self.codex_authoring is not None:
138
+ return self.codex_authoring
139
+ from ksadk.studio.codex_authoring import CodexAuthoringExecutor
140
+
141
+ self.codex_authoring = CodexAuthoringExecutor(
142
+ self.studio.workspace,
143
+ credential_resolver=self.studio.credentials,
144
+ )
145
+ return self.codex_authoring
146
+
147
+ def _use_codex_authoring(self) -> bool:
148
+ """Use heavy Codex filesystem authoring only by explicit opt-in.
149
+
150
+ Conversational Draft Patch authoring is a bounded structured-chat
151
+ request. Probing a local Codex installation and silently switching to
152
+ its full filesystem/tool harness made a small form action huge and
153
+ unreliable (and changed the selected model's request shape). Keep the
154
+ optional expert authoring path, but never choose it implicitly.
155
+ """
156
+
157
+ mode = str(os.environ.get("KSADK_STUDIO_AUTHORIZER") or "").strip().lower()
158
+ if mode in {"codex", "codex-writing", "1", "true"}:
159
+ return True
160
+ if mode in {"chat", "off", "0", "false", "none"}:
161
+ return False
162
+ return False
163
+
164
+ # ------------------------------------------------------------------
165
+ # Conversation authoring stage tracking
166
+ # ------------------------------------------------------------------
167
+
168
+ def _record_conversation_stage(
169
+ self,
170
+ request_id: str | None,
171
+ stage: str,
172
+ *,
173
+ detail: str | None = None,
174
+ ) -> None:
175
+ if not request_id:
176
+ return
177
+ entry = {
178
+ "requestId": request_id,
179
+ "stage": stage,
180
+ "updatedAt": time.time(),
181
+ **({"detail": detail} if detail else {}),
182
+ }
183
+ with self._status_lock:
184
+ self._conversation_status[request_id] = entry
185
+ self._conversation_status.move_to_end(request_id)
186
+ while len(self._conversation_status) > _CONVERSATION_STATUS_LIMIT:
187
+ self._conversation_status.popitem(last=False)
188
+
189
+ def conversation_status(self, request_id: str) -> dict[str, Any] | None:
190
+ with self._status_lock:
191
+ entry = self._conversation_status.get(request_id)
192
+ return dict(entry) if entry else None
193
+
194
+ def _local_conversation_fallback(
195
+ self,
196
+ *,
197
+ messages: list[dict[str, str]],
198
+ previous_proposal: ConversationProposal | None,
199
+ runtime_type: str,
200
+ model_spec: ModelSpec,
201
+ bindings: AgentBindings,
202
+ reason_code: str,
203
+ ) -> dict[str, Any]:
204
+ """Return a reviewable draft when the selected authoring model is unavailable.
205
+
206
+ This is intentionally not a hidden second model or an auto-deploy
207
+ mechanism. Studio keeps the user's selected runtime and resource
208
+ bindings, produces only deterministic editable semantic fields, and
209
+ tells the caller that a manual review is required.
210
+ """
211
+
212
+ latest_user_message = next(
213
+ (
214
+ str(item.get("content") or "").strip()
215
+ for item in reversed(messages)
216
+ if str(item.get("role") or "").strip() == "user"
217
+ and str(item.get("content") or "").strip()
218
+ ),
219
+ "请根据已确认的需求完成 Agent,并在部署前检查配置和权限。",
220
+ )
221
+ previous_instructions = (
222
+ previous_proposal.spec.instructions if previous_proposal else None
223
+ )
224
+ fallback_payload = {
225
+ "name": previous_proposal.name if previous_proposal else "待确认 Agent",
226
+ "slug": previous_proposal.slug if previous_proposal else "conversation-agent",
227
+ "description": (
228
+ previous_proposal.description
229
+ if previous_proposal and previous_proposal.description
230
+ else latest_user_message[:1024]
231
+ ),
232
+ "spec": {
233
+ "instructions": {
234
+ "system": (
235
+ previous_instructions.system
236
+ if previous_instructions and previous_instructions.system
237
+ else "根据用户需求完成任务;不确定时先澄清,并遵守已绑定能力的权限边界。"
238
+ ),
239
+ "task": latest_user_message,
240
+ }
241
+ },
242
+ }
243
+ proposal = self.backend.parse_conversation_proposal(
244
+ json.dumps(fallback_payload, ensure_ascii=False),
245
+ base=previous_proposal,
246
+ runtime_type=runtime_type,
247
+ )
248
+ return {
249
+ "proposal": _inject_managed_bindings(
250
+ proposal,
251
+ model_spec=model_spec,
252
+ bindings=bindings,
253
+ ).model_dump(by_alias=True, mode="json"),
254
+ "requiresConfirmation": True,
255
+ "authoringMode": "local-fallback",
256
+ "fallback": {
257
+ "active": True,
258
+ "reason": _LOCAL_FALLBACK_REASON.get(reason_code, "model-unavailable"),
259
+ },
260
+ "usage": Usage(source="local-fallback").model_dump(
261
+ by_alias=True, mode="json"
262
+ ),
263
+ }
33
264
 
34
265
  def create(
35
266
  self,
@@ -47,7 +278,30 @@ class StudioAuthoringCoordinator:
47
278
  resolved = (spec or default_agent_spec(template, description=description)).model_copy(
48
279
  deep=True
49
280
  )
50
- resolved.runtime = self.backend.runtime_ref(agent_id, runtime_type)
281
+ canonical_runtime = self.backend.runtime_ref(agent_id, runtime_type)
282
+ proposed_runtime = resolved.runtime
283
+ if proposed_runtime is not None and proposed_runtime.type != runtime_type:
284
+ raise StudioError(
285
+ "AGENT_RUNTIME_MISMATCH",
286
+ "AgentSpec Runtime 与创建方式不一致",
287
+ status_code=422,
288
+ field="runtimeType",
289
+ details={
290
+ "runtimeType": runtime_type,
291
+ "specRuntimeType": proposed_runtime.type,
292
+ },
293
+ )
294
+ if proposed_runtime is not None:
295
+ if runtime_type == "codex" and proposed_runtime.version:
296
+ canonical_runtime.version = proposed_runtime.version
297
+ elif runtime_type in {"adk", "langgraph"}:
298
+ canonical_runtime.entry_point = (
299
+ proposed_runtime.entry_point or canonical_runtime.entry_point
300
+ )
301
+ canonical_runtime.agent_variable = proposed_runtime.agent_variable
302
+ canonical_runtime.version = proposed_runtime.version
303
+ canonical_runtime.detection = proposed_runtime.detection
304
+ resolved.runtime = canonical_runtime
51
305
  if description:
52
306
  resolved.description = description
53
307
  draft = self.studio.create_studio_agent(
@@ -94,7 +348,15 @@ class StudioAuthoringCoordinator:
94
348
  status_code=422,
95
349
  )
96
350
  if spec.runtime.type in {"adk", "langgraph"}:
97
- spec.runtime = self.backend.runtime_ref(agent_id, spec.runtime.type)
351
+ imported_runtime = spec.runtime
352
+ canonical_runtime = self.backend.runtime_ref(agent_id, imported_runtime.type)
353
+ canonical_runtime.entry_point = (
354
+ imported_runtime.entry_point or canonical_runtime.entry_point
355
+ )
356
+ canonical_runtime.agent_variable = imported_runtime.agent_variable
357
+ canonical_runtime.version = imported_runtime.version
358
+ canonical_runtime.detection = imported_runtime.detection
359
+ spec.runtime = canonical_runtime
98
360
  created = self.studio.create_agent(
99
361
  agent_id=agent_id,
100
362
  name=display_name,
@@ -123,7 +385,24 @@ class StudioAuthoringCoordinator:
123
385
  return cast(AgentDraft, self.studio.drafts.get(agent_id))
124
386
 
125
387
  def inspect_project(self, project_path: str) -> dict:
126
- return self.backend.inspect_project(project_path)
388
+ inspection = self.backend.inspect_project(project_path)
389
+ spec, unresolved = self._project_agent_spec(
390
+ inspection,
391
+ agent_id="agentkit-preview",
392
+ model_profile_id=None,
393
+ )
394
+ return {
395
+ **inspection,
396
+ "agentSpec": spec.model_dump(by_alias=True, exclude_none=True, mode="json"),
397
+ "bindingProjection": {
398
+ "preserved": spec.bindings.model_dump(
399
+ by_alias=True,
400
+ exclude_none=True,
401
+ mode="json",
402
+ ),
403
+ "unresolved": unresolved,
404
+ },
405
+ }
127
406
 
128
407
  def commit_project(
129
408
  self,
@@ -155,30 +434,22 @@ class StudioAuthoringCoordinator:
155
434
 
156
435
  agent_id = self._allocate_agent_id(resolved_slug)
157
436
  resolved_slug = resolved_slug or agent_id
158
- config = inspection.get("evidence", {}).get("config", {})
159
- prompt = str(
160
- config.get("prompt")
161
- or config.get("instruction")
162
- or "You are a reliable assistant imported from an existing project."
163
- )
164
- runtime = RuntimeRef(
165
- type=cast(Any, runtime_type),
166
- project_path=str(inspection["projectPath"]),
167
- entry_point=str(inspection.get("entryPoint") or "agent.py"),
168
- agent_variable=str(
169
- inspection.get("agentVariable")
170
- or ("graph" if runtime_type == "langgraph" else "root_agent")
171
- ),
172
- detection="auto",
437
+ spec, unresolved = self._project_agent_spec(
438
+ inspection,
439
+ agent_id=agent_id,
440
+ model_profile_id=model_profile_id,
173
441
  )
442
+ if unresolved:
443
+ raise StudioError(
444
+ "PROJECT_BINDINGS_UNRESOLVED",
445
+ "项目中的 Tool、MCP 或 Skill 绑定无法无损映射,请先安装或修正对应资源",
446
+ status_code=422,
447
+ details={"unresolved": unresolved},
448
+ )
174
449
  created = self.studio.create_agent(
175
450
  agent_id=agent_id,
176
451
  name=display_name,
177
- spec=AgentSpec(
178
- runtime=runtime,
179
- instructions=Instructions(system=prompt),
180
- bindings=AgentBindings(model_profile_id=model_profile_id),
181
- ),
452
+ spec=spec,
182
453
  labels={
183
454
  "agentkit.ksyun.com/slug": self.backend.normalize_slug(resolved_slug),
184
455
  "agentkit.ksyun.com/source": "project-detection",
@@ -193,32 +464,511 @@ class StudioAuthoringCoordinator:
193
464
  *,
194
465
  messages: list[dict[str, str]],
195
466
  model_profile_id: str,
467
+ runtime_type: str = "codex",
468
+ agent_model_profile_ids: list[str] | None = None,
469
+ agent_default_model_profile_id: str | None = None,
470
+ tool_resource_ids: list[str] | None = None,
471
+ mcp_resource_ids: list[str] | None = None,
472
+ skill_resource_ids: list[str] | None = None,
473
+ request_id: str | None = None,
196
474
  ) -> dict:
197
- model_spec = self.studio.catalog.resolve_model(
198
- AgentBindings(model_profile_id=model_profile_id)
475
+ started = time.monotonic()
476
+ self._record_conversation_stage(request_id, "resolving_model")
477
+ LOGGER.info(
478
+ "conversation authoring started: modelProfileId=%s messages=%d requestId=%s",
479
+ model_profile_id,
480
+ len(messages),
481
+ request_id or "-",
199
482
  )
200
- if model_spec is None:
483
+ try:
484
+ result = await self._compose_conversation_inner(
485
+ messages=messages,
486
+ model_profile_id=model_profile_id,
487
+ runtime_type=runtime_type,
488
+ agent_model_profile_ids=agent_model_profile_ids or [],
489
+ agent_default_model_profile_id=agent_default_model_profile_id,
490
+ tool_resource_ids=tool_resource_ids or [],
491
+ mcp_resource_ids=mcp_resource_ids or [],
492
+ skill_resource_ids=skill_resource_ids or [],
493
+ request_id=request_id,
494
+ started=started,
495
+ )
496
+ except Exception as exc:
497
+ self._record_conversation_stage(request_id, "failed", detail=str(exc))
498
+ LOGGER.warning(
499
+ "conversation authoring failed after %.2fs: modelProfileId=%s reason=%s",
500
+ time.monotonic() - started,
501
+ model_profile_id,
502
+ exc,
503
+ )
504
+ raise
505
+ return result
506
+
507
+ async def _compose_conversation_inner(
508
+ self,
509
+ *,
510
+ messages: list[dict[str, str]],
511
+ model_profile_id: str,
512
+ runtime_type: str,
513
+ agent_model_profile_ids: list[str],
514
+ agent_default_model_profile_id: str | None,
515
+ tool_resource_ids: list[str],
516
+ mcp_resource_ids: list[str],
517
+ skill_resource_ids: list[str],
518
+ request_id: str | None,
519
+ started: float,
520
+ ) -> dict:
521
+ # The authoring model is a one-off control-plane choice. The Agent's
522
+ # model allow-list is a separate deploy-time contract and can contain
523
+ # multiple profiles. Keep backwards compatibility for API callers
524
+ # that only send ``modelProfileId`` by using it as the one Agent model.
525
+ selected_agent_model_ids = [
526
+ binding.resource_id
527
+ for binding in _deduplicated_bindings(agent_model_profile_ids or [model_profile_id])
528
+ ]
529
+ if not selected_agent_model_ids:
530
+ raise StudioError(
531
+ "AGENT_MODEL_REQUIRED",
532
+ "Agent 至少需要选择一个可用 Model Profile",
533
+ status_code=422,
534
+ )
535
+ default_agent_model_id = str(
536
+ agent_default_model_profile_id or selected_agent_model_ids[0]
537
+ ).strip()
538
+ if default_agent_model_id not in selected_agent_model_ids:
539
+ raise StudioError(
540
+ "AGENT_DEFAULT_MODEL_NOT_SELECTED",
541
+ "Agent 默认模型必须包含在可用模型列表中",
542
+ status_code=422,
543
+ details={
544
+ "defaultModelProfileId": default_agent_model_id,
545
+ "modelProfileIds": selected_agent_model_ids,
546
+ },
547
+ )
548
+ authoring_bindings = AgentBindings(
549
+ model_profile_id=model_profile_id,
550
+ model_profile_ids=[model_profile_id],
551
+ )
552
+ bindings = AgentBindings(
553
+ model_profile_id=default_agent_model_id,
554
+ model_profile_ids=selected_agent_model_ids,
555
+ tools=_deduplicated_bindings(tool_resource_ids),
556
+ mcp_servers=_deduplicated_bindings(mcp_resource_ids),
557
+ skills=_deduplicated_bindings(skill_resource_ids),
558
+ )
559
+ # Resolve every selected resource now. This validates kind, readiness
560
+ # and capability contracts before an LLM response can be displayed as a
561
+ # deployable Draft Patch.
562
+ authoring_model_spec = self.studio.catalog.resolve_model(authoring_bindings)
563
+ if authoring_model_spec is None:
201
564
  raise StudioError(
202
565
  "AGENT_MODEL_REQUIRED",
203
566
  "对话构建需要选择一个 Model Profile",
204
567
  status_code=422,
205
568
  )
206
- model = self.studio.catalog.resolver.resolve_model(model_spec)
207
- response = await self.studio.model_client.complete(
208
- model,
209
- messages=self.backend.conversation_messages(messages),
210
- network_policy=self.backend.authoring_network_policy(model.endpoint_url),
211
- timeout_seconds=60,
212
- max_attempts=2,
213
- backoff_seconds=1,
569
+ # Resolve every Agent model before returning a Draft Patch. This
570
+ # catches a deleted, wrong-kind or malformed secondary profile rather
571
+ # than accepting a non-deployable multi-model Agent in the browser.
572
+ self.studio.catalog.resolve_models(bindings)
573
+ model_spec = self.studio.catalog.resolve_model(bindings)
574
+ if model_spec is None: # defensive: bindings above requires a default
575
+ raise StudioError(
576
+ "AGENT_MODEL_REQUIRED",
577
+ "Agent 需要选择一个默认 Model Profile",
578
+ status_code=422,
579
+ )
580
+ self.studio.catalog.policy_preview(bindings)
581
+ self.studio.catalog.resolve_mcp_servers(bindings)
582
+ self.studio.catalog.resolve_skills(bindings)
583
+ model = self.studio.catalog.resolver.resolve_model(authoring_model_spec)
584
+ # authoring 自身的 max_tokens 跟随 profile:未配置则 payload 不带该字段
585
+ # (服务端默认);finishReason=length 截断由 model_client 一次性扩容重试兜底。
586
+ LOGGER.info(
587
+ "conversation authoring model resolved: model=%s endpoint=%s",
588
+ getattr(model, "model", "-"),
589
+ getattr(model, "endpoint_url", "-"),
590
+ )
591
+ normalized_runtime_type = str(runtime_type or "").strip().lower()
592
+ if normalized_runtime_type not in {"codex", "adk", "langgraph"}:
593
+ raise StudioError(
594
+ "AGENT_RUNTIME_INVALID",
595
+ "对话构建 Runtime 仅支持 Codex、ADK 或 LangGraph",
596
+ status_code=422,
597
+ field="runtimeType",
598
+ )
599
+ normalized_messages = self.backend.conversation_messages(
600
+ messages,
601
+ runtime_type=normalized_runtime_type,
602
+ )
603
+ previous_proposal = None
604
+ for item in reversed(messages):
605
+ if str(item.get("role") or "").strip() != "assistant":
606
+ continue
607
+ try:
608
+ previous_proposal = self.backend.parse_conversation_proposal(
609
+ str(item.get("content") or ""),
610
+ runtime_type=normalized_runtime_type,
611
+ )
612
+ except StudioError:
613
+ continue
614
+ break
615
+
616
+ if self._use_codex_authoring():
617
+ codex_result = await self._compose_conversation_codex(
618
+ messages=messages,
619
+ model=model,
620
+ previous_proposal=previous_proposal,
621
+ request_id=request_id,
622
+ started=started,
623
+ model_profile_id=model_profile_id,
624
+ model_spec=model_spec,
625
+ bindings=bindings,
626
+ )
627
+ if codex_result is not None:
628
+ return codex_result
629
+
630
+ request_options = {
631
+ "network_policy": self.backend.authoring_network_policy(model.endpoint_url),
632
+ # The authoring output is deliberately small. Keep a bounded
633
+ # request budget so an unavailable upstream cannot make a simple
634
+ # create flow appear to hang for minutes.
635
+ "timeout_seconds": 20,
636
+ "backoff_seconds": 1,
637
+ "response_format": {"type": "json_object"},
638
+ # finishReason=length 截断时在 model_client 内自动扩容 max_tokens
639
+ # 重发一次(一次性),避免大 JSON 被截断成空响应。
640
+ "retry_on_length": True,
641
+ }
642
+ self._record_conversation_stage(request_id, "generating")
643
+ try:
644
+ response = await self.studio.model_client.complete(
645
+ model,
646
+ messages=normalized_messages,
647
+ max_attempts=2,
648
+ **request_options,
649
+ )
650
+ except StudioError as exc:
651
+ if exc.code not in _LOCAL_FALLBACK_MODEL_ERRORS:
652
+ raise
653
+ LOGGER.warning(
654
+ "conversation authoring model unavailable; returning local fallback: "
655
+ "modelProfileId=%s reason=%s",
656
+ model_profile_id,
657
+ exc.code,
658
+ )
659
+ self._record_conversation_stage(request_id, "done")
660
+ return self._local_conversation_fallback(
661
+ messages=messages,
662
+ previous_proposal=previous_proposal,
663
+ runtime_type=normalized_runtime_type,
664
+ model_spec=model_spec,
665
+ bindings=bindings,
666
+ reason_code=exc.code,
667
+ )
668
+ self._record_conversation_stage(request_id, "validating")
669
+ try:
670
+ proposal = self.backend.parse_conversation_proposal(
671
+ response.content,
672
+ base=previous_proposal,
673
+ runtime_type=normalized_runtime_type,
674
+ )
675
+ except StudioError as exc:
676
+ if exc.code != "AUTHORING_MODEL_OUTPUT_INVALID":
677
+ raise
678
+ LOGGER.warning(
679
+ "conversation authoring patch invalid; returning local fallback: "
680
+ "modelProfileId=%s",
681
+ model_profile_id,
682
+ )
683
+ self._record_conversation_stage(request_id, "done")
684
+ return self._local_conversation_fallback(
685
+ messages=messages,
686
+ previous_proposal=previous_proposal,
687
+ runtime_type=normalized_runtime_type,
688
+ model_spec=model_spec,
689
+ bindings=bindings,
690
+ reason_code=exc.code,
691
+ )
692
+ self._record_conversation_stage(request_id, "done")
693
+ LOGGER.info(
694
+ "conversation authoring finished in %.2fs: modelProfileId=%s slug=%s",
695
+ time.monotonic() - started,
696
+ model_profile_id,
697
+ getattr(proposal, "slug", "-"),
214
698
  )
215
- proposal = self.backend.parse_conversation_proposal(response.content)
216
699
  return {
217
- "proposal": proposal.model_dump(mode="json"),
700
+ "proposal": _inject_managed_bindings(
701
+ proposal,
702
+ model_spec=model_spec,
703
+ bindings=bindings,
704
+ ).model_dump(
705
+ by_alias=True, mode="json"
706
+ ),
218
707
  "requiresConfirmation": True,
708
+ "authoringMode": "chat",
219
709
  "usage": response.usage.model_dump(by_alias=True, mode="json"),
220
710
  }
221
711
 
712
+ async def _compose_conversation_codex(
713
+ self,
714
+ *,
715
+ messages: list[dict[str, str]],
716
+ model: Any,
717
+ previous_proposal: Any,
718
+ request_id: str | None,
719
+ started: float,
720
+ model_profile_id: str,
721
+ model_spec: ModelSpec,
722
+ bindings: AgentBindings,
723
+ ) -> dict | None:
724
+ """让真实 Codex 会话在工作区写 agentkit.yaml;任何失败降级 chat 链。
725
+
726
+ 返回 ``None`` 表示应降级(探测失败/超时/重试后仍不合法),调用方继续走
727
+ 既有 chat 链路,保证零回归。
728
+ """
729
+
730
+ self._record_conversation_stage(request_id, "codex_writing")
731
+ executor = self._codex_authoring_executor()
732
+ try:
733
+ result = await executor.compose(
734
+ messages=messages,
735
+ model=model,
736
+ base=previous_proposal,
737
+ request_id=request_id,
738
+ )
739
+ except Exception as exc:
740
+ LOGGER.warning(
741
+ "codex authoring failed after %.2fs, falling back to chat chain: "
742
+ "modelProfileId=%s requestId=%s reason=%s",
743
+ time.monotonic() - started,
744
+ model_profile_id,
745
+ request_id or "-",
746
+ exc,
747
+ )
748
+ self._record_conversation_stage(
749
+ request_id, "generating", detail=f"codex authoring 降级: {exc}"
750
+ )
751
+ return None
752
+ self._record_conversation_stage(request_id, "done")
753
+ LOGGER.info(
754
+ "conversation authoring finished in %.2fs via codex: modelProfileId=%s "
755
+ "slug=%s attempts=%d",
756
+ time.monotonic() - started,
757
+ model_profile_id,
758
+ getattr(result.proposal, "slug", "-"),
759
+ result.attempts,
760
+ )
761
+ return {
762
+ "proposal": _inject_managed_bindings(
763
+ result.proposal,
764
+ model_spec=model_spec,
765
+ bindings=bindings,
766
+ ).model_dump(
767
+ by_alias=True, mode="json"
768
+ ),
769
+ "requiresConfirmation": True,
770
+ "authoringMode": "codex",
771
+ "usage": result.usage.model_dump(by_alias=True, mode="json"),
772
+ }
773
+
774
+ def _project_agent_spec(
775
+ self,
776
+ inspection: dict[str, Any],
777
+ *,
778
+ agent_id: str,
779
+ model_profile_id: str | None,
780
+ ) -> tuple[AgentSpec, list[dict[str, Any]]]:
781
+ """Project detected project config without silently dropping authoring fields."""
782
+
783
+ config = inspection.get("evidence", {}).get("config", {})
784
+ if not isinstance(config, dict):
785
+ config = {}
786
+ embedded = config.get("spec")
787
+ payload: dict[str, Any] = copy.deepcopy(embedded) if isinstance(embedded, dict) else {}
788
+
789
+ for field in (
790
+ "description",
791
+ "model",
792
+ "capabilities",
793
+ "bindings",
794
+ "execution",
795
+ "context",
796
+ "memory",
797
+ "security",
798
+ "evaluation",
799
+ ):
800
+ if field not in payload and field in config:
801
+ payload[field] = copy.deepcopy(config[field])
802
+
803
+ instructions = payload.get("instructions")
804
+ if not isinstance(instructions, dict):
805
+ instructions = {}
806
+ instructions.setdefault(
807
+ "system",
808
+ str(
809
+ config.get("prompt")
810
+ or config.get("instruction")
811
+ or "You are a reliable assistant imported from an existing project."
812
+ ),
813
+ )
814
+ instructions.setdefault("task", str(config.get("task_prompt") or config.get("task") or ""))
815
+ payload["instructions"] = instructions
816
+
817
+ model_payload = payload.get("model")
818
+ if isinstance(model_payload, str) and model_payload.strip():
819
+ upstream = (
820
+ (os.environ.get("OPENAI_BASE_URL") or os.environ.get("OPENAI_API_BASE") or "")
821
+ .strip()
822
+ .rstrip("/")
823
+ )
824
+ endpoint = (
825
+ {"endpointUrl": upstream}
826
+ if upstream.endswith("/chat/completions")
827
+ else {"baseUrl": upstream or "https://api.openai.com/v1"}
828
+ )
829
+ payload["model"] = {
830
+ "model": model_payload.strip(),
831
+ "credentialRef": "env://AGENTKIT_MODEL_API_KEY",
832
+ **endpoint,
833
+ }
834
+
835
+ bindings = payload.get("bindings")
836
+ if not isinstance(bindings, dict):
837
+ bindings = {}
838
+ else:
839
+ bindings = copy.deepcopy(bindings)
840
+ capabilities = payload.get("capabilities")
841
+ if not isinstance(capabilities, dict):
842
+ capabilities = {}
843
+ else:
844
+ capabilities = copy.deepcopy(capabilities)
845
+ unresolved: list[dict[str, Any]] = []
846
+ legacy_fields = (
847
+ ("tools", "tools"),
848
+ ("mcpServers", "mcp_servers"),
849
+ ("skills", "skills"),
850
+ )
851
+ for canonical, legacy in legacy_fields:
852
+ if canonical in bindings or canonical in capabilities:
853
+ continue
854
+ raw = config.get(canonical, config.get(legacy))
855
+ if raw is None:
856
+ continue
857
+ if not isinstance(raw, list):
858
+ unresolved.append(
859
+ {"kind": canonical, "value": copy.deepcopy(raw), "reason": "not-a-list"}
860
+ )
861
+ continue
862
+ projected_bindings: list[dict[str, Any]] = []
863
+ projected_capabilities: list[dict[str, Any]] = []
864
+ for item in raw:
865
+ if isinstance(item, str) and item.strip():
866
+ projected_bindings.append({"resourceId": item.strip()})
867
+ elif isinstance(item, dict) and (item.get("resourceId") or item.get("resource_id")):
868
+ projected_bindings.append(copy.deepcopy(item))
869
+ elif isinstance(item, dict) and item.get("name") and item.get("version"):
870
+ projected_capabilities.append(copy.deepcopy(item))
871
+ else:
872
+ unresolved.append(
873
+ {
874
+ "kind": canonical,
875
+ "value": copy.deepcopy(item),
876
+ "reason": "unsupported-binding-shape",
877
+ }
878
+ )
879
+ if projected_bindings:
880
+ bindings[canonical] = projected_bindings
881
+ if projected_capabilities:
882
+ capabilities[canonical] = projected_capabilities
883
+
884
+ if model_profile_id:
885
+ existing_profiles = list(
886
+ bindings.get("modelProfileIds") or bindings.get("model_profile_ids") or []
887
+ )
888
+ bindings["modelProfileId"] = model_profile_id
889
+ bindings["modelProfileIds"] = list(
890
+ dict.fromkeys([model_profile_id, *existing_profiles])
891
+ )
892
+ if "modelParameters" not in bindings and "model_parameters" in config:
893
+ bindings["modelParameters"] = copy.deepcopy(config["model_parameters"])
894
+ if "policyTemplate" not in bindings:
895
+ policy = config.get("policy_template", config.get("policy"))
896
+ if isinstance(policy, str) and policy:
897
+ bindings["policyTemplate"] = policy
898
+ payload["bindings"] = bindings
899
+ payload["capabilities"] = capabilities
900
+
901
+ try:
902
+ spec = AgentSpec.model_validate(payload)
903
+ except ValueError as exc:
904
+ raise StudioError(
905
+ "PROJECT_AGENT_SPEC_INVALID",
906
+ "项目配置无法无损转换为 AgentSpec",
907
+ status_code=422,
908
+ details={"reason": str(exc)},
909
+ ) from exc
910
+
911
+ runtime_type = str(inspection["runtimeType"])
912
+ detected_runtime = self.backend.runtime_ref(agent_id, runtime_type)
913
+ supplied_runtime = spec.runtime
914
+ if supplied_runtime is not None and supplied_runtime.type != runtime_type:
915
+ raise StudioError(
916
+ "PROJECT_RUNTIME_MISMATCH",
917
+ "项目配置中的 Runtime 与检测结果不一致",
918
+ status_code=422,
919
+ details={
920
+ "detected": runtime_type,
921
+ "configured": supplied_runtime.type,
922
+ },
923
+ )
924
+ if runtime_type in {"adk", "langgraph"}:
925
+ detected_runtime = RuntimeRef(
926
+ type=cast(Any, runtime_type),
927
+ project_path=str(inspection["projectPath"]),
928
+ entry_point=(
929
+ supplied_runtime.entry_point
930
+ if supplied_runtime and supplied_runtime.entry_point
931
+ else str(inspection.get("entryPoint") or "agent.py")
932
+ ),
933
+ agent_variable=(
934
+ supplied_runtime.agent_variable
935
+ if supplied_runtime
936
+ else str(
937
+ inspection.get("agentVariable")
938
+ or ("graph" if runtime_type == "langgraph" else "root_agent")
939
+ )
940
+ ),
941
+ version=supplied_runtime.version if supplied_runtime else None,
942
+ detection="auto",
943
+ )
944
+ elif supplied_runtime is not None and supplied_runtime.version:
945
+ detected_runtime.version = supplied_runtime.version
946
+ spec.runtime = detected_runtime
947
+
948
+ catalog_ids = {
949
+ kind: {item.resource_id for item in self.studio.catalog.list(kind=kind, limit=500)}
950
+ for kind in ("tool", "mcp", "skill")
951
+ }
952
+ for kind, values in (
953
+ ("tool", spec.bindings.tools),
954
+ ("mcp", spec.bindings.mcp_servers),
955
+ ("skill", spec.bindings.skills),
956
+ ):
957
+ for binding in values:
958
+ if binding.resource_id not in catalog_ids[kind]:
959
+ unresolved.append(
960
+ {
961
+ "kind": kind,
962
+ "value": binding.model_dump(
963
+ by_alias=True,
964
+ exclude_none=True,
965
+ mode="json",
966
+ ),
967
+ "reason": "not-in-resource-catalog",
968
+ }
969
+ )
970
+ return spec, unresolved
971
+
222
972
  def _agent_exists(self, agent_id: str) -> bool:
223
973
  return bool(
224
974
  self.studio.codex_manifests.exists(agent_id)
@@ -250,36 +1000,18 @@ class StudioAuthoringCoordinator:
250
1000
  *,
251
1001
  resolved_slug: str,
252
1002
  ) -> AgentDraft:
253
- upstream = (
254
- (os.environ.get("OPENAI_BASE_URL") or os.environ.get("OPENAI_API_BASE") or "")
255
- .strip()
256
- .rstrip("/")
257
- )
258
- if upstream.endswith("/chat/completions"):
259
- model_endpoint: dict[str, str] = {"endpoint_url": upstream}
260
- else:
261
- model_endpoint = {"base_url": upstream or "https://api.openai.com/v1"}
262
- spec = AgentSpec(
263
- runtime=RuntimeRef(type="codex", version=manifest.runtime.version),
264
- instructions=Instructions(system=manifest.prompt),
265
- model=ModelSpec(
266
- model=manifest.model,
267
- credential_ref="env://AGENTKIT_MODEL_API_KEY",
268
- **model_endpoint,
269
- ),
270
- )
271
- return cast(
272
- AgentDraft,
273
- self.studio.create_codex_agent(
274
- agent_id=agent_id,
275
- spec=spec,
276
- name=display_name,
277
- labels={
278
- "agentkit.ksyun.com/slug": self.backend.normalize_slug(resolved_slug),
279
- "agentkit.ksyun.com/source": "import",
280
- },
281
- ),
1003
+ imported = manifest.model_copy(update={"name": agent_id}, deep=True)
1004
+ snapshot = self.studio.codex_manifests.save(imported)
1005
+ draft = self.studio.codex_agents._project(snapshot)
1006
+ draft.metadata.name = display_name
1007
+ draft.metadata.labels.update(
1008
+ {
1009
+ "agentkit.ksyun.com/slug": self.backend.normalize_slug(resolved_slug),
1010
+ "agentkit.ksyun.com/source": "import",
1011
+ }
282
1012
  )
1013
+ self.studio.codex_drafts.save(draft)
1014
+ return cast(AgentDraft, self.studio.codex_agents._project(snapshot, current=draft))
283
1015
 
284
1016
 
285
1017
  __all__ = ["StudioAuthoringCoordinator"]