agentengine-sdk-python 0.8.1__py3-none-any.whl → 0.8.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (476) hide show
  1. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.3.dist-info}/METADATA +25 -12
  2. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.3.dist-info}/RECORD +421 -232
  3. ksadk/_build_provenance.json +1 -0
  4. ksadk/a2a/_space_client_events.py +381 -0
  5. ksadk/a2a/event_adapter.py +156 -64
  6. ksadk/a2a/executor.py +179 -63
  7. ksadk/a2a/space_client.py +6 -333
  8. ksadk/a2ui/core.py +222 -53
  9. ksadk/agui/_agent_helpers.py +247 -0
  10. ksadk/agui/a2ui_projection.py +12 -5
  11. ksadk/agui/agent.py +443 -259
  12. ksadk/api/client.py +528 -27
  13. ksadk/builders/code_builder.py +284 -8
  14. ksadk/builders/container_builder.py +8 -1
  15. ksadk/builders/managed_runtime_builder.py +42 -35
  16. ksadk/cli/__init__.py +23 -0
  17. ksadk/cli/cmd_dashboard.py +15 -1
  18. ksadk/cli/cmd_deploy.py +10 -2
  19. ksadk/cli/cmd_eval.py +66 -9
  20. ksadk/cli/cmd_evalset.py +284 -0
  21. ksadk/cli/cmd_files.py +5 -1
  22. ksadk/cli/cmd_hermes.py +1 -126
  23. ksadk/cli/cmd_invoke.py +28 -38
  24. ksadk/cli/cmd_managed_runtime.py +95 -0
  25. ksadk/cli/cmd_observe.py +85 -0
  26. ksadk/cli/cmd_openclaw.py +1 -435
  27. ksadk/cli/cmd_plugin.py +657 -0
  28. ksadk/cli/cmd_replay.py +28 -4
  29. ksadk/cli/cmd_studio.py +99 -8
  30. ksadk/cli/cmd_web.py +10 -2
  31. ksadk/cli/hermes_env.py +151 -0
  32. ksadk/cli/invoke_payload.py +49 -0
  33. ksadk/cli/openclaw_env.py +458 -0
  34. ksadk/codex/client.py +180 -30
  35. ksadk/codex/runtime.py +475 -331
  36. ksadk/configs/env_registry.py +94 -12
  37. ksadk/configs/env_registry_pcm.py +181 -0
  38. ksadk/configs/env_var_spec.py +18 -0
  39. ksadk/configs/global_config.py +5 -0
  40. ksadk/configs/settings.py +9 -0
  41. ksadk/context_engine/__init__.py +98 -0
  42. ksadk/context_engine/assembler.py +176 -0
  43. ksadk/context_engine/baseline.py +419 -0
  44. ksadk/context_engine/cache_observability.py +228 -0
  45. ksadk/context_engine/capabilities.py +443 -0
  46. ksadk/context_engine/contributors.py +409 -0
  47. ksadk/context_engine/hosted_pipeline.py +417 -0
  48. ksadk/context_engine/models.py +117 -0
  49. ksadk/context_engine/planner.py +698 -0
  50. ksadk/context_engine/policies.py +319 -0
  51. ksadk/context_engine/projection.py +31 -0
  52. ksadk/context_engine/shadow_plan.py +266 -0
  53. ksadk/context_engine/tokenizer.py +164 -0
  54. ksadk/conversations/__init__.py +19 -0
  55. ksadk/conversations/context.py +230 -30
  56. ksadk/conversations/contracts.py +266 -0
  57. ksadk/conversations/message_projection.py +308 -26
  58. ksadk/conversations/model_context.py +49 -3
  59. ksadk/conversations/projector.py +616 -0
  60. ksadk/conversations/reducer.py +98 -0
  61. ksadk/conversations/runtime_compaction.py +207 -5
  62. ksadk/conversations/runtime_input.py +184 -2
  63. ksadk/conversations/runtime_invocation.py +81 -3
  64. ksadk/conversations/runtime_observability.py +141 -0
  65. ksadk/conversations/runtime_payloads.py +42 -0
  66. ksadk/conversations/runtime_preparation.py +272 -2
  67. ksadk/conversations/runtime_resume.py +20 -1
  68. ksadk/conversations/runtime_stream_events.py +94 -9
  69. ksadk/conversations/semantic_summary.py +311 -1
  70. ksadk/conversations/session_lock.py +51 -0
  71. ksadk/deployment/env_forward.py +72 -0
  72. ksadk/deployment/managed_runtime.py +5 -0
  73. ksadk/deployment/providers/serverless.py +143 -69
  74. ksadk/evaluation/__init__.py +32 -0
  75. ksadk/evaluation/a2a_adapter.py +9 -1
  76. ksadk/evaluation/adapters.py +18 -5
  77. ksadk/evaluation/agent_eval_client.py +395 -0
  78. ksadk/evaluation/cloud_binding.py +80 -0
  79. ksadk/evaluation/cloud_converter.py +194 -0
  80. ksadk/evaluation/cloud_service.py +199 -0
  81. ksadk/evaluation/contracts.py +73 -4
  82. ksadk/evaluation/evaluators.py +183 -17
  83. ksadk/evaluation/evidence.py +258 -0
  84. ksadk/evaluation/executor.py +31 -6
  85. ksadk/evaluation/local_adapter.py +550 -0
  86. ksadk/evaluation/service_env.py +27 -0
  87. ksadk/evaluation/studio_build_adapter.py +253 -0
  88. ksadk/evaluation/target.py +15 -6
  89. ksadk/events/__init__.py +13 -13
  90. ksadk/events/_v1_compat/__init__.py +1 -0
  91. ksadk/events/_v1_compat/models.py +299 -0
  92. ksadk/events/_v1_compat/parser.py +247 -0
  93. ksadk/events/_v1_compat/projection.py +755 -0
  94. ksadk/events/adapters/__init__.py +34 -0
  95. ksadk/events/adapters/_a2a_snapshot.py +622 -0
  96. ksadk/events/adapters/_a2a_support.py +275 -0
  97. ksadk/events/adapters/_codex_interactions.py +375 -0
  98. ksadk/events/adapters/_codex_items.py +801 -0
  99. ksadk/events/adapters/_codex_validators.py +311 -0
  100. ksadk/events/adapters/_langgraph_support.py +785 -0
  101. ksadk/events/adapters/a2a.py +841 -0
  102. ksadk/events/adapters/adk.py +503 -0
  103. ksadk/events/adapters/codex.py +907 -0
  104. ksadk/events/adapters/langgraph.py +745 -0
  105. ksadk/events/canonical.py +440 -0
  106. ksadk/events/canonical_replay.py +497 -0
  107. ksadk/events/canonical_store.py +468 -0
  108. ksadk/events/cold_recovery.py +257 -0
  109. ksadk/events/content.py +89 -0
  110. ksadk/events/identity.py +86 -0
  111. ksadk/events/pipeline.py +455 -0
  112. ksadk/events/projections.py +66 -0
  113. ksadk/events/reducer.py +661 -0
  114. ksadk/events/replay.py +3 -36
  115. ksadk/events/runtime_event.py +20 -265
  116. ksadk/events/session_event.py +324 -0
  117. ksadk/events/store.py +3 -316
  118. ksadk/events/v1_compat.py +41 -0
  119. ksadk/harness/__init__.py +45 -35
  120. ksadk/harness/runtime.py +220 -58
  121. ksadk/identity/resolver.py +4 -3
  122. ksadk/interaction/__init__.py +28 -0
  123. ksadk/interaction/contracts.py +136 -0
  124. ksadk/interaction/ledger.py +189 -0
  125. ksadk/interaction/provider.py +122 -0
  126. ksadk/interaction/providers/__init__.py +52 -0
  127. ksadk/interaction/providers/adk.py +40 -0
  128. ksadk/interaction/providers/codex.py +94 -0
  129. ksadk/interaction/providers/langgraph.py +58 -0
  130. ksadk/kernel/__init__.py +117 -0
  131. ksadk/kernel/authorization.py +258 -0
  132. ksadk/kernel/bootstrap.py +1074 -0
  133. ksadk/kernel/contract_fingerprints.py +62 -0
  134. ksadk/kernel/contracts.py +370 -0
  135. ksadk/kernel/control.py +220 -0
  136. ksadk/kernel/errors.py +79 -0
  137. ksadk/kernel/ingress.py +1123 -0
  138. ksadk/kernel/mapping.py +111 -0
  139. ksadk/kernel/memory_store.py +1134 -0
  140. ksadk/kernel/postgres_store.py +1757 -0
  141. ksadk/kernel/recovery.py +452 -0
  142. ksadk/kernel/runtime_identity.py +103 -0
  143. ksadk/kernel/sql/001_agent_kernel.sql +120 -0
  144. ksadk/kernel/sqlite_store.py +1411 -0
  145. ksadk/kernel/state.py +145 -0
  146. ksadk/kernel/store.py +232 -0
  147. ksadk/kernel/worker.py +964 -0
  148. ksadk/knowledge_base/client.py +7 -0
  149. ksadk/knowledge_base/service.py +32 -5
  150. ksadk/memory/__init__.py +61 -0
  151. ksadk/memory/adk/backends/base_ltm_backend.py +96 -1
  152. ksadk/memory/adk/backends/http_ltm_backend.py +14 -5
  153. ksadk/memory/adk/backends/inmemory_ltm_backend.py +136 -3
  154. ksadk/memory/adk/backends/sdk_ltm_backend.py +384 -2
  155. ksadk/memory/adk/backends/sqlite_ltm_backend.py +95 -0
  156. ksadk/memory/coordinator.py +461 -0
  157. ksadk/memory/events.py +202 -0
  158. ksadk/memory/extraction.py +228 -0
  159. ksadk/memory/ltm_backend_factory.py +16 -3
  160. ksadk/memory/models.py +316 -0
  161. ksadk/memory/policy.py +215 -0
  162. ksadk/memory/provider.py +47 -0
  163. ksadk/memory/provider_adapter.py +159 -0
  164. ksadk/memory/provider_resolver.py +71 -0
  165. ksadk/memory/providers/__init__.py +5 -0
  166. ksadk/memory/providers/local_sqlite.py +497 -0
  167. ksadk/memory/resolved_policy.py +145 -0
  168. ksadk/memory/service.py +188 -6
  169. ksadk/model_proxy/bootstrap.py +16 -9
  170. ksadk/model_proxy/detect.py +202 -9
  171. ksadk/model_proxy/namespace.py +39 -15
  172. ksadk/model_proxy/server.py +24 -5
  173. ksadk/model_proxy/transform.py +60 -2
  174. ksadk/observability/__init__.py +25 -0
  175. ksadk/observability/session_log.py +420 -0
  176. ksadk/observability/trajectory.py +105 -0
  177. ksadk/plugins/__init__.py +167 -0
  178. ksadk/plugins/bridges/__init__.py +45 -0
  179. ksadk/plugins/bridges/codex.py +552 -0
  180. ksadk/plugins/bridges/dsh.py +1116 -0
  181. ksadk/plugins/builtins.py +747 -0
  182. ksadk/plugins/bundle.py +246 -0
  183. ksadk/plugins/bundle_security.py +167 -0
  184. ksadk/plugins/composition.py +529 -0
  185. ksadk/plugins/context_contributor.py +437 -0
  186. ksadk/plugins/contracts.py +451 -0
  187. ksadk/plugins/dsh_toolchain.py +935 -0
  188. ksadk/plugins/ecosystem_bridge.py +1105 -0
  189. ksadk/plugins/ecosystem_probe.py +191 -0
  190. ksadk/plugins/host.py +724 -0
  191. ksadk/plugins/providers/__init__.py +85 -0
  192. ksadk/plugins/providers/bundles/ksadk-codex/cordis.patch.yml +3 -0
  193. ksadk/plugins/providers/bundles/ksadk-codex/index.mjs +18 -0
  194. ksadk/plugins/providers/bundles/ksadk-codex/package.json +19 -0
  195. ksadk/plugins/providers/bundles/ksadk-harness/cordis.patch.yml +3 -0
  196. ksadk/plugins/providers/bundles/ksadk-harness/index.mjs +19 -0
  197. ksadk/plugins/providers/bundles/ksadk-harness/package.json +19 -0
  198. ksadk/plugins/providers/codex.py +722 -0
  199. ksadk/plugins/providers/codex_dsh.py +83 -0
  200. ksadk/plugins/providers/dsh.py +951 -0
  201. ksadk/plugins/providers/dsh_descriptor_host.py +226 -0
  202. ksadk/plugins/providers/harness.py +627 -0
  203. ksadk/plugins/providers/harness_dsh.py +91 -0
  204. ksadk/plugins/providers/legacy.py +215 -0
  205. ksadk/plugins/providers/legacy_catalog.py +100 -0
  206. ksadk/plugins/providers/shipped_dsh.py +370 -0
  207. ksadk/plugins/resolver.py +331 -0
  208. ksadk/plugins/subagent_providers/__init__.py +12 -0
  209. ksadk/plugins/subagent_providers/codex.py +443 -0
  210. ksadk/plugins/subagents.py +333 -0
  211. ksadk/prompts/__init__.py +71 -0
  212. ksadk/prompts/compiler.py +215 -0
  213. ksadk/prompts/models.py +82 -0
  214. ksadk/prompts/projection.py +89 -0
  215. ksadk/prompts/resolved.py +137 -0
  216. ksadk/prompts/sources.py +233 -0
  217. ksadk/runners/_langgraph_runner_streams.py +822 -0
  218. ksadk/runners/adk_runner.py +5 -5
  219. ksadk/runners/base_runner.py +16 -0
  220. ksadk/runners/factory.py +1 -2
  221. ksadk/runners/langgraph_runner.py +198 -437
  222. ksadk/runtime/_runner_adapter/__init__.py +1 -0
  223. ksadk/runtime/_runner_adapter/stream_mapping.py +797 -0
  224. ksadk/runtime/adapter.py +119 -2
  225. ksadk/runtime/conversation_execution.py +316 -182
  226. ksadk/runtime/executor.py +144 -3
  227. ksadk/runtime/factory.py +268 -8
  228. ksadk/runtime/framework_adapters.py +18 -0
  229. ksadk/runtime/hosted_finalizer.py +268 -0
  230. ksadk/runtime/launch.py +14 -1
  231. ksadk/runtime/preprocessing.py +25 -3
  232. ksadk/runtime/runner_adapter.py +293 -508
  233. ksadk/runtime/usage.py +33 -0
  234. ksadk/sandbox/__init__.py +2 -1
  235. ksadk/sandbox/backends/e2b.py +35 -2
  236. ksadk/sandbox/factory.py +72 -0
  237. ksadk/scheduler/__init__.py +18 -0
  238. ksadk/scheduler/calendar.py +102 -0
  239. ksadk/scheduler/contracts.py +254 -0
  240. ksadk/scheduler/dispatcher.py +129 -0
  241. ksadk/scheduler/engine.py +416 -0
  242. ksadk/scheduler/sqlite_store.py +563 -0
  243. ksadk/server/composition.py +8 -0
  244. ksadk/server/factory.py +69 -6
  245. ksadk/server/routes/kernel_ingress.py +258 -0
  246. ksadk/server/routes/openai_compat.py +88 -8
  247. ksadk/server/routes/projection.py +35 -20
  248. ksadk/server/routes/run.py +94 -0
  249. ksadk/server/routes/sessions.py +2 -0
  250. ksadk/server/routes/streaming.py +5 -2
  251. ksadk/server/static/assets/{ArtifactsPanel-CakCqxEN.js → ArtifactsPanel-10GPXIJ6.js} +1 -1
  252. ksadk/server/static/assets/{CodeBlock-CynLknyO.js → CodeBlock-BXk1l-G1.js} +4 -4
  253. ksadk/server/static/assets/MathMessageMarkdown-BorAY7qD.css +1 -0
  254. ksadk/server/static/assets/{MathMessageMarkdown-3sQdOysS.js → MathMessageMarkdown-D9uB55Oh.js} +4 -4
  255. ksadk/server/static/assets/MermaidBlock--OEYoXIJ.js +312 -0
  256. ksadk/server/static/assets/NativeTerminalPanel-DrEe1syq.js +1 -0
  257. ksadk/server/static/assets/abnfDiagram-VCTEODGH-eeTFJLvo.js +1 -0
  258. ksadk/server/static/assets/arc-S3ZNbIAd.js +1 -0
  259. ksadk/server/static/assets/architecture-7GRP2DOG-Dk9nRQ6-.js +1 -0
  260. ksadk/server/static/assets/architectureDiagram-5GKGNRK7-CRjgEJDX.js +36 -0
  261. ksadk/server/static/assets/blockDiagram-NRAW4CY4-C7bsBoNH.js +129 -0
  262. ksadk/server/static/assets/c4Diagram-UCG6FXSJ-BX3c33Ob.js +38 -0
  263. ksadk/server/static/assets/channel-s354Yo6o.js +1 -0
  264. ksadk/server/static/assets/chunk-2Q5K7J3B-CDpPpKR5.js +1 -0
  265. ksadk/server/static/assets/{chunk-FMBD7UC4-B_fH2YuH.js → chunk-5VM5RSS4-bZ7ws-8m.js} +1 -1
  266. ksadk/server/static/assets/{chunk-ND2GUHAM-D6rFLaVV.js → chunk-F27PBJKO-D2r0pvhY.js} +1 -1
  267. ksadk/server/static/assets/chunk-G27WJ6UU-CZwF3oUP.js +231 -0
  268. ksadk/server/static/assets/{chunk-4BX2VUAB-B9drTgOW.js → chunk-JWPE2WC7-DigFYCML.js} +1 -1
  269. ksadk/server/static/assets/chunk-LCL6LL3I-CnorGV5q.js +206 -0
  270. ksadk/server/static/assets/chunk-POPQ4Y6H-CexntQA-.js +1 -0
  271. ksadk/server/static/assets/chunk-RHFEMEQ7-CHTC7UxA.js +168 -0
  272. ksadk/server/static/assets/chunk-SVP7TREG-BC1EWt_-.js +88 -0
  273. ksadk/server/static/assets/chunk-XXDRQBXY-BTG24xN0.js +1 -0
  274. ksadk/server/static/assets/classDiagram-DTDB5LWJ-Dd0nucl9.js +1 -0
  275. ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-Dd0nucl9.js +1 -0
  276. ksadk/server/static/assets/{cose-bilkent-S5V4N54A-ChNs_Mdk.js → cose-bilkent-JH36ORCC-CkonRdAS.js} +1 -1
  277. ksadk/server/static/assets/cynefin-OW5HDTMX-BBlT4uMf.js +1 -0
  278. ksadk/server/static/assets/cynefinDiagram-5FMLGOSQ-DEOCBe15.js +62 -0
  279. ksadk/server/static/assets/cytoscape.esm-CyCl8rPi.js +321 -0
  280. ksadk/server/static/assets/dagre-3AP2YEHR-DBThPuLa.js +4 -0
  281. ksadk/server/static/assets/dagre-ClTPrTht.js +1 -0
  282. ksadk/server/static/assets/diagram-S7CK7UJ4-DsZHrnpJ.js +30 -0
  283. ksadk/server/static/assets/diagram-UQ7AKVKN-BJR4nhVr.js +41 -0
  284. ksadk/server/static/assets/diagram-VSXAHHWV-xzcERX0a.js +3 -0
  285. ksadk/server/static/assets/diagram-VX7I27RA-tn2xM1xu.js +24 -0
  286. ksadk/server/static/assets/diagram-Z3DM3KII-BZPGBkw5.js +24 -0
  287. ksadk/server/static/assets/{dist-gLnrGsoU.js → dist-4zN1_tq5.js} +1 -1
  288. ksadk/server/static/assets/{dist-C1sz5Eo3.js → dist-8VWI-2bZ.js} +1 -1
  289. ksadk/server/static/assets/{dist-BjOdlDjj.js → dist-B-46q3Hk.js} +1 -1
  290. ksadk/server/static/assets/{dist-o2OVmKvt.js → dist-B0gdk4rl.js} +1 -1
  291. ksadk/server/static/assets/{dist-4N2NMVYg.js → dist-B1oWRmrH.js} +1 -1
  292. ksadk/server/static/assets/{dist-CmZHzlwZ.js → dist-BI1qQH4n.js} +1 -1
  293. ksadk/server/static/assets/{dist-X4TnqwOK.js → dist-BJLUwH6W.js} +1 -1
  294. ksadk/server/static/assets/{dist-B5oS1zcj.js → dist-Bbwqa8TG.js} +1 -1
  295. ksadk/server/static/assets/{dist-j6Dxmf_j.js → dist-Btyo4vX4.js} +1 -1
  296. ksadk/server/static/assets/{dist-DQU9EWw-.js → dist-C71CsujO.js} +1 -1
  297. ksadk/server/static/assets/{dist-BIlKoW0k.js → dist-CXpH_Rdd.js} +1 -1
  298. ksadk/server/static/assets/{dist-DJN_6Y39.js → dist-Cq4OL6nO.js} +1 -1
  299. ksadk/server/static/assets/{dist-Dli7qW-B.js → dist-D1V3f1B4.js} +1 -1
  300. ksadk/server/static/assets/{dist-fhOuWds5.js → dist-DJmCPRVB.js} +1 -1
  301. ksadk/server/static/assets/{dist-CWV4lDr1.js → dist-DQAB0qn_.js} +2 -2
  302. ksadk/server/static/assets/{dist-D6MpCZtz.js → dist-DUwKa_l6.js} +1 -1
  303. ksadk/server/static/assets/dist-DdRBIceA.js +1 -0
  304. ksadk/server/static/assets/{dist-DWKByxWV.js → dist-DfGFrb0v.js} +1 -1
  305. ksadk/server/static/assets/{dist-DsHPdk3W.js → dist-DjhwGUgZ.js} +1 -1
  306. ksadk/server/static/assets/{dist-B-jNR6Vn.js → dist-DyqV4fL4.js} +1 -1
  307. ksadk/server/static/assets/{dist-BB3-OwTQ.js → dist-M67Hwu0n.js} +1 -1
  308. ksadk/server/static/assets/{dist-_pHPTyZ-.js → dist-u8tVH5Ak.js} +1 -1
  309. ksadk/server/static/assets/ebnfDiagram-PWID7BFC-DVfMsLge.js +1 -0
  310. ksadk/server/static/assets/erDiagram-SSCWMZ5O-CWEZSYaw.js +99 -0
  311. ksadk/server/static/assets/eventmodeling-NTZA5JFV-Dr5okd8V.js +1 -0
  312. ksadk/server/static/assets/flowDiagram-A5DVABFB-VEO-cVcO.js +1 -0
  313. ksadk/server/static/assets/ganttDiagram-EL5Y4UJY-TWIxoyYL.js +292 -0
  314. ksadk/server/static/assets/gitGraph-4MIJSDKK-CKiKY9Cf.js +1 -0
  315. ksadk/server/static/assets/gitGraphDiagram-WWUBYQGX-DDh2hd2S.js +106 -0
  316. ksadk/server/static/assets/index-B2k_urY8.js +241 -0
  317. ksadk/server/static/assets/index-ILPt6IWF.css +2 -0
  318. ksadk/server/static/assets/info-A6RAGUB7-ubA4Nm_p.js +1 -0
  319. ksadk/server/static/assets/infoDiagram-RXCK75RN-CgoyWiDR.js +2 -0
  320. ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-K0OOfVna.js → ishikawaDiagram-5VMMS53U-CyRIu_om.js} +6 -6
  321. ksadk/server/static/assets/{journeyDiagram-JHISSGLW-8LeOJaZp.js → journeyDiagram-EYS64GPL-DN5JXHzN.js} +6 -6
  322. ksadk/server/static/assets/{kanban-definition-UN3LZRKU-C6uCgZrW.js → kanban-definition-3QL26DDD-CNP3ui66.js} +10 -10
  323. ksadk/server/static/assets/katex-DWEzQy1d.js +257 -0
  324. ksadk/server/static/assets/{linear-hdBcYzM8.js → linear-C-qHp7d2.js} +1 -1
  325. ksadk/server/static/assets/mermaid-parser.core-Cl-K943T.js +166 -0
  326. ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-Bjy_2v4_.js → mindmap-definition-FBJOCRG2-D5DB5mYm.js} +29 -29
  327. ksadk/server/static/assets/packet-AYTQ26CC-C7R3NUlK.js +1 -0
  328. ksadk/server/static/assets/pegDiagram-XKGWAZYB-BQVc9ia_.js +1 -0
  329. ksadk/server/static/assets/pie-WAS4IAKB-BIT6qjr3.js +1 -0
  330. ksadk/server/static/assets/pieDiagram-E7YTZNPT-B6UCAvtY.js +39 -0
  331. ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-BmkRbwCg.js → quadrantDiagram-AXDQQJYC-mIr2d3eb.js} +6 -6
  332. ksadk/server/static/assets/radar-RG4KPBEZ-l5noef9X.js +1 -0
  333. ksadk/server/static/assets/railroad-74A4TZTK-Baea6ZvM.js +1 -0
  334. ksadk/server/static/assets/railroad-abnf-HS5TGJTU-OKn8zZnU.js +1 -0
  335. ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-DdKAKC4l.js +1 -0
  336. ksadk/server/static/assets/railroad-peg-WCYAUIDC-Bds8udlG.js +1 -0
  337. ksadk/server/static/assets/railroadDiagram-O6MQD6OU-Dt3FFDmK.js +1 -0
  338. ksadk/server/static/assets/requirementDiagram-EFPCY7ZU-Cz_vQtgF.js +84 -0
  339. ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-l6iXmU65.js → sankeyDiagram-P5KCCOFB-wlO1TsH5.js} +7 -7
  340. ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-Cq6UBeQB.js → sequenceDiagram-WJ2MYXX4-CHRbElYq.js} +10 -10
  341. ksadk/server/static/assets/sizeCapture-X5ZJPWSS-DXX54iyO.js +1 -0
  342. ksadk/server/static/assets/stateDiagram-HBIQ2CUA-Dp_lw9II.js +1 -0
  343. ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-5b-8o2iS.js +1 -0
  344. ksadk/server/static/assets/swimlanes-XN3QIQJK-D8O5UlLG.js +1 -0
  345. ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-uf6WPRA4.js +8 -0
  346. ksadk/server/static/assets/{timeline-definition-PNZ67QCA-DEz24vV0.js → timeline-definition-24CTP7MA-8mfKcxvG.js} +7 -7
  347. ksadk/server/static/assets/treeView-Q6P3EWNA-CK5CSrSO.js +1 -0
  348. ksadk/server/static/assets/treemap-WGGIJYW6-Ch2BKgAM.js +1 -0
  349. ksadk/server/static/assets/vennDiagram-4TSXK5OY-EnNl0DE7.js +34 -0
  350. ksadk/server/static/assets/wardley-WFR3VGLG-YW3Q0ml_.js +1 -0
  351. ksadk/server/static/assets/wardleyDiagram-VM6X3IG4-DBu5fbYr.js +78 -0
  352. ksadk/server/static/assets/xychartDiagram-S5SC5T6Z-BSVXs4IY.js +7 -0
  353. ksadk/server/static/index.html +2 -2
  354. ksadk/sessions/__init__.py +26 -9
  355. ksadk/sessions/_local_service_sync.py +688 -0
  356. ksadk/sessions/_local_tables.py +21 -0
  357. ksadk/sessions/_postgres_schema.py +305 -0
  358. ksadk/sessions/_postgres_tables.py +15 -0
  359. ksadk/sessions/base.py +80 -1
  360. ksadk/sessions/in_memory.py +53 -2
  361. ksadk/sessions/local_service.py +63 -604
  362. ksadk/sessions/postgres_service.py +58 -148
  363. ksadk/sessions/resilient.py +35 -1
  364. ksadk/skills/service_client.py +48 -0
  365. ksadk/studio/agent_lifecycle.py +26 -20
  366. ksadk/studio/api.py +1063 -69
  367. ksadk/studio/api_catalog_routes.py +5 -0
  368. ksadk/studio/api_contracts.py +148 -6
  369. ksadk/studio/api_memory_routes.py +82 -0
  370. ksadk/studio/api_plugin_routes.py +480 -0
  371. ksadk/studio/attachment_store.py +187 -0
  372. ksadk/studio/authoring.py +259 -28
  373. ksadk/studio/authoring_coordinator.py +809 -67
  374. ksadk/studio/builder.py +197 -40
  375. ksadk/studio/capabilities.py +17 -0
  376. ksadk/studio/cloud.py +1920 -95
  377. ksadk/studio/codex_agent_service.py +137 -15
  378. ksadk/studio/codex_authoring.py +469 -0
  379. ksadk/studio/codex_builder.py +177 -25
  380. ksadk/studio/codex_manifest.py +71 -22
  381. ksadk/studio/codex_run.py +59 -4
  382. ksadk/studio/compatibility_report.py +445 -0
  383. ksadk/studio/compiler.py +7 -1
  384. ksadk/studio/contracts.py +234 -4
  385. ksadk/studio/dsh_provider_registration.py +768 -0
  386. ksadk/studio/event_store.py +79 -101
  387. ksadk/studio/framework_run.py +264 -3
  388. ksadk/studio/hosted_kernel.py +317 -0
  389. ksadk/studio/manifest_resolver.py +131 -0
  390. ksadk/studio/model_client.py +95 -9
  391. ksadk/studio/model_profile_service.py +1 -1
  392. ksadk/studio/operations.py +47 -5
  393. ksadk/studio/otel_trace.py +348 -42
  394. ksadk/studio/pcm_memory.py +85 -0
  395. ksadk/studio/plugin_composition.py +301 -0
  396. ksadk/studio/plugin_kernel_adapter.py +128 -0
  397. ksadk/studio/plugin_runtime.py +652 -0
  398. ksadk/studio/repository.py +23 -2
  399. ksadk/studio/resource_catalog.py +49 -1
  400. ksadk/studio/run_service.py +1266 -202
  401. ksadk/studio/runtime_source.py +51 -17
  402. ksadk/studio/scheduler_runtime.py +374 -0
  403. ksadk/studio/scheduler_service.py +211 -0
  404. ksadk/studio/service.py +1815 -117
  405. ksadk/studio/shared_web.py +199 -50
  406. ksadk/studio/soul.py +52 -0
  407. ksadk/studio/static/assets/index-DXs1Eq4M.css +1 -0
  408. ksadk/studio/static/assets/index-DeDqwTsD.js +283 -0
  409. ksadk/studio/static/index.html +2 -2
  410. ksadk/studio/templates.py +100 -8
  411. ksadk/studio/validator.py +20 -1
  412. ksadk/studio/workspace.py +31 -32
  413. ksadk/tracing/setup.py +304 -3
  414. ksadk/version.py +1 -1
  415. ksadk_runtime_common/schemas/runtime_event_v1.json +37 -0
  416. ksadk_runtime_common/schemas/runtime_event_v2.json +271 -0
  417. ksadk_runtime_common/workspace_files/path_utils.py +41 -0
  418. ksadk/events/parser.py +0 -191
  419. ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +0 -1
  420. ksadk/server/static/assets/MermaidBlock-NERD_ef7.js +0 -303
  421. ksadk/server/static/assets/NativeTerminalPanel-CiHWf82q.js +0 -1
  422. ksadk/server/static/assets/arc-DrgjcKhl.js +0 -1
  423. ksadk/server/static/assets/architecture-7EHR7CIX-CpZLRCcY.js +0 -1
  424. ksadk/server/static/assets/architectureDiagram-3BPJPVTR-BXoVRbQ6.js +0 -36
  425. ksadk/server/static/assets/blockDiagram-GPEHLZMM-BnaB3Ywv.js +0 -132
  426. ksadk/server/static/assets/c4Diagram-AAUBKEIU-Dw4weZcp.js +0 -10
  427. ksadk/server/static/assets/channel-Dt3rQTFI.js +0 -1
  428. ksadk/server/static/assets/chunk-2J33WTMH-ChjpLK7j.js +0 -1
  429. ksadk/server/static/assets/chunk-55IACEB6-CK4ROATy.js +0 -1
  430. ksadk/server/static/assets/chunk-727SXJPM-B9KtTTgw.js +0 -206
  431. ksadk/server/static/assets/chunk-AQP2D5EJ-1O1mki3m.js +0 -231
  432. ksadk/server/static/assets/chunk-QZHKN3VN-iXEZtKQc.js +0 -1
  433. ksadk/server/static/assets/classDiagram-4FO5ZUOK-DIveV17Q.js +0 -1
  434. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-DIveV17Q.js +0 -1
  435. ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +0 -321
  436. ksadk/server/static/assets/dagre-BM42HDAG-D0QXg_MQ.js +0 -4
  437. ksadk/server/static/assets/dagre-Bx709z4p.js +0 -1
  438. ksadk/server/static/assets/diagram-2AECGRRQ-DBDDKwBQ.js +0 -43
  439. ksadk/server/static/assets/diagram-5GNKFQAL-BkwDOxmO.js +0 -10
  440. ksadk/server/static/assets/diagram-KO2AKTUF-Bv8waYvY.js +0 -3
  441. ksadk/server/static/assets/diagram-LMA3HP47-e1VECDdu.js +0 -24
  442. ksadk/server/static/assets/diagram-OG6HWLK6-Ds9adfRl.js +0 -24
  443. ksadk/server/static/assets/dist-B5J2uhNk.js +0 -1
  444. ksadk/server/static/assets/erDiagram-TEJ5UH35-BPItFKH6.js +0 -85
  445. ksadk/server/static/assets/eventmodeling-FCH6USID-DYFSxbqr.js +0 -1
  446. ksadk/server/static/assets/flowDiagram-I6XJVG4X-BbYB9w5Y.js +0 -162
  447. ksadk/server/static/assets/ganttDiagram-6RSMTGT7-TQXa0KXv.js +0 -292
  448. ksadk/server/static/assets/gitGraph-WXDBUCRP-DyNYp6BN.js +0 -1
  449. ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-bKblCnpa.js +0 -106
  450. ksadk/server/static/assets/graphlib-B8gBHxth.js +0 -1
  451. ksadk/server/static/assets/index-R52pJJvg.js +0 -177
  452. ksadk/server/static/assets/index-jJr3_955.css +0 -2
  453. ksadk/server/static/assets/info-J43DQDTF-WUx7qTde.js +0 -1
  454. ksadk/server/static/assets/infoDiagram-5YYISTIA-BGVT7J_i.js +0 -2
  455. ksadk/server/static/assets/katex-CYWseY5E.js +0 -265
  456. ksadk/server/static/assets/mermaid-parser.core-BWrDjSZM.js +0 -161
  457. ksadk/server/static/assets/packet-YPE3B663-BN13y_xl.js +0 -1
  458. ksadk/server/static/assets/pie-LRSECV5Y-ftSLXhhc.js +0 -1
  459. ksadk/server/static/assets/pieDiagram-4H26LBE5-GmmqNiVH.js +0 -30
  460. ksadk/server/static/assets/radar-GUYGQ44K-jaICkIeO.js +0 -1
  461. ksadk/server/static/assets/requirementDiagram-4Y6WPE33-DSWFqL7c.js +0 -84
  462. ksadk/server/static/assets/stateDiagram-AJRCARHV-qzTQpg3Y.js +0 -1
  463. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-BYCoYGAN.js +0 -1
  464. ksadk/server/static/assets/treeView-BLDUP644-Cpib_vlz.js +0 -1
  465. ksadk/server/static/assets/treemap-LRROVOQU-DQxfmVHK.js +0 -1
  466. ksadk/server/static/assets/vennDiagram-CIIHVFJN-CMCixki2.js +0 -34
  467. ksadk/server/static/assets/wardley-L42UT6IY-BQYHnnkU.js +0 -1
  468. ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-CVzB8F9K.js +0 -78
  469. ksadk/server/static/assets/xychartDiagram-2RQKCTM6-BFBqBLc1.js +0 -7
  470. ksadk/studio/evaluation.py +0 -192
  471. ksadk/studio/static/assets/index-CaEN0eTR.css +0 -1
  472. ksadk/studio/static/assets/index-DI2eY_NH.js +0 -252
  473. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.3.dist-info}/WHEEL +0 -0
  474. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.3.dist-info}/entry_points.txt +0 -0
  475. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.3.dist-info}/licenses/LICENSE +0 -0
  476. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.3.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,40 @@ 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
