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,1017 @@
1
+ """Application-level commits for Agent authoring inspections."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import copy
6
+ import json
7
+ import logging
8
+ import os
9
+ import shutil
10
+ import threading
11
+ import time
12
+ from collections import OrderedDict
13
+ from pathlib import Path
14
+ from typing import Any, cast
15
+
16
+ from ksadk.studio.authoring import AgentAuthoringService, ConversationProposal
17
+ from ksadk.studio.codex_manifest import CodexAgentManifest
18
+ from ksadk.studio.contracts import (
19
+ AgentBindings,
20
+ AgentDraft,
21
+ AgentSpec,
22
+ CapabilitiesSpec,
23
+ CapabilityBinding,
24
+ ModelSpec,
25
+ RuntimeRef,
26
+ Usage,
27
+ )
28
+ from ksadk.studio.errors import StudioError
29
+ from ksadk.studio.identifiers import generate_agent_slug, is_generated_agent_slug
30
+ from ksadk.studio.templates import default_agent_spec
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
+
123
+
124
+ class StudioAuthoringCoordinator:
125
+ """Coordinates repositories without expanding the StudioService façade."""
126
+
127
+ def __init__(self, studio: Any) -> None:
128
+ self.studio = studio
129
+ self.backend = AgentAuthoringService(studio.workspace)
130
+ # Codex authoring 执行器可注入替换(测试);默认惰性探测可用性。
131
+ self.codex_authoring: Any = getattr(studio, "codex_authoring_executor", None)
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
+ }
264
+
265
+ def create(
266
+ self,
267
+ *,
268
+ name: str,
269
+ slug: str | None = None,
270
+ runtime_type: str,
271
+ template: str = "blank",
272
+ description: str = "",
273
+ spec: AgentSpec | None = None,
274
+ ) -> AgentDraft:
275
+ with self._id_lock:
276
+ agent_id = self._allocate_agent_id(slug)
277
+ resolved_slug = slug or agent_id
278
+ resolved = (spec or default_agent_spec(template, description=description)).model_copy(
279
+ deep=True
280
+ )
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
305
+ if description:
306
+ resolved.description = description
307
+ draft = self.studio.create_studio_agent(
308
+ agent_id=agent_id,
309
+ name=name,
310
+ description=description,
311
+ template=template,
312
+ spec=resolved,
313
+ labels={"agentkit.ksyun.com/slug": self.backend.normalize_slug(resolved_slug)},
314
+ )
315
+ return cast(AgentDraft, draft)
316
+
317
+ def inspect_import(self, content: bytes, *, filename: str) -> dict:
318
+ return self.backend.inspect_import(content, filename=filename)
319
+
320
+ def commit_import(
321
+ self,
322
+ inspection_token: str,
323
+ *,
324
+ name: str | None = None,
325
+ slug: str | None = None,
326
+ ) -> AgentDraft:
327
+ inspection = self.backend.load_import(inspection_token)
328
+ display_name = str(name or inspection.display_name).strip()
329
+ agent_id = self._allocate_agent_id(slug)
330
+ resolved_slug = slug or agent_id
331
+ if inspection.kind == "codex-manifest":
332
+ created = self._commit_codex_import(
333
+ agent_id,
334
+ display_name,
335
+ CodexAgentManifest.model_validate(inspection.payload),
336
+ resolved_slug=resolved_slug,
337
+ )
338
+ self.backend.consume_import(inspection_token)
339
+ return created
340
+
341
+ imported = AgentDraft.model_validate(inspection.payload)
342
+ spec = imported.spec.model_copy(deep=True)
343
+ source = self.backend.source_directory(inspection)
344
+ if spec.runtime is None:
345
+ raise StudioError(
346
+ "AGENT_RUNTIME_REQUIRED",
347
+ "导入 Agent 必须声明 RuntimeRef",
348
+ status_code=422,
349
+ )
350
+ if spec.runtime.type in {"adk", "langgraph"}:
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
360
+ created = self.studio.create_agent(
361
+ agent_id=agent_id,
362
+ name=display_name,
363
+ description=spec.description,
364
+ template=imported.metadata.labels.get(
365
+ "agentkit.ksyun.com/template",
366
+ "blank",
367
+ ),
368
+ spec=spec,
369
+ labels={
370
+ "agentkit.ksyun.com/slug": self.backend.normalize_slug(resolved_slug),
371
+ "agentkit.ksyun.com/source": "import",
372
+ "agentkit.ksyun.com/source-digest": inspection.source_digest,
373
+ },
374
+ )
375
+ if source is not None and created.spec.runtime and created.spec.runtime.project_path:
376
+ target = self.studio.workspace.resolve(created.spec.runtime.project_path)
377
+ shutil.rmtree(target, ignore_errors=True)
378
+ shutil.copytree(
379
+ source,
380
+ target,
381
+ symlinks=False,
382
+ ignore=shutil.ignore_patterns("__pycache__", "*.pyc", ".DS_Store"),
383
+ )
384
+ self.backend.consume_import(inspection_token)
385
+ return cast(AgentDraft, self.studio.drafts.get(agent_id))
386
+
387
+ def inspect_project(self, project_path: str) -> dict:
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
+ }
406
+
407
+ def commit_project(
408
+ self,
409
+ inspection_token: str,
410
+ *,
411
+ name: str | None = None,
412
+ slug: str | None = None,
413
+ model_profile_id: str | None = None,
414
+ ) -> AgentDraft:
415
+ inspection = self.backend.load_project(inspection_token)
416
+ runtime_type = str(inspection["runtimeType"])
417
+ display_name = str(name or inspection.get("name") or "Imported Agent").strip()
418
+ resolved_slug = slug
419
+ if runtime_type == "codex":
420
+ manifest_path = self.studio.workspace.resolve(
421
+ Path(str(inspection["projectPath"])) / "agentengine.yaml",
422
+ must_exist=True,
423
+ )
424
+ created = self.commit_import(
425
+ self.inspect_import(
426
+ manifest_path.read_bytes(),
427
+ filename="agentengine.yaml",
428
+ )["inspectionToken"],
429
+ name=display_name,
430
+ slug=resolved_slug,
431
+ )
432
+ self.backend.consume_project(inspection_token)
433
+ return created
434
+
435
+ agent_id = self._allocate_agent_id(resolved_slug)
436
+ resolved_slug = resolved_slug or agent_id
437
+ spec, unresolved = self._project_agent_spec(
438
+ inspection,
439
+ agent_id=agent_id,
440
+ model_profile_id=model_profile_id,
441
+ )
442
+ if unresolved:
443
+ raise StudioError(
444
+ "PROJECT_BINDINGS_UNRESOLVED",
445
+ "项目中的 Tool、MCP 或 Skill 绑定无法无损映射,请先安装或修正对应资源",
446
+ status_code=422,
447
+ details={"unresolved": unresolved},
448
+ )
449
+ created = self.studio.create_agent(
450
+ agent_id=agent_id,
451
+ name=display_name,
452
+ spec=spec,
453
+ labels={
454
+ "agentkit.ksyun.com/slug": self.backend.normalize_slug(resolved_slug),
455
+ "agentkit.ksyun.com/source": "project-detection",
456
+ "agentkit.ksyun.com/source-digest": str(inspection["sourceDigest"]),
457
+ },
458
+ )
459
+ self.backend.consume_project(inspection_token)
460
+ return cast(AgentDraft, created)
461
+
462
+ async def compose_conversation(
463
+ self,
464
+ *,
465
+ messages: list[dict[str, str]],
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,
474
+ ) -> dict:
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 "-",
482
+ )
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:
564
+ raise StudioError(
565
+ "AGENT_MODEL_REQUIRED",
566
+ "对话构建需要选择一个 Model Profile",
567
+ status_code=422,
568
+ )
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", "-"),
698
+ )
699
+ return {
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
+ ),
707
+ "requiresConfirmation": True,
708
+ "authoringMode": "chat",
709
+ "usage": response.usage.model_dump(by_alias=True, mode="json"),
710
+ }
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
+
972
+ def _agent_exists(self, agent_id: str) -> bool:
973
+ return bool(
974
+ self.studio.codex_manifests.exists(agent_id)
975
+ or self.studio._draft_exists(agent_id)
976
+ or (self.studio.workspace.resolve("agents") / agent_id).exists()
977
+ )
978
+
979
+ def _allocate_agent_id(self, slug: str | None) -> str:
980
+ if slug and slug.strip():
981
+ if is_generated_agent_slug(slug):
982
+ candidate = slug.strip()
983
+ if self._agent_exists(candidate):
984
+ raise StudioError(
985
+ "AGENT_ALREADY_EXISTS",
986
+ "本地标识已存在,请重新生成",
987
+ status_code=409,
988
+ field="slug",
989
+ details={"id": candidate},
990
+ )
991
+ return candidate
992
+ return self.backend.allocate_agent_id(slug)
993
+ return generate_agent_slug(self._agent_exists)
994
+
995
+ def _commit_codex_import(
996
+ self,
997
+ agent_id: str,
998
+ display_name: str,
999
+ manifest: CodexAgentManifest,
1000
+ *,
1001
+ resolved_slug: str,
1002
+ ) -> AgentDraft:
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
+ }
1012
+ )
1013
+ self.studio.codex_drafts.save(draft)
1014
+ return cast(AgentDraft, self.studio.codex_agents._project(snapshot, current=draft))
1015
+
1016
+
1017
+ __all__ = ["StudioAuthoringCoordinator"]