+ proposed_runtime = resolved.runtime
282
+ if proposed_runtime is not None and proposed_runtime.type != runtime_type:
283
+ raise StudioError(
284
+ "AGENT_RUNTIME_MISMATCH",
285
+ "AgentSpec Runtime 与创建方式不一致",
286
+ status_code=422,
287
+ field="runtimeType",
288
+ details={
289
+ "runtimeType": runtime_type,
290
+ "specRuntimeType": proposed_runtime.type,
291
+ },
292
+ )
293
+ if runtime_type == "plugin":
294
+ if proposed_runtime is None or proposed_runtime.provider_ref is None:
295
+ raise StudioError(
296
+ "AGENT_PROVIDER_REFERENCE_REQUIRED",
297
+ "外部 AgentProvider 必须选择一个已安装的精确版本",
298
+ status_code=422,
299
+ field="spec.runtime.providerRef",
300
+ )
301
+ canonical_runtime = proposed_runtime.model_copy(deep=True)
302
+ else:
303
+ canonical_runtime = self.backend.runtime_ref(agent_id, runtime_type)
304
+ if proposed_runtime is not None and runtime_type != "plugin":
305
+ if runtime_type == "codex" and proposed_runtime.version:
306
+ canonical_runtime.version = proposed_runtime.version
307
+ elif runtime_type in {"adk", "langgraph"}:
308
+ canonical_runtime.entry_point = (
309
+ proposed_runtime.entry_point or canonical_runtime.entry_point
310
+ )
311
+ canonical_runtime.agent_variable = proposed_runtime.agent_variable
312
+ canonical_runtime.version = proposed_runtime.version
313
+ canonical_runtime.detection = proposed_runtime.detection
314
+ resolved.runtime = canonical_runtime
51
315
  if description:
52
316
  resolved.description = description
53
317
  draft = self.studio.create_studio_agent(
@@ -94,7 +358,15 @@ class StudioAuthoringCoordinator:
94
358
  status_code=422,
95
359
  )
96
360
  if spec.runtime.type in {"adk", "langgraph"}:
97
- spec.runtime = self.backend.runtime_ref(agent_id, spec.runtime.type)
361
+ imported_runtime = spec.runtime
362
+ canonical_runtime = self.backend.runtime_ref(agent_id, imported_runtime.type)
363
+ canonical_runtime.entry_point = (
364
+ imported_runtime.entry_point or canonical_runtime.entry_point
365
+ )
366
+ canonical_runtime.agent_variable = imported_runtime.agent_variable
367
+ canonical_runtime.version = imported_runtime.version
368
+ canonical_runtime.detection = imported_runtime.detection
369
+ spec.runtime = canonical_runtime
98
370
  created = self.studio.create_agent(
99
371
  agent_id=agent_id,
100
372
  name=display_name,
@@ -123,7 +395,24 @@ class StudioAuthoringCoordinator:
123
395
  return cast(AgentDraft, self.studio.drafts.get(agent_id))
124
396
 
125
397
  def inspect_project(self, project_path: str) -> dict:
126
- return self.backend.inspect_project(project_path)
398
+ inspection = self.backend.inspect_project(project_path)
399
+ spec, unresolved = self._project_agent_spec(
400
+ inspection,
401
+ agent_id="agentkit-preview",
402
+ model_profile_id=None,
403
+ )
404
+ return {
405
+ **inspection,
406
+ "agentSpec": spec.model_dump(by_alias=True, exclude_none=True, mode="json"),
407
+ "bindingProjection": {
408
+ "preserved": spec.bindings.model_dump(
409
+ by_alias=True,
410
+ exclude_none=True,
411
+ mode="json",
412
+ ),
413
+ "unresolved": unresolved,
414
+ },
415
+ }
127
416
 
128
417
  def commit_project(
129
418
  self,
@@ -155,30 +444,22 @@ class StudioAuthoringCoordinator:
155
444
 
156
445
  agent_id = self._allocate_agent_id(resolved_slug)
157
446
  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",
447
+ spec, unresolved = self._project_agent_spec(
448
+ inspection,
449
+ agent_id=agent_id,
450
+ model_profile_id=model_profile_id,
173
451
  )
452
+ if unresolved:
453
+ raise StudioError(
454
+ "PROJECT_BINDINGS_UNRESOLVED",
455
+ "项目中的 Tool、MCP 或 Skill 绑定无法无损映射,请先安装或修正对应资源",
456
+ status_code=422,
457
+ details={"unresolved": unresolved},
458
+ )
174
459
  created = self.studio.create_agent(
175
460
  agent_id=agent_id,
176
461
  name=display_name,
177
- spec=AgentSpec(
178
- runtime=runtime,
179
- instructions=Instructions(system=prompt),
180
- bindings=AgentBindings(model_profile_id=model_profile_id),
181
- ),
462
+ spec=spec,
182
463
  labels={
183
464
  "agentkit.ksyun.com/slug": self.backend.normalize_slug(resolved_slug),
184
465
  "agentkit.ksyun.com/source": "project-detection",
@@ -193,32 +474,511 @@ class StudioAuthoringCoordinator:
193
474
  *,
194
475
  messages: list[dict[str, str]],
195
476
  model_profile_id: str,
477
+ runtime_type: str = "codex",
478
+ agent_model_profile_ids: list[str] | None = None,
479
+ agent_default_model_profile_id: str | None = None,
480
+ tool_resource_ids: list[str] | None = None,
481
+ mcp_resource_ids: list[str] | None = None,
482
+ skill_resource_ids: list[str] | None = None,
483
+ request_id: str | None = None,
484
+ ) -> dict:
485
+ started = time.monotonic()
486
+ self._record_conversation_stage(request_id, "resolving_model")
487
+ LOGGER.info(
488
+ "conversation authoring started: modelProfileId=%s messages=%d requestId=%s",
489
+ model_profile_id,
490
+ len(messages),
491
+ request_id or "-",
492
+ )
493
+ try:
494
+ result = await self._compose_conversation_inner(
495
+ messages=messages,
496
+ model_profile_id=model_profile_id,
497
+ runtime_type=runtime_type,
498
+ agent_model_profile_ids=agent_model_profile_ids or [],
499
+ agent_default_model_profile_id=agent_default_model_profile_id,
500
+ tool_resource_ids=tool_resource_ids or [],
501
+ mcp_resource_ids=mcp_resource_ids or [],
502
+ skill_resource_ids=skill_resource_ids or [],
503
+ request_id=request_id,
504
+ started=started,
505
+ )
506
+ except Exception as exc:
507
+ self._record_conversation_stage(request_id, "failed", detail=str(exc))
508
+ LOGGER.warning(
509
+ "conversation authoring failed after %.2fs: modelProfileId=%s reason=%s",
510
+ time.monotonic() - started,
511
+ model_profile_id,
512
+ exc,
513
+ )
514
+ raise
515
+ return result
516
+
517
+ async def _compose_conversation_inner(
518
+ self,
519
+ *,
520
+ messages: list[dict[str, str]],
521
+ model_profile_id: str,
522
+ runtime_type: str,
523
+ agent_model_profile_ids: list[str],
524
+ agent_default_model_profile_id: str | None,
525
+ tool_resource_ids: list[str],
526
+ mcp_resource_ids: list[str],
527
+ skill_resource_ids: list[str],
528
+ request_id: str | None,
529
+ started: float,
196
530
  ) -> dict:
197
- model_spec = self.studio.catalog.resolve_model(
198
- AgentBindings(model_profile_id=model_profile_id)
531
+ # The authoring model is a one-off control-plane choice. The Agent's
532
+ # model allow-list is a separate deploy-time contract and can contain
533
+ # multiple profiles. Keep backwards compatibility for API callers
534
+ # that only send ``modelProfileId`` by using it as the one Agent model.
535
+ selected_agent_model_ids = [
536
+ binding.resource_id
537
+ for binding in _deduplicated_bindings(agent_model_profile_ids or [model_profile_id])
538
+ ]
539
+ if not selected_agent_model_ids:
540
+ raise StudioError(
541
+ "AGENT_MODEL_REQUIRED",
542
+ "Agent 至少需要选择一个可用 Model Profile",
543
+ status_code=422,
544
+ )
545
+ default_agent_model_id = str(
546
+ agent_default_model_profile_id or selected_agent_model_ids[0]
547
+ ).strip()
548
+ if default_agent_model_id not in selected_agent_model_ids:
549
+ raise StudioError(
550
+ "AGENT_DEFAULT_MODEL_NOT_SELECTED",
551
+ "Agent 默认模型必须包含在可用模型列表中",
552
+ status_code=422,
553
+ details={
554
+ "defaultModelProfileId": default_agent_model_id,
555
+ "modelProfileIds": selected_agent_model_ids,
556
+ },
557
+ )
558
+ authoring_bindings = AgentBindings(
559
+ model_profile_id=model_profile_id,
560
+ model_profile_ids=[model_profile_id],
561
+ )
562
+ bindings = AgentBindings(
563
+ model_profile_id=default_agent_model_id,
564
+ model_profile_ids=selected_agent_model_ids,
565
+ tools=_deduplicated_bindings(tool_resource_ids),
566
+ mcp_servers=_deduplicated_bindings(mcp_resource_ids),
567
+ skills=_deduplicated_bindings(skill_resource_ids),
199
568
  )
200
- if model_spec is None:
569
+ # Resolve every selected resource now. This validates kind, readiness
570
+ # and capability contracts before an LLM response can be displayed as a
571
+ # deployable Draft Patch.
572
+ authoring_model_spec = self.studio.catalog.resolve_model(authoring_bindings)
573
+ if authoring_model_spec is None:
201
574
  raise StudioError(
202
575
  "AGENT_MODEL_REQUIRED",
203
576
  "对话构建需要选择一个 Model Profile",
204
577
  status_code=422,
205
578
  )
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,
579
+ # Resolve every Agent model before returning a Draft Patch. This
580
+ # catches a deleted, wrong-kind or malformed secondary profile rather
581
+ # than accepting a non-deployable multi-model Agent in the browser.
582
+ self.studio.catalog.resolve_models(bindings)
583
+ model_spec = self.studio.catalog.resolve_model(bindings)
584
+ if model_spec is None: # defensive: bindings above requires a default
585
+ raise StudioError(
586
+ "AGENT_MODEL_REQUIRED",
587
+ "Agent 需要选择一个默认 Model Profile",
588
+ status_code=422,
589
+ )
590
+ self.studio.catalog.policy_preview(bindings)
591
+ self.studio.catalog.resolve_mcp_servers(bindings)
592
+ self.studio.catalog.resolve_skills(bindings)
593
+ model = self.studio.catalog.resolver.resolve_model(authoring_model_spec)
594
+ # authoring 自身的 max_tokens 跟随 profile:未配置则 payload 不带该字段
595
+ # (服务端默认);finishReason=length 截断由 model_client 一次性扩容重试兜底。
596
+ LOGGER.info(
597
+ "conversation authoring model resolved: model=%s endpoint=%s",
598
+ getattr(model, "model", "-"),
599
+ getattr(model, "endpoint_url", "-"),
600
+ )
601
+ normalized_runtime_type = str(runtime_type or "").strip().lower()
602
+ if normalized_runtime_type not in {"codex", "adk", "langgraph"}:
603
+ raise StudioError(
604
+ "AGENT_RUNTIME_INVALID",
605
+ "对话构建 Runtime 仅支持 Codex、ADK 或 LangGraph",
606
+ status_code=422,
607
+ field="runtimeType",
608
+ )
609
+ normalized_messages = self.backend.conversation_messages(
610
+ messages,
611
+ runtime_type=normalized_runtime_type,
612
+ )
613
+ previous_proposal = None
614
+ for item in reversed(messages):
615
+ if str(item.get("role") or "").strip() != "assistant":
616
+ continue
617
+ try:
618
+ previous_proposal = self.backend.parse_conversation_proposal(
619
+ str(item.get("content") or ""),
620
+ runtime_type=normalized_runtime_type,
621
+ )
622
+ except StudioError:
623
+ continue
624
+ break
625
+
626
+ if self._use_codex_authoring():
627
+ codex_result = await self._compose_conversation_codex(
628
+ messages=messages,
629
+ model=model,
630
+ previous_proposal=previous_proposal,
631
+ request_id=request_id,
632
+ started=started,
633
+ model_profile_id=model_profile_id,
634
+ model_spec=model_spec,
635
+ bindings=bindings,
636
+ )
637
+ if codex_result is not None:
638
+ return codex_result
639
+
640
+ request_options = {
641
+ "network_policy": self.backend.authoring_network_policy(model.endpoint_url),
642
+ # The authoring output is deliberately small. Keep a bounded
643
+ # request budget so an unavailable upstream cannot make a simple
644
+ # create flow appear to hang for minutes.
645
+ "timeout_seconds": 20,
646
+ "backoff_seconds": 1,
647
+ "response_format": {"type": "json_object"},
648
+ # finishReason=length 截断时在 model_client 内自动扩容 max_tokens
649
+ # 重发一次(一次性),避免大 JSON 被截断成空响应。
650
+ "retry_on_length": True,
651
+ }
652
+ self._record_conversation_stage(request_id, "generating")
653
+ try:
654
+ response = await self.studio.model_client.complete(
655
+ model,
656
+ messages=normalized_messages,
657
+ max_attempts=2,
658
+ **request_options,
659
+ )
660
+ except StudioError as exc:
661
+ if exc.code not in _LOCAL_FALLBACK_MODEL_ERRORS:
662
+ raise
663
+ LOGGER.warning(
664
+ "conversation authoring model unavailable; returning local fallback: "
665
+ "modelProfileId=%s reason=%s",
666
+ model_profile_id,
667
+ exc.code,
668
+ )
669
+ self._record_conversation_stage(request_id, "done")
670
+ return self._local_conversation_fallback(
671
+ messages=messages,
672
+ previous_proposal=previous_proposal,
673
+ runtime_type=normalized_runtime_type,
674
+ model_spec=model_spec,
675
+ bindings=bindings,
676
+ reason_code=exc.code,
677
+ )
678
+ self._record_conversation_stage(request_id, "validating")
679
+ try:
680
+ proposal = self.backend.parse_conversation_proposal(
681
+ response.content,
682
+ base=previous_proposal,
683
+ runtime_type=normalized_runtime_type,
684
+ )
685
+ except StudioError as exc:
686
+ if exc.code != "AUTHORING_MODEL_OUTPUT_INVALID":
687
+ raise
688
+ LOGGER.warning(
689
+ "conversation authoring patch invalid; returning local fallback: "
690
+ "modelProfileId=%s",
691
+ model_profile_id,
692
+ )
693
+ self._record_conversation_stage(request_id, "done")
694
+ return self._local_conversation_fallback(
695
+ messages=messages,
696
+ previous_proposal=previous_proposal,
697
+ runtime_type=normalized_runtime_type,
698
+ model_spec=model_spec,
699
+ bindings=bindings,
700
+ reason_code=exc.code,
701
+ )
702
+ self._record_conversation_stage(request_id, "done")
703
+ LOGGER.info(
704
+ "conversation authoring finished in %.2fs: modelProfileId=%s slug=%s",
705
+ time.monotonic() - started,
706
+ model_profile_id,
707
+ getattr(proposal, "slug", "-"),
214
708
  )
215
- proposal = self.backend.parse_conversation_proposal(response.content)
216
709
  return {
217
- "proposal": proposal.model_dump(mode="json"),
710
+ "proposal": _inject_managed_bindings(
711
+ proposal,
712
+ model_spec=model_spec,
713
+ bindings=bindings,
714
+ ).model_dump(
715
+ by_alias=True, mode="json"
716
+ ),
218
717
  "requiresConfirmation": True,
718
+ "authoringMode": "chat",
219
719
  "usage": response.usage.model_dump(by_alias=True, mode="json"),
220
720
  }
221
721
 
722
+ async def _compose_conversation_codex(
723
+ self,
724
+ *,
725
+ messages: list[dict[str, str]],
726
+ model: Any,
727
+ previous_proposal: Any,
728
+ request_id: str | None,
729
+ started: float,
730
+ model_profile_id: str,
731
+ model_spec: ModelSpec,
732
+ bindings: AgentBindings,
733
+ ) -> dict | None:
734
+ """让真实 Codex 会话在工作区写 agentkit.yaml;任何失败降级 chat 链。
735
+
736
+ 返回 ``None`` 表示应降级(探测失败/超时/重试后仍不合法),调用方继续走
737
+ 既有 chat 链路,保证零回归。
738
+ """
739
+
740
+ self._record_conversation_stage(request_id, "codex_writing")
741
+ executor = self._codex_authoring_executor()
742
+ try:
743
+ result = await executor.compose(
744
+ messages=messages,
745
+ model=model,
746
+ base=previous_proposal,
747
+ request_id=request_id,
748
+ )
749
+ except Exception as exc:
750
+ LOGGER.warning(
751
+ "codex authoring failed after %.2fs, falling back to chat chain: "
752
+ "modelProfileId=%s requestId=%s reason=%s",
753
+ time.monotonic() - started,
754
+ model_profile_id,
755
+ request_id or "-",
756
+ exc,
757
+ )
758
+ self._record_conversation_stage(
759
+ request_id, "generating", detail=f"codex authoring 降级: {exc}"
760
+ )
761
+ return None
762
+ self._record_conversation_stage(request_id, "done")
763
+ LOGGER.info(
764
+ "conversation authoring finished in %.2fs via codex: modelProfileId=%s "
765
+ "slug=%s attempts=%d",
766
+ time.monotonic() - started,
767
+ model_profile_id,
768
+ getattr(result.proposal, "slug", "-"),
769
+ result.attempts,
770
+ )
771
+ return {
772
+ "proposal": _inject_managed_bindings(
773
+ result.proposal,
774
+ model_spec=model_spec,
775
+ bindings=bindings,
776
+ ).model_dump(
777
+ by_alias=True, mode="json"
778
+ ),
779
+ "requiresConfirmation": True,
780
+ "authoringMode": "codex",
781
+ "usage": result.usage.model_dump(by_alias=True, mode="json"),
782
+ }
783
+
784
+ def _project_agent_spec(
785
+ self,
786
+ inspection: dict[str, Any],
787
+ *,
788
+ agent_id: str,
789
+ model_profile_id: str | None,
790
+ ) -> tuple[AgentSpec, list[dict[str, Any]]]:
791
+ """Project detected project config without silently dropping authoring fields."""
792
+
793
+ config = inspection.get("evidence", {}).get("config", {})
794
+ if not isinstance(config, dict):
795
+ config = {}
796
+ embedded = config.get("spec")
797
+ payload: dict[str, Any] = copy.deepcopy(embedded) if isinstance(embedded, dict) else {}
798
+
799
+ for field in (
800
+ "description",
801
+ "model",
802
+ "capabilities",
803
+ "bindings",
804
+ "execution",
805
+ "context",
806
+ "memory",
807
+ "security",
808
+ "evaluation",
809
+ ):
810
+ if field not in payload and field in config:
811
+ payload[field] = copy.deepcopy(config[field])
812
+
813
+ instructions = payload.get("instructions")
814
+ if not isinstance(instructions, dict):
815
+ instructions = {}
816
+ instructions.setdefault(
817
+ "system",
818
+ str(
819
+ config.get("prompt")
820
+ or config.get("instruction")
821
+ or "You are a reliable assistant imported from an existing project."
822
+ ),
823
+ )
824
+ instructions.setdefault("task", str(config.get("task_prompt") or config.get("task") or ""))
825
+ payload["instructions"] = instructions
826
+
827
+ model_payload = payload.get("model")
828
+ if isinstance(model_payload, str) and model_payload.strip():
829
+ upstream = (
830
+ (os.environ.get("OPENAI_BASE_URL") or os.environ.get("OPENAI_API_BASE") or "")
831
+ .strip()
832
+ .rstrip("/")
833
+ )
834
+ endpoint = (
835
+ {"endpointUrl": upstream}
836
+ if upstream.endswith("/chat/completions")
837
+ else {"baseUrl": upstream or "https://api.openai.com/v1"}
838
+ )
839
+ payload["model"] = {
840
+ "model": model_payload.strip(),
841
+ "credentialRef": "env://AGENTKIT_MODEL_API_KEY",
842
+ **endpoint,
843
+ }
844
+
845
+ bindings = payload.get("bindings")
846
+ if not isinstance(bindings, dict):
847
+ bindings = {}
848
+ else:
849
+ bindings = copy.deepcopy(bindings)
850
+ capabilities = payload.get("capabilities")
851
+ if not isinstance(capabilities, dict):
852
+ capabilities = {}
853
+ else:
854
+ capabilities = copy.deepcopy(capabilities)
855
+ unresolved: list[dict[str, Any]] = []
856
+ legacy_fields = (
857
+ ("tools", "tools"),
858
+ ("mcpServers", "mcp_servers"),
859
+ ("skills", "skills"),
860
+ )
861
+ for canonical, legacy in legacy_fields:
862
+ if canonical in bindings or canonical in capabilities:
863
+ continue
864
+ raw = config.get(canonical, config.get(legacy))
865
+ if raw is None:
866
+ continue
867
+ if not isinstance(raw, list):
868
+ unresolved.append(
869
+ {"kind": canonical, "value": copy.deepcopy(raw), "reason": "not-a-list"}
870
+ )
871
+ continue
872
+ projected_bindings: list[dict[str, Any]] = []
873
+ projected_capabilities: list[dict[str, Any]] = []
874
+ for item in raw:
875
+ if isinstance(item, str) and item.strip():
876
+ projected_bindings.append({"resourceId": item.strip()})
877
+ elif isinstance(item, dict) and (item.get("resourceId") or item.get("resource_id")):
878
+ projected_bindings.append(copy.deepcopy(item))
879
+ elif isinstance(item, dict) and item.get("name") and item.get("version"):
880
+ projected_capabilities.append(copy.deepcopy(item))
881
+ else:
882
+ unresolved.append(
883
+ {
884
+ "kind": canonical,
885
+ "value": copy.deepcopy(item),
886
+ "reason": "unsupported-binding-shape",
887
+ }
888
+ )
889
+ if projected_bindings:
890
+ bindings[canonical] = projected_bindings
891
+ if projected_capabilities:
892
+ capabilities[canonical] = projected_capabilities
893
+
894
+ if model_profile_id:
895
+ existing_profiles = list(
896
+ bindings.get("modelProfileIds") or bindings.get("model_profile_ids") or []
897
+ )
898
+ bindings["modelProfileId"] = model_profile_id
899
+ bindings["modelProfileIds"] = list(
900
+ dict.fromkeys([model_profile_id, *existing_profiles])
901
+ )
902
+ if "modelParameters" not in bindings and "model_parameters" in config:
903
+ bindings["modelParameters"] = copy.deepcopy(config["model_parameters"])
904
+ if "policyTemplate" not in bindings:
905
+ policy = config.get("policy_template", config.get("policy"))
906
+ if isinstance(policy, str) and policy:
907
+ bindings["policyTemplate"] = policy
908
+ payload["bindings"] = bindings
909
+ payload["capabilities"] = capabilities
910
+
911
+ try:
912
+ spec = AgentSpec.model_validate(payload)
913
+ except ValueError as exc:
914
+ raise StudioError(
915
+ "PROJECT_AGENT_SPEC_INVALID",
916
+ "项目配置无法无损转换为 AgentSpec",
917
+ status_code=422,
918
+ details={"reason": str(exc)},
919
+ ) from exc
920
+
921
+ runtime_type = str(inspection["runtimeType"])
922
+ detected_runtime = self.backend.runtime_ref(agent_id, runtime_type)
923
+ supplied_runtime = spec.runtime
924
+ if supplied_runtime is not None and supplied_runtime.type != runtime_type:
925
+ raise StudioError(
926
+ "PROJECT_RUNTIME_MISMATCH",
927
+ "项目配置中的 Runtime 与检测结果不一致",
928
+ status_code=422,
929
+ details={
930
+ "detected": runtime_type,
931
+ "configured": supplied_runtime.type,
932
+ },
933
+ )
934
+ if runtime_type in {"adk", "langgraph"}:
935
+ detected_runtime = RuntimeRef(
936
+ type=cast(Any, runtime_type),
937
+ project_path=str(inspection["projectPath"]),
938
+ entry_point=(
939
+ supplied_runtime.entry_point
940
+ if supplied_runtime and supplied_runtime.entry_point
941
+ else str(inspection.get("entryPoint") or "agent.py")
942
+ ),
943
+ agent_variable=(
944
+ supplied_runtime.agent_variable
945
+ if supplied_runtime
946
+ else str(
947
+ inspection.get("agentVariable")
948
+ or ("graph" if runtime_type == "langgraph" else "root_agent")
949
+ )
950
+ ),
951
+ version=supplied_runtime.version if supplied_runtime else None,
952
+ detection="auto",
953
+ )
954
+ elif supplied_runtime is not None and supplied_runtime.version:
955
+ detected_runtime.version = supplied_runtime.version
956
+ spec.runtime = detected_runtime
957
+
958
+ catalog_ids = {
959
+ kind: {item.resource_id for item in self.studio.catalog.list(kind=kind, limit=500)}
960
+ for kind in ("tool", "mcp", "skill")
961
+ }
962
+ for kind, values in (
963
+ ("tool", spec.bindings.tools),
964
+ ("mcp", spec.bindings.mcp_servers),
965
+ ("skill", spec.bindings.skills),
966
+ ):
967
+ for binding in values:
968
+ if binding.resource_id not in catalog_ids[kind]:
969
+ unresolved.append(
970
+ {
971
+ "kind": kind,
972
+ "value": binding.model_dump(
973
+ by_alias=True,
974
+ exclude_none=True,
975
+ mode="json",
976
+ ),
977
+ "reason": "not-in-resource-catalog",
978
+ }
979
+ )
980
+ return spec, unresolved
981
+
222
982
  def _agent_exists(self, agent_id: str) -> bool:
223
983
  return bool(
224
984
  self.studio.codex_manifests.exists(agent_id)
@@ -250,36 +1010,18 @@ class StudioAuthoringCoordinator:
250
1010
  *,
251
1011
  resolved_slug: str,
252
1012
  ) -> 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
- ),
1013
+ imported = manifest.model_copy(update={"name": agent_id}, deep=True)
1014
+ snapshot = self.studio.codex_manifests.save(imported)
1015
+ draft = self.studio.codex_agents._project(snapshot)
1016
+ draft.metadata.name = display_name
1017
+ draft.metadata.labels.update(
1018
+ {
1019
+ "agentkit.ksyun.com/slug": self.backend.normalize_slug(resolved_slug),
1020
+ "agentkit.ksyun.com/source": "import",
1021
+ }
282
1022
  )
1023
+ self.studio.codex_drafts.save(draft)
1024
+ return cast(AgentDraft, self.studio.codex_agents._project(snapshot, current=draft))
283
1025
 
284
1026
 
285
1027
  __all__ = ["StudioAuthoringCoordinator"]