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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (411) hide show
  1. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/METADATA +22 -2
  2. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/RECORD +356 -222
  3. ksadk/a2a/_space_client_events.py +381 -0
  4. ksadk/a2a/event_adapter.py +156 -64
  5. ksadk/a2a/executor.py +179 -63
  6. ksadk/a2a/space_client.py +6 -333
  7. ksadk/a2ui/core.py +222 -53
  8. ksadk/agui/_agent_helpers.py +243 -0
  9. ksadk/agui/a2ui_projection.py +12 -5
  10. ksadk/agui/agent.py +443 -259
  11. ksadk/api/client.py +525 -27
  12. ksadk/builders/code_builder.py +284 -8
  13. ksadk/builders/container_builder.py +8 -1
  14. ksadk/builders/managed_runtime_builder.py +39 -35
  15. ksadk/cli/__init__.py +17 -0
  16. ksadk/cli/cmd_dashboard.py +15 -1
  17. ksadk/cli/cmd_deploy.py +10 -2
  18. ksadk/cli/cmd_eval.py +66 -9
  19. ksadk/cli/cmd_evalset.py +284 -0
  20. ksadk/cli/cmd_files.py +5 -1
  21. ksadk/cli/cmd_hermes.py +1 -126
  22. ksadk/cli/cmd_invoke.py +28 -38
  23. ksadk/cli/cmd_managed_runtime.py +95 -0
  24. ksadk/cli/cmd_observe.py +85 -0
  25. ksadk/cli/cmd_openclaw.py +1 -435
  26. ksadk/cli/cmd_replay.py +28 -4
  27. ksadk/cli/cmd_studio.py +99 -8
  28. ksadk/cli/cmd_web.py +10 -2
  29. ksadk/cli/hermes_env.py +151 -0
  30. ksadk/cli/invoke_payload.py +49 -0
  31. ksadk/cli/openclaw_env.py +458 -0
  32. ksadk/codex/client.py +180 -30
  33. ksadk/codex/runtime.py +428 -326
  34. ksadk/configs/env_registry.py +66 -12
  35. ksadk/configs/env_registry_pcm.py +181 -0
  36. ksadk/configs/env_var_spec.py +18 -0
  37. ksadk/configs/global_config.py +5 -0
  38. ksadk/context_engine/__init__.py +98 -0
  39. ksadk/context_engine/assembler.py +176 -0
  40. ksadk/context_engine/baseline.py +419 -0
  41. ksadk/context_engine/cache_observability.py +228 -0
  42. ksadk/context_engine/capabilities.py +443 -0
  43. ksadk/context_engine/contributors.py +344 -0
  44. ksadk/context_engine/hosted_pipeline.py +417 -0
  45. ksadk/context_engine/models.py +117 -0
  46. ksadk/context_engine/planner.py +698 -0
  47. ksadk/context_engine/policies.py +319 -0
  48. ksadk/context_engine/projection.py +31 -0
  49. ksadk/context_engine/shadow_plan.py +266 -0
  50. ksadk/context_engine/tokenizer.py +164 -0
  51. ksadk/conversations/context.py +126 -6
  52. ksadk/conversations/message_projection.py +292 -28
  53. ksadk/conversations/model_context.py +49 -3
  54. ksadk/conversations/runtime_compaction.py +207 -5
  55. ksadk/conversations/runtime_input.py +184 -2
  56. ksadk/conversations/runtime_invocation.py +81 -3
  57. ksadk/conversations/runtime_observability.py +141 -0
  58. ksadk/conversations/runtime_payloads.py +42 -0
  59. ksadk/conversations/runtime_preparation.py +272 -2
  60. ksadk/conversations/runtime_resume.py +20 -1
  61. ksadk/conversations/runtime_stream_events.py +94 -9
  62. ksadk/conversations/semantic_summary.py +311 -1
  63. ksadk/conversations/session_lock.py +51 -0
  64. ksadk/deployment/env_forward.py +72 -0
  65. ksadk/deployment/managed_runtime.py +5 -0
  66. ksadk/deployment/providers/serverless.py +143 -69
  67. ksadk/evaluation/__init__.py +32 -0
  68. ksadk/evaluation/a2a_adapter.py +9 -1
  69. ksadk/evaluation/adapters.py +18 -5
  70. ksadk/evaluation/agent_eval_client.py +395 -0
  71. ksadk/evaluation/cloud_binding.py +80 -0
  72. ksadk/evaluation/cloud_converter.py +194 -0
  73. ksadk/evaluation/cloud_service.py +199 -0
  74. ksadk/evaluation/contracts.py +73 -4
  75. ksadk/evaluation/evaluators.py +183 -17
  76. ksadk/evaluation/evidence.py +258 -0
  77. ksadk/evaluation/executor.py +31 -6
  78. ksadk/evaluation/local_adapter.py +550 -0
  79. ksadk/evaluation/service_env.py +27 -0
  80. ksadk/evaluation/studio_build_adapter.py +253 -0
  81. ksadk/evaluation/target.py +15 -6
  82. ksadk/events/__init__.py +13 -13
  83. ksadk/events/_v1_compat/__init__.py +1 -0
  84. ksadk/events/_v1_compat/models.py +299 -0
  85. ksadk/events/_v1_compat/parser.py +247 -0
  86. ksadk/events/_v1_compat/projection.py +753 -0
  87. ksadk/events/adapters/__init__.py +34 -0
  88. ksadk/events/adapters/_a2a_snapshot.py +622 -0
  89. ksadk/events/adapters/_a2a_support.py +275 -0
  90. ksadk/events/adapters/_codex_interactions.py +375 -0
  91. ksadk/events/adapters/_codex_items.py +801 -0
  92. ksadk/events/adapters/_codex_validators.py +311 -0
  93. ksadk/events/adapters/_langgraph_support.py +785 -0
  94. ksadk/events/adapters/a2a.py +841 -0
  95. ksadk/events/adapters/adk.py +503 -0
  96. ksadk/events/adapters/codex.py +717 -0
  97. ksadk/events/adapters/langgraph.py +745 -0
  98. ksadk/events/canonical.py +440 -0
  99. ksadk/events/canonical_replay.py +497 -0
  100. ksadk/events/canonical_store.py +468 -0
  101. ksadk/events/cold_recovery.py +257 -0
  102. ksadk/events/content.py +89 -0
  103. ksadk/events/identity.py +86 -0
  104. ksadk/events/pipeline.py +455 -0
  105. ksadk/events/projections.py +66 -0
  106. ksadk/events/reducer.py +661 -0
  107. ksadk/events/replay.py +3 -36
  108. ksadk/events/runtime_event.py +20 -265
  109. ksadk/events/session_event.py +324 -0
  110. ksadk/events/store.py +3 -316
  111. ksadk/events/v1_compat.py +41 -0
  112. ksadk/harness/runtime.py +160 -57
  113. ksadk/identity/resolver.py +4 -3
  114. ksadk/interaction/__init__.py +28 -0
  115. ksadk/interaction/contracts.py +136 -0
  116. ksadk/interaction/ledger.py +189 -0
  117. ksadk/interaction/provider.py +122 -0
  118. ksadk/interaction/providers/__init__.py +52 -0
  119. ksadk/interaction/providers/adk.py +40 -0
  120. ksadk/interaction/providers/codex.py +94 -0
  121. ksadk/interaction/providers/langgraph.py +58 -0
  122. ksadk/kernel/__init__.py +117 -0
  123. ksadk/kernel/authorization.py +258 -0
  124. ksadk/kernel/bootstrap.py +1074 -0
  125. ksadk/kernel/contract_fingerprints.py +61 -0
  126. ksadk/kernel/contracts.py +362 -0
  127. ksadk/kernel/control.py +219 -0
  128. ksadk/kernel/errors.py +79 -0
  129. ksadk/kernel/ingress.py +1102 -0
  130. ksadk/kernel/mapping.py +111 -0
  131. ksadk/kernel/memory_store.py +1133 -0
  132. ksadk/kernel/postgres_store.py +1756 -0
  133. ksadk/kernel/recovery.py +452 -0
  134. ksadk/kernel/runtime_identity.py +103 -0
  135. ksadk/kernel/sql/001_agent_kernel.sql +120 -0
  136. ksadk/kernel/sqlite_store.py +1410 -0
  137. ksadk/kernel/state.py +145 -0
  138. ksadk/kernel/store.py +232 -0
  139. ksadk/kernel/worker.py +953 -0
  140. ksadk/knowledge_base/client.py +7 -0
  141. ksadk/knowledge_base/service.py +32 -5
  142. ksadk/memory/__init__.py +61 -0
  143. ksadk/memory/adk/backends/base_ltm_backend.py +96 -1
  144. ksadk/memory/adk/backends/http_ltm_backend.py +14 -5
  145. ksadk/memory/adk/backends/inmemory_ltm_backend.py +136 -3
  146. ksadk/memory/adk/backends/sdk_ltm_backend.py +384 -2
  147. ksadk/memory/adk/backends/sqlite_ltm_backend.py +95 -0
  148. ksadk/memory/coordinator.py +461 -0
  149. ksadk/memory/events.py +202 -0
  150. ksadk/memory/extraction.py +228 -0
  151. ksadk/memory/ltm_backend_factory.py +16 -3
  152. ksadk/memory/models.py +316 -0
  153. ksadk/memory/policy.py +215 -0
  154. ksadk/memory/provider.py +47 -0
  155. ksadk/memory/provider_adapter.py +159 -0
  156. ksadk/memory/provider_resolver.py +71 -0
  157. ksadk/memory/providers/__init__.py +5 -0
  158. ksadk/memory/providers/local_sqlite.py +490 -0
  159. ksadk/memory/resolved_policy.py +145 -0
  160. ksadk/memory/service.py +188 -6
  161. ksadk/model_proxy/bootstrap.py +16 -9
  162. ksadk/model_proxy/detect.py +202 -9
  163. ksadk/model_proxy/namespace.py +39 -15
  164. ksadk/model_proxy/server.py +24 -5
  165. ksadk/model_proxy/transform.py +60 -2
  166. ksadk/observability/__init__.py +25 -0
  167. ksadk/observability/session_log.py +420 -0
  168. ksadk/observability/trajectory.py +105 -0
  169. ksadk/prompts/__init__.py +71 -0
  170. ksadk/prompts/compiler.py +215 -0
  171. ksadk/prompts/models.py +82 -0
  172. ksadk/prompts/projection.py +89 -0
  173. ksadk/prompts/resolved.py +137 -0
  174. ksadk/prompts/sources.py +233 -0
  175. ksadk/runners/_langgraph_runner_streams.py +822 -0
  176. ksadk/runners/adk_runner.py +5 -5
  177. ksadk/runners/base_runner.py +16 -0
  178. ksadk/runners/factory.py +1 -2
  179. ksadk/runners/langgraph_runner.py +198 -437
  180. ksadk/runtime/_runner_adapter/__init__.py +1 -0
  181. ksadk/runtime/_runner_adapter/stream_mapping.py +788 -0
  182. ksadk/runtime/adapter.py +118 -2
  183. ksadk/runtime/conversation_execution.py +316 -182
  184. ksadk/runtime/executor.py +144 -3
  185. ksadk/runtime/factory.py +193 -7
  186. ksadk/runtime/hosted_finalizer.py +268 -0
  187. ksadk/runtime/launch.py +14 -1
  188. ksadk/runtime/preprocessing.py +25 -3
  189. ksadk/runtime/runner_adapter.py +288 -508
  190. ksadk/runtime/usage.py +33 -0
  191. ksadk/sandbox/backends/e2b.py +35 -2
  192. ksadk/server/composition.py +8 -0
  193. ksadk/server/factory.py +69 -6
  194. ksadk/server/routes/kernel_ingress.py +258 -0
  195. ksadk/server/routes/openai_compat.py +88 -8
  196. ksadk/server/routes/projection.py +35 -20
  197. ksadk/server/routes/run.py +94 -0
  198. ksadk/server/routes/sessions.py +2 -0
  199. ksadk/server/routes/streaming.py +5 -2
  200. ksadk/server/static/assets/{ArtifactsPanel-CakCqxEN.js → ArtifactsPanel-DSU1sWD_.js} +1 -1
  201. ksadk/server/static/assets/{CodeBlock-CynLknyO.js → CodeBlock-DOH6MVcn.js} +4 -4
  202. ksadk/server/static/assets/{MathMessageMarkdown-3sQdOysS.js → MathMessageMarkdown-BL8my1R2.js} +4 -4
  203. ksadk/server/static/assets/MathMessageMarkdown-BorAY7qD.css +1 -0
  204. ksadk/server/static/assets/MermaidBlock-Dz4IP-Tx.js +312 -0
  205. ksadk/server/static/assets/NativeTerminalPanel-DUrK0JpZ.js +1 -0
  206. ksadk/server/static/assets/abnfDiagram-VCTEODGH-g20pFzNV.js +1 -0
  207. ksadk/server/static/assets/arc-C4FzinUA.js +1 -0
  208. ksadk/server/static/assets/architecture-7GRP2DOG-DjKratdf.js +1 -0
  209. ksadk/server/static/assets/architectureDiagram-5GKGNRK7-DpIqL5h4.js +36 -0
  210. ksadk/server/static/assets/blockDiagram-NRAW4CY4-BjkljTNz.js +129 -0
  211. ksadk/server/static/assets/c4Diagram-UCG6FXSJ-Dk_ieq2X.js +38 -0
  212. ksadk/server/static/assets/channel-4cQHKtx1.js +1 -0
  213. ksadk/server/static/assets/chunk-2Q5K7J3B-DmgWkESh.js +1 -0
  214. ksadk/server/static/assets/{chunk-FMBD7UC4-B_fH2YuH.js → chunk-5VM5RSS4-BVHAchFb.js} +1 -1
  215. ksadk/server/static/assets/{chunk-ND2GUHAM-D6rFLaVV.js → chunk-F27PBJKO-C-ipQzuS.js} +1 -1
  216. ksadk/server/static/assets/chunk-G27WJ6UU-C2EyJbIK.js +231 -0
  217. ksadk/server/static/assets/{chunk-4BX2VUAB-B9drTgOW.js → chunk-JWPE2WC7-vYvVJb_M.js} +1 -1
  218. ksadk/server/static/assets/chunk-LCL6LL3I-BcI6ysPT.js +206 -0
  219. ksadk/server/static/assets/chunk-POPQ4Y6H-C030x_Z1.js +1 -0
  220. ksadk/server/static/assets/chunk-RHFEMEQ7-yyGpsz4n.js +168 -0
  221. ksadk/server/static/assets/chunk-SVP7TREG-BLTlmMU7.js +88 -0
  222. ksadk/server/static/assets/chunk-XXDRQBXY-C_32ArgP.js +1 -0
  223. ksadk/server/static/assets/classDiagram-DTDB5LWJ-DLFd9Xi0.js +1 -0
  224. ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-DLFd9Xi0.js +1 -0
  225. ksadk/server/static/assets/{cose-bilkent-S5V4N54A-ChNs_Mdk.js → cose-bilkent-JH36ORCC-9YQYwdl0.js} +1 -1
  226. ksadk/server/static/assets/cynefin-OW5HDTMX-DjEM48Qp.js +1 -0
  227. ksadk/server/static/assets/cynefinDiagram-5FMLGOSQ-ErLF5N13.js +62 -0
  228. ksadk/server/static/assets/cytoscape.esm-CyCl8rPi.js +321 -0
  229. ksadk/server/static/assets/dagre-3AP2YEHR-DFiQF-6f.js +4 -0
  230. ksadk/server/static/assets/dagre-BuhGnRI1.js +1 -0
  231. ksadk/server/static/assets/diagram-S7CK7UJ4-Bt1v8-GC.js +30 -0
  232. ksadk/server/static/assets/diagram-UQ7AKVKN-DSCxJdBK.js +41 -0
  233. ksadk/server/static/assets/diagram-VSXAHHWV-DHfYwp_9.js +3 -0
  234. ksadk/server/static/assets/diagram-VX7I27RA-DdzD-4Le.js +24 -0
  235. ksadk/server/static/assets/diagram-Z3DM3KII-Du-nAJ9F.js +24 -0
  236. ksadk/server/static/assets/{dist-Dli7qW-B.js → dist-8hqcx0sU.js} +1 -1
  237. ksadk/server/static/assets/dist-B7seoj3d.js +1 -0
  238. ksadk/server/static/assets/{dist-C1sz5Eo3.js → dist-BE7bu-DL.js} +1 -1
  239. ksadk/server/static/assets/{dist-fhOuWds5.js → dist-BVDffZ0U.js} +1 -1
  240. ksadk/server/static/assets/{dist-BjOdlDjj.js → dist-BX8z72IC.js} +1 -1
  241. ksadk/server/static/assets/{dist-DsHPdk3W.js → dist-Bf2M8m3N.js} +1 -1
  242. ksadk/server/static/assets/{dist-o2OVmKvt.js → dist-BjU6y-A2.js} +1 -1
  243. ksadk/server/static/assets/{dist-DWKByxWV.js → dist-BytlxIDT.js} +1 -1
  244. ksadk/server/static/assets/{dist-DJN_6Y39.js → dist-CSh_DE14.js} +1 -1
  245. ksadk/server/static/assets/{dist-DQU9EWw-.js → dist-CXYYBw3_.js} +1 -1
  246. ksadk/server/static/assets/{dist-4N2NMVYg.js → dist-C_wsv-Qd.js} +1 -1
  247. ksadk/server/static/assets/{dist-gLnrGsoU.js → dist-ClxGviKw.js} +1 -1
  248. ksadk/server/static/assets/{dist-j6Dxmf_j.js → dist-CttYUqzS.js} +1 -1
  249. ksadk/server/static/assets/{dist-D6MpCZtz.js → dist-DNp6rLRA.js} +1 -1
  250. ksadk/server/static/assets/{dist-BB3-OwTQ.js → dist-DUX-id_F.js} +1 -1
  251. ksadk/server/static/assets/{dist-_pHPTyZ-.js → dist-DmldrHd_.js} +1 -1
  252. ksadk/server/static/assets/{dist-CWV4lDr1.js → dist-DnfXs8Vn.js} +2 -2
  253. ksadk/server/static/assets/{dist-B5oS1zcj.js → dist-Dq9gLD7L.js} +1 -1
  254. ksadk/server/static/assets/{dist-B-jNR6Vn.js → dist-W-TIVntx.js} +1 -1
  255. ksadk/server/static/assets/{dist-BIlKoW0k.js → dist-dDdADKFA.js} +1 -1
  256. ksadk/server/static/assets/{dist-X4TnqwOK.js → dist-dydu68Fl.js} +1 -1
  257. ksadk/server/static/assets/{dist-CmZHzlwZ.js → dist-ngtN0DJB.js} +1 -1
  258. ksadk/server/static/assets/ebnfDiagram-PWID7BFC-Da9C9NO1.js +1 -0
  259. ksadk/server/static/assets/erDiagram-SSCWMZ5O-EAGqqR79.js +99 -0
  260. ksadk/server/static/assets/eventmodeling-NTZA5JFV-CMgMaJrC.js +1 -0
  261. ksadk/server/static/assets/flowDiagram-A5DVABFB-BBKL0x3P.js +1 -0
  262. ksadk/server/static/assets/ganttDiagram-EL5Y4UJY-DqJsKb59.js +292 -0
  263. ksadk/server/static/assets/gitGraph-4MIJSDKK-Dw63mrhw.js +1 -0
  264. ksadk/server/static/assets/gitGraphDiagram-WWUBYQGX-B6g4dtDi.js +106 -0
  265. ksadk/server/static/assets/index-8ipRcQ-M.js +240 -0
  266. ksadk/server/static/assets/index-ByFCqjlh.css +2 -0
  267. ksadk/server/static/assets/info-A6RAGUB7-B6iAblII.js +1 -0
  268. ksadk/server/static/assets/infoDiagram-RXCK75RN-Djm_nbd5.js +2 -0
  269. ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-K0OOfVna.js → ishikawaDiagram-5VMMS53U-DWya9SG2.js} +6 -6
  270. ksadk/server/static/assets/{journeyDiagram-JHISSGLW-8LeOJaZp.js → journeyDiagram-EYS64GPL-DutxhSNI.js} +6 -6
  271. ksadk/server/static/assets/{kanban-definition-UN3LZRKU-C6uCgZrW.js → kanban-definition-3QL26DDD-DJdOF8Fm.js} +10 -10
  272. ksadk/server/static/assets/katex-vFWytM5c.js +257 -0
  273. ksadk/server/static/assets/{linear-hdBcYzM8.js → linear-BHjG8b-J.js} +1 -1
  274. ksadk/server/static/assets/mermaid-parser.core-KGSy4jWT.js +166 -0
  275. ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-Bjy_2v4_.js → mindmap-definition-FBJOCRG2-9-HC88D4.js} +29 -29
  276. ksadk/server/static/assets/packet-AYTQ26CC-BOYAipuq.js +1 -0
  277. ksadk/server/static/assets/pegDiagram-XKGWAZYB-DrEZd4fD.js +1 -0
  278. ksadk/server/static/assets/pie-WAS4IAKB-Ctq0Kbku.js +1 -0
  279. ksadk/server/static/assets/pieDiagram-E7YTZNPT-DpjqOFFp.js +39 -0
  280. ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-BmkRbwCg.js → quadrantDiagram-AXDQQJYC-BiNhgOXX.js} +6 -6
  281. ksadk/server/static/assets/radar-RG4KPBEZ-DN0-ul_0.js +1 -0
  282. ksadk/server/static/assets/railroad-74A4TZTK-C6cC_to0.js +1 -0
  283. ksadk/server/static/assets/railroad-abnf-HS5TGJTU-BimjIuRc.js +1 -0
  284. ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-CvY65ABq.js +1 -0
  285. ksadk/server/static/assets/railroad-peg-WCYAUIDC-D72Iq_Tv.js +1 -0
  286. ksadk/server/static/assets/railroadDiagram-O6MQD6OU-_Y3Ojg6G.js +1 -0
  287. ksadk/server/static/assets/requirementDiagram-EFPCY7ZU-DeCFdg9K.js +84 -0
  288. ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-l6iXmU65.js → sankeyDiagram-P5KCCOFB-CPH75rhw.js} +7 -7
  289. ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-Cq6UBeQB.js → sequenceDiagram-WJ2MYXX4-OEpQQdr4.js} +10 -10
  290. ksadk/server/static/assets/sizeCapture-X5ZJPWSS-heUErzhI.js +1 -0
  291. ksadk/server/static/assets/stateDiagram-HBIQ2CUA-DvA3jSMB.js +1 -0
  292. ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BgQY03nz.js +1 -0
  293. ksadk/server/static/assets/swimlanes-XN3QIQJK-CR-dfN_t.js +1 -0
  294. ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-DLVw3q5Q.js +8 -0
  295. ksadk/server/static/assets/{timeline-definition-PNZ67QCA-DEz24vV0.js → timeline-definition-24CTP7MA-DAVJf1TX.js} +7 -7
  296. ksadk/server/static/assets/treeView-Q6P3EWNA-BMpO0wsB.js +1 -0
  297. ksadk/server/static/assets/treemap-WGGIJYW6-Et0aqzU3.js +1 -0
  298. ksadk/server/static/assets/vennDiagram-4TSXK5OY-DwM8eDkh.js +34 -0
  299. ksadk/server/static/assets/wardley-WFR3VGLG-CbY0mhpl.js +1 -0
  300. ksadk/server/static/assets/wardleyDiagram-VM6X3IG4-DZL-Zz2t.js +78 -0
  301. ksadk/server/static/assets/xychartDiagram-S5SC5T6Z-Dy6Yh_hu.js +7 -0
  302. ksadk/server/static/index.html +2 -2
  303. ksadk/sessions/__init__.py +26 -9
  304. ksadk/sessions/_local_service_sync.py +688 -0
  305. ksadk/sessions/_local_tables.py +21 -0
  306. ksadk/sessions/_postgres_schema.py +305 -0
  307. ksadk/sessions/_postgres_tables.py +15 -0
  308. ksadk/sessions/base.py +80 -1
  309. ksadk/sessions/in_memory.py +53 -2
  310. ksadk/sessions/local_service.py +63 -604
  311. ksadk/sessions/postgres_service.py +58 -148
  312. ksadk/sessions/resilient.py +35 -1
  313. ksadk/studio/api.py +577 -25
  314. ksadk/studio/api_contracts.py +114 -5
  315. ksadk/studio/api_memory_routes.py +82 -0
  316. ksadk/studio/authoring.py +252 -27
  317. ksadk/studio/authoring_coordinator.py +799 -67
  318. ksadk/studio/builder.py +88 -11
  319. ksadk/studio/capabilities.py +17 -0
  320. ksadk/studio/cloud.py +1916 -95
  321. ksadk/studio/codex_agent_service.py +125 -14
  322. ksadk/studio/codex_authoring.py +469 -0
  323. ksadk/studio/codex_builder.py +138 -25
  324. ksadk/studio/codex_manifest.py +51 -22
  325. ksadk/studio/codex_run.py +46 -3
  326. ksadk/studio/compiler.py +1 -0
  327. ksadk/studio/contracts.py +114 -3
  328. ksadk/studio/event_store.py +25 -95
  329. ksadk/studio/framework_run.py +231 -3
  330. ksadk/studio/hosted_kernel.py +317 -0
  331. ksadk/studio/manifest_resolver.py +131 -0
  332. ksadk/studio/model_client.py +95 -9
  333. ksadk/studio/model_profile_service.py +1 -1
  334. ksadk/studio/operations.py +37 -5
  335. ksadk/studio/otel_trace.py +348 -42
  336. ksadk/studio/pcm_memory.py +85 -0
  337. ksadk/studio/repository.py +23 -2
  338. ksadk/studio/resource_catalog.py +43 -0
  339. ksadk/studio/run_service.py +563 -137
  340. ksadk/studio/runtime_source.py +42 -16
  341. ksadk/studio/service.py +1180 -72
  342. ksadk/studio/shared_web.py +32 -22
  343. ksadk/studio/static/assets/index-BLpcdrgW.css +1 -0
  344. ksadk/studio/static/assets/index-CYekR2Xv.js +260 -0
  345. ksadk/studio/static/index.html +2 -2
  346. ksadk/studio/templates.py +100 -8
  347. ksadk/studio/validator.py +19 -0
  348. ksadk/studio/workspace.py +30 -32
  349. ksadk/tracing/setup.py +304 -3
  350. ksadk/version.py +1 -1
  351. ksadk_runtime_common/schemas/runtime_event_v1.json +37 -0
  352. ksadk_runtime_common/schemas/runtime_event_v2.json +271 -0
  353. ksadk/events/parser.py +0 -191
  354. ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +0 -1
  355. ksadk/server/static/assets/MermaidBlock-NERD_ef7.js +0 -303
  356. ksadk/server/static/assets/NativeTerminalPanel-CiHWf82q.js +0 -1
  357. ksadk/server/static/assets/arc-DrgjcKhl.js +0 -1
  358. ksadk/server/static/assets/architecture-7EHR7CIX-CpZLRCcY.js +0 -1
  359. ksadk/server/static/assets/architectureDiagram-3BPJPVTR-BXoVRbQ6.js +0 -36
  360. ksadk/server/static/assets/blockDiagram-GPEHLZMM-BnaB3Ywv.js +0 -132
  361. ksadk/server/static/assets/c4Diagram-AAUBKEIU-Dw4weZcp.js +0 -10
  362. ksadk/server/static/assets/channel-Dt3rQTFI.js +0 -1
  363. ksadk/server/static/assets/chunk-2J33WTMH-ChjpLK7j.js +0 -1
  364. ksadk/server/static/assets/chunk-55IACEB6-CK4ROATy.js +0 -1
  365. ksadk/server/static/assets/chunk-727SXJPM-B9KtTTgw.js +0 -206
  366. ksadk/server/static/assets/chunk-AQP2D5EJ-1O1mki3m.js +0 -231
  367. ksadk/server/static/assets/chunk-QZHKN3VN-iXEZtKQc.js +0 -1
  368. ksadk/server/static/assets/classDiagram-4FO5ZUOK-DIveV17Q.js +0 -1
  369. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-DIveV17Q.js +0 -1
  370. ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +0 -321
  371. ksadk/server/static/assets/dagre-BM42HDAG-D0QXg_MQ.js +0 -4
  372. ksadk/server/static/assets/dagre-Bx709z4p.js +0 -1
  373. ksadk/server/static/assets/diagram-2AECGRRQ-DBDDKwBQ.js +0 -43
  374. ksadk/server/static/assets/diagram-5GNKFQAL-BkwDOxmO.js +0 -10
  375. ksadk/server/static/assets/diagram-KO2AKTUF-Bv8waYvY.js +0 -3
  376. ksadk/server/static/assets/diagram-LMA3HP47-e1VECDdu.js +0 -24
  377. ksadk/server/static/assets/diagram-OG6HWLK6-Ds9adfRl.js +0 -24
  378. ksadk/server/static/assets/dist-B5J2uhNk.js +0 -1
  379. ksadk/server/static/assets/erDiagram-TEJ5UH35-BPItFKH6.js +0 -85
  380. ksadk/server/static/assets/eventmodeling-FCH6USID-DYFSxbqr.js +0 -1
  381. ksadk/server/static/assets/flowDiagram-I6XJVG4X-BbYB9w5Y.js +0 -162
  382. ksadk/server/static/assets/ganttDiagram-6RSMTGT7-TQXa0KXv.js +0 -292
  383. ksadk/server/static/assets/gitGraph-WXDBUCRP-DyNYp6BN.js +0 -1
  384. ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-bKblCnpa.js +0 -106
  385. ksadk/server/static/assets/graphlib-B8gBHxth.js +0 -1
  386. ksadk/server/static/assets/index-R52pJJvg.js +0 -177
  387. ksadk/server/static/assets/index-jJr3_955.css +0 -2
  388. ksadk/server/static/assets/info-J43DQDTF-WUx7qTde.js +0 -1
  389. ksadk/server/static/assets/infoDiagram-5YYISTIA-BGVT7J_i.js +0 -2
  390. ksadk/server/static/assets/katex-CYWseY5E.js +0 -265
  391. ksadk/server/static/assets/mermaid-parser.core-BWrDjSZM.js +0 -161
  392. ksadk/server/static/assets/packet-YPE3B663-BN13y_xl.js +0 -1
  393. ksadk/server/static/assets/pie-LRSECV5Y-ftSLXhhc.js +0 -1
  394. ksadk/server/static/assets/pieDiagram-4H26LBE5-GmmqNiVH.js +0 -30
  395. ksadk/server/static/assets/radar-GUYGQ44K-jaICkIeO.js +0 -1
  396. ksadk/server/static/assets/requirementDiagram-4Y6WPE33-DSWFqL7c.js +0 -84
  397. ksadk/server/static/assets/stateDiagram-AJRCARHV-qzTQpg3Y.js +0 -1
  398. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-BYCoYGAN.js +0 -1
  399. ksadk/server/static/assets/treeView-BLDUP644-Cpib_vlz.js +0 -1
  400. ksadk/server/static/assets/treemap-LRROVOQU-DQxfmVHK.js +0 -1
  401. ksadk/server/static/assets/vennDiagram-CIIHVFJN-CMCixki2.js +0 -34
  402. ksadk/server/static/assets/wardley-L42UT6IY-BQYHnnkU.js +0 -1
  403. ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-CVzB8F9K.js +0 -78
  404. ksadk/server/static/assets/xychartDiagram-2RQKCTM6-BFBqBLc1.js +0 -7
  405. ksadk/studio/evaluation.py +0 -192
  406. ksadk/studio/static/assets/index-CaEN0eTR.css +0 -1
  407. ksadk/studio/static/assets/index-DI2eY_NH.js +0 -252
  408. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/WHEEL +0 -0
  409. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/entry_points.txt +0 -0
  410. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/licenses/LICENSE +0 -0
  411. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/top_level.txt +0 -0
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from typing import Any, Literal
6
6
 
7
- from pydantic import Field, SecretStr
7
+ from pydantic import Field, SecretStr, field_validator
8
8
 
9
9
  from ksadk.evaluation import EvaluationConfig as PublicEvaluationConfig
10
10
  from ksadk.evaluation import TargetRef
@@ -48,6 +48,28 @@ class BuildRequest(ContractModel):
48
48
  evaluation_suite_refs: list[str] = Field(default_factory=list)
49
49
 
50
50
 
51
+ class ImportRootRequest(ContractModel):
52
+ """一键导入根 Framework 项目(方案 §6.1)。"""
53
+ name: str | None = None
54
+ slug: str | None = None
55
+
56
+
57
+ class PromptCompileRequest(ContractModel):
58
+ """PR-S2:Prompt 编译预览请求(方案 §6.2)。只读,不写 Session/Trace。"""
59
+ revision: int = Field(default=1, ge=1)
60
+ request_instructions: str = Field(default="", max_length=32768)
61
+ include_content: bool = False # local debug 显式请求正文
62
+
63
+
64
+ class ContextPreviewRequest(ContractModel):
65
+ """PR-S2:Context 预览请求(方案 §6.2)。复用真实 Planner,不调模型。"""
66
+ revision: int = Field(default=1, ge=1)
67
+ user_input: str = Field(default="", max_length=1_000_000)
68
+ request_instructions: str = Field(default="", max_length=32768)
69
+ simulated_history: list[MessageInput] = Field(default_factory=list)
70
+ include_content: bool = False
71
+
72
+
51
73
  class MessageInput(ContractModel):
52
74
  role: str = "user"
53
75
  content: str = Field(min_length=1, max_length=1_000_000)
@@ -69,7 +91,29 @@ class AuthoringConversationMessage(ContractModel):
69
91
 
70
92
  class ConversationAuthoringRequest(ContractModel):
71
93
  messages: list[AuthoringConversationMessage] = Field(min_length=1, max_length=100)
94
+ # This is the single profile used to generate the Draft Patch. It is not
95
+ # implicitly the deployed Agent's only model: callers may bind a separate
96
+ # allow-list and default below.
72
97
  model_profile_id: str = Field(min_length=3, max_length=256)
98
+ # Runtime is a Studio-owned deployment choice. The authoring model only
99
+ # supplies semantic intent and must not choose an incompatible framework
100
+ # source contract on its own.
101
+ runtime_type: Literal["codex", "adk", "langgraph"] = "codex"
102
+ agent_model_profile_ids: list[str] = Field(default_factory=list, max_length=100)
103
+ agent_default_model_profile_id: str | None = Field(
104
+ default=None,
105
+ min_length=3,
106
+ max_length=256,
107
+ )
108
+ # Resource identities are selected by the Studio user, never generated by
109
+ # the authoring model. The API resolves and validates their contracts
110
+ # before adding them to the returned Draft Patch.
111
+ tool_resource_ids: list[str] = Field(default_factory=list, max_length=100)
112
+ mcp_resource_ids: list[str] = Field(default_factory=list, max_length=100)
113
+ skill_resource_ids: list[str] = Field(default_factory=list, max_length=100)
114
+ # 可选的进度关联标识:前端生成后随请求带上,可通过
115
+ # GET /api/v1/authoring/conversations:status/{request_id} 轮询构建阶段。
116
+ request_id: str | None = Field(default=None, min_length=1, max_length=128)
73
117
 
74
118
 
75
119
  class AuthoringCommitRequest(ContractModel):
@@ -96,10 +140,71 @@ class InteractionSubmitRequest(ContractModel):
96
140
  data: dict[str, Any] = Field(default_factory=dict)
97
141
 
98
142
 
99
- class EvaluationRequest(ContractModel):
100
- suite_refs: list[str] = Field(min_length=1)
101
- concurrency: int = Field(default=1, ge=1, le=4)
102
- fail_fast: bool = False
143
+ class CloudChatMessageRequest(ContractModel):
144
+ """One cloud turn submitted through Studio's loopback control plane.
145
+
146
+ The loopback process, never the browser, owns the AK/SK used to admit the
147
+ message through the cloud control plane. ``content`` uses the same
148
+ OpenAI-compatible text/image/file part shape as RunAgent; execution-policy
149
+ fields remain bounded enums and are revalidated by Server/Runtime.
150
+ """
151
+
152
+ content: str | list[dict[str, Any]]
153
+ model: str | None = Field(default=None, min_length=1, max_length=256)
154
+ model_options: dict[str, Any] = Field(default_factory=dict)
155
+ tool_approval_mode: Literal["ask", "risk", "full"] = "risk"
156
+ collaboration_mode: Literal["default", "plan"] | None = None
157
+ goal_objective: str | None = Field(default=None, min_length=1, max_length=4096)
158
+
159
+ @field_validator("content")
160
+ @classmethod
161
+ def validate_content(cls, value):
162
+ if isinstance(value, str):
163
+ if not value.strip():
164
+ raise ValueError("content must not be empty")
165
+ if len(value) > 1_000_000:
166
+ raise ValueError("text content is too large")
167
+ return value
168
+ if not 1 <= len(value) <= 9:
169
+ raise ValueError("content must contain between 1 and 9 parts")
170
+ attachment_count = 0
171
+ for part in value:
172
+ kind = str(part.get("type") or "")
173
+ if kind == "input_text":
174
+ text = part.get("text")
175
+ if not isinstance(text, str) or not text.strip() or len(text) > 1_000_000:
176
+ raise ValueError("input_text must contain bounded non-empty text")
177
+ continue
178
+ if kind == "input_image":
179
+ attachment_count += 1
180
+ url = part.get("image_url")
181
+ if not isinstance(url, str) or not url or len(url) > 14_000_000:
182
+ raise ValueError("input_image must contain a bounded image_url")
183
+ continue
184
+ if kind == "input_file":
185
+ attachment_count += 1
186
+ filename = part.get("filename")
187
+ data = part.get("file_data") or part.get("file_url")
188
+ if not isinstance(filename, str) or not filename.strip():
189
+ raise ValueError("input_file must contain a filename")
190
+ if not isinstance(data, str) or not data or len(data) > 14_000_000:
191
+ raise ValueError("input_file must contain bounded file data")
192
+ continue
193
+ raise ValueError(f"unsupported content part: {kind or '<missing>'}")
194
+ if attachment_count > 8:
195
+ raise ValueError("a turn supports at most 8 attachments")
196
+ return value
197
+
198
+
199
+ class CloudChatInteractionSubmitRequest(ContractModel):
200
+ """Public Interaction/v1 fields accepted by the local cloud-chat proxy."""
201
+
202
+ run_id: str = Field(min_length=1, max_length=256)
203
+ interaction_id: str = Field(min_length=1, max_length=256)
204
+ expected_revision: int = Field(ge=1)
205
+ action: Literal["approve", "reject", "submit", "cancel"]
206
+ response: dict[str, Any] = Field(default_factory=dict)
207
+ idempotency_key: str = Field(min_length=1, max_length=256)
103
208
 
104
209
 
105
210
  class StudioEvaluationCreate(ContractModel):
@@ -123,6 +228,10 @@ class RollbackRequest(ContractModel):
123
228
  target_build_id: str
124
229
 
125
230
 
231
+ class CloudAgentVersionRollbackRequest(ContractModel):
232
+ version_id: str = Field(min_length=1, max_length=256)
233
+
234
+
126
235
  class ModelProfileCreateRequest(ContractModel):
127
236
  name: str = Field(pattern=r"^[a-z][a-z0-9._-]{1,127}$")
128
237
  display_name: str = Field(min_length=1, max_length=128)
@@ -0,0 +1,82 @@
1
+ """Memory observability and local management routes for Studio."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from fastapi import FastAPI, Query
8
+
9
+ from ksadk.memory.coordinator import agent_user_scope_id
10
+ from ksadk.memory.models import MemoryDeleteRequest, MemorySearchRequest
11
+ from ksadk.memory.providers.local_sqlite import resolve_default_memory_provider
12
+
13
+
14
+ def register_memory_routes(app: FastAPI, studio: Any) -> None:
15
+ """Register PCM memory routes without expanding the central API module."""
16
+
17
+ @app.get("/api/v1/runs/{run_id}/memory-events")
18
+ async def get_run_memory_events(run_id: str):
19
+ events = studio.event_store.events(run_id)
20
+ items = []
21
+ for event in events:
22
+ data = event.data or {}
23
+ has_memory_payload = isinstance(data, dict) and (
24
+ "memory_event" in data
25
+ or any(
26
+ isinstance(value, str) and value.startswith("memory.")
27
+ for value in data.values()
28
+ )
29
+ )
30
+ if "memory" in str(event.type or "").lower() or has_memory_payload:
31
+ items.append({"id": event.id, "type": event.type, "data": data})
32
+ return {"items": items}
33
+
34
+ @app.get("/api/v1/memories")
35
+ async def list_memories(
36
+ user_id: str = Query(default="local-user", alias="userId"),
37
+ agent_id: str | None = Query(default=None, alias="agentId"),
38
+ ):
39
+ provider = resolve_default_memory_provider()
40
+ scope_id = agent_user_scope_id(agent_id=agent_id, user_id=user_id) if agent_id else user_id
41
+ result = provider.search(
42
+ MemorySearchRequest(
43
+ query="",
44
+ scopes=[("user", scope_id)],
45
+ memory_types=["profile", "fact", "episode"],
46
+ top_k=100,
47
+ max_tokens=10000,
48
+ min_score=0.0,
49
+ )
50
+ )
51
+ return {
52
+ "items": [
53
+ {
54
+ "memory_id": record.memory_id,
55
+ "scope": record.scope,
56
+ "scope_id": record.scope_id,
57
+ "memory_type": record.memory_type,
58
+ "content": record.content[:200],
59
+ "summary": record.summary,
60
+ "status": record.status,
61
+ "confidence": record.confidence,
62
+ "created_at": record.created_at,
63
+ }
64
+ for record in result.records
65
+ ]
66
+ }
67
+
68
+ @app.delete("/api/v1/memories/{memory_id}")
69
+ async def delete_memory(memory_id: str):
70
+ provider = resolve_default_memory_provider()
71
+ record = provider.get(memory_id)
72
+ if record is None:
73
+ return {"deleted": False, "status": "ok"}
74
+ result = provider.delete(
75
+ MemoryDeleteRequest(
76
+ memory_id=memory_id,
77
+ scope=record.scope,
78
+ scope_id=record.scope_id,
79
+ hard=True,
80
+ )
81
+ )
82
+ return {"deleted": result.deleted, "status": result.status}
ksadk/studio/authoring.py CHANGED
@@ -7,6 +7,7 @@ ID allocation to the server.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
+ import copy
10
11
  import hashlib
11
12
  import io
12
13
  import json
@@ -21,14 +22,15 @@ from urllib.parse import urlparse
21
22
  from uuid import uuid4
22
23
 
23
24
  import yaml # type: ignore[import-untyped]
24
- from pydantic import BaseModel, ConfigDict, Field, ValidationError
25
+ from pydantic import BaseModel, ConfigDict, Field, ValidationError, model_validator
25
26
 
26
27
  from ksadk.detection.detector import FrameworkDetector
28
+ from ksadk.managed_runtime import installed_runtime_version
27
29
  from ksadk.studio.capabilities import canonical_json, sha256_digest
28
30
  from ksadk.studio.codex_manifest import CodexAgentManifest
29
31
  from ksadk.studio.contracts import (
30
32
  AgentDraft,
31
- Instructions,
33
+ AgentSpec,
32
34
  NetworkPolicy,
33
35
  RuntimeRef,
34
36
  )
@@ -48,7 +50,33 @@ class ConversationProposal(BaseModel):
48
50
  slug: str = Field(min_length=1, max_length=63)
49
51
  runtimeType: Literal["codex", "adk", "langgraph"]
50
52
  description: str = Field(default="", max_length=1024)
51
- instructions: Instructions
53
+ spec: AgentSpec
54
+
55
+ @model_validator(mode="before")
56
+ @classmethod
57
+ def migrate_prompt_only_proposal(cls, value: Any) -> Any:
58
+ """Accept one release of old model output without persisting its data loss.
59
+
60
+ Older authoring prompts asked the model for only ``instructions``. Turn
61
+ that response into a complete AgentSpec at the boundary so callers only
62
+ ever consume the lossless proposal shape.
63
+ """
64
+
65
+ if not isinstance(value, dict) or "spec" in value:
66
+ return value
67
+ payload = dict(value)
68
+ instructions = payload.pop("instructions", None)
69
+ payload["spec"] = {
70
+ "description": str(payload.get("description") or ""),
71
+ "instructions": instructions or {},
72
+ }
73
+ return payload
74
+
75
+ @model_validator(mode="after")
76
+ def validate_runtime_type(self) -> "ConversationProposal":
77
+ if self.spec.runtime is not None and self.spec.runtime.type != self.runtimeType:
78
+ raise ValueError("spec.runtime.type 必须与 runtimeType 一致")
79
+ return self
52
80
 
53
81
 
54
82
  @dataclass(frozen=True)
@@ -82,16 +110,11 @@ class AgentAuthoringService:
82
110
  normalized = f"agent-{normalized}" if normalized else "agent"
83
111
  return normalized[:48].rstrip("-")
84
112
 
85
- def allocate_agent_id(self, _slug: str) -> str:
86
- """Allocate a server-owned identifier without using a slug as a path segment.
87
-
88
- Callers retain the normalized slug in Agent metadata for display and search,
89
- while source directories only use this opaque identifier.
90
- """
91
-
113
+ def allocate_agent_id(self, slug: str) -> str:
114
+ base = self.normalize_slug(slug)
92
115
  for _attempt in range(100):
93
- candidate = f"agentkit-{uuid4().hex[:8]}"
94
- if not (self.workspace.resolve("agents") / candidate).exists():
116
+ candidate = f"{base}-{uuid4().hex[:12]}"
117
+ if not self.workspace.resolve(Path("agents") / candidate).exists():
95
118
  return candidate
96
119
  raise StudioError(
97
120
  "AGENT_ID_ALLOCATION_FAILED",
@@ -111,7 +134,13 @@ class AgentAuthoringService:
111
134
  details={"runtimeType": normalized},
112
135
  )
113
136
  if normalized == "codex":
114
- return RuntimeRef(type="codex", version="0.144.4")
137
+ # A new YAML Agent must lock the CLI actually installed on this
138
+ # Studio host. Cloud admission resolves that explicit version via
139
+ # the Server-owned catalog instead of accepting a client image.
140
+ return RuntimeRef(
141
+ type="codex",
142
+ version=installed_runtime_version("codex") or "0.144.4",
143
+ )
115
144
  return RuntimeRef(
116
145
  type=cast(Any, normalized),
117
146
  project_path=f"agents/{agent_id}/source",
@@ -291,17 +320,197 @@ class AgentAuthoringService:
291
320
  path.unlink()
292
321
 
293
322
  @staticmethod
294
- def parse_conversation_proposal(content: str) -> ConversationProposal:
323
+ def _sanitize_model_block(payload: dict[str, Any]) -> None:
324
+ """清掉模型照抄示例或凭空编造的 spec.model 字段。
325
+
326
+ - baseUrl/endpointUrl 写着 example.com/placeholder 等占位域名的直接删掉
327
+ (Studio 会按选中的模型 Profile 注入真实 endpoint)
328
+ - parameters 只保留用户对话明确要求时模型写出的值;模型自行编造的
329
+ 常见值(temperature 0.x + maxTokens 2048/4096 这类组合)无法与
330
+ 用户意图区分时一并删除,交平台默认值兜底
331
+ """
332
+ spec = payload.get("spec")
333
+ if not isinstance(spec, dict):
334
+ return
335
+ model = spec.get("model")
336
+ if not isinstance(model, dict):
337
+ return
338
+ for key in ("baseUrl", "endpointUrl"):
339
+ value = model.get(key)
340
+ if not isinstance(value, str):
341
+ continue
342
+ try:
343
+ hostname = (urlparse(value).hostname or "").lower().rstrip(".")
344
+ except ValueError:
345
+ hostname = ""
346
+ labels = hostname.split(".")
347
+ is_example_host = hostname == "example.com" or hostname.endswith(".example.com")
348
+ is_placeholder_host = any(label == "placeholder" for label in labels)
349
+ if is_example_host or is_placeholder_host:
350
+ model.pop(key, None)
351
+ # ModelSpec 校验要求 baseUrl/endpointUrl 二选一;模型没写或写了占位被删时,
352
+ # 置一个显式标记值,coordinator 会用选中模型 Profile 的真实 endpoint 覆写。
353
+ if not model.get("baseUrl") and not model.get("endpointUrl"):
354
+ model["baseUrl"] = "https://model-profile.invalid/placeholder"
355
+
356
+ @staticmethod
357
+ def _coerce_model_credential_ref(payload: dict[str, Any]) -> None:
358
+ """容忍模型把 spec.model.credentialRef 写成对象/空值的常见错误形态。
359
+
360
+ 模型偶尔会把字符串引用字段写成 {} 或 {"ref": ...};在进入 Pydantic 校验前
361
+ 收敛为默认 env 引用,避免浪费唯一一次纠错重试。
362
+ """
363
+ spec = payload.get("spec")
364
+ if not isinstance(spec, dict):
365
+ return
366
+ model = spec.get("model")
367
+ if not isinstance(model, dict):
368
+ return
369
+ ref = model.get("credentialRef")
370
+ if isinstance(ref, str) and ref.strip().startswith(("env://", "keychain://", "secret-manager://")):
371
+ return
372
+ if isinstance(ref, dict):
373
+ nested = ref.get("ref") or ref.get("credentialRef") or ref.get("value")
374
+ if isinstance(nested, str) and nested.strip().startswith(("env://", "keychain://", "secret-manager://")):
375
+ model["credentialRef"] = nested.strip()
376
+ return
377
+ model["credentialRef"] = "env://AGENTKIT_MODEL_API_KEY"
378
+
379
+ @staticmethod
380
+ def _coerce_runtime_type(payload: dict[str, Any]) -> None:
381
+ """容忍模型把 spec.runtime.type 写成 provider 的常见错误形态。"""
382
+ spec = payload.get("spec")
383
+ if not isinstance(spec, dict):
384
+ return
385
+ runtime = spec.get("runtime")
386
+ if not isinstance(runtime, dict):
387
+ return
388
+ if not runtime.get("type") and runtime.get("provider"):
389
+ runtime["type"] = runtime.pop("provider")
390
+ elif runtime.get("provider") and runtime.get("type"):
391
+ runtime.pop("provider")
392
+
393
+ @staticmethod
394
+ def _conversation_json_object(content: str) -> dict[str, Any]:
395
+ """Extract one JSON object without trusting surrounding model prose."""
396
+
295
397
  text = str(content or "").strip()
296
- if text.startswith("```"):
297
- lines = text.splitlines()
298
- if lines and lines[0].startswith("```"):
299
- lines = lines[1:]
300
- if lines and lines[-1].strip() == "```":
301
- lines = lines[:-1]
302
- text = "\n".join(lines).strip()
398
+ candidates = [text]
399
+ candidates.extend(
400
+ match.group(1).strip()
401
+ for match in re.finditer(r"```(?:json)?\s*([\s\S]*?)```", text, re.IGNORECASE)
402
+ )
403
+ decoder = json.JSONDecoder()
404
+ for candidate in candidates:
405
+ try:
406
+ payload = json.loads(candidate)
407
+ except ValueError:
408
+ payload = None
409
+ if isinstance(payload, dict):
410
+ return cast(dict[str, Any], payload)
411
+ for start, character in enumerate(text):
412
+ if character != "{":
413
+ continue
414
+ try:
415
+ payload, _end = decoder.raw_decode(text, start)
416
+ except ValueError:
417
+ continue
418
+ if isinstance(payload, dict):
419
+ return cast(dict[str, Any], payload)
420
+ raise ValueError("model output does not contain a JSON object")
421
+
422
+ @staticmethod
423
+ def _merge_conversation_patch(
424
+ base: dict[str, Any], patch: dict[str, Any]
425
+ ) -> dict[str, Any]:
426
+ merged = copy.deepcopy(base)
427
+ for key, value in patch.items():
428
+ if isinstance(value, dict) and isinstance(merged.get(key), dict):
429
+ merged[key] = AgentAuthoringService._merge_conversation_patch(
430
+ cast(dict[str, Any], merged[key]), value
431
+ )
432
+ else:
433
+ merged[key] = copy.deepcopy(value)
434
+ return merged
435
+
436
+ @staticmethod
437
+ def _studio_owned_conversation_patch(
438
+ payload: dict[str, Any],
439
+ *,
440
+ runtime_type: str,
441
+ ) -> dict[str, Any]:
442
+ """Keep an authoring-model response to semantic fields only.
443
+
444
+ A conversation can describe an Agent, but it cannot produce a valid
445
+ local ADK/LangGraph project, resource identity, credential or runtime
446
+ binding. Treating its complete AgentSpec as deployable made a simple
447
+ conversation depend on it guessing every evolving Studio contract.
448
+ Studio owns those fields and injects them after this parser returns.
449
+ """
450
+
451
+ normalized = str(runtime_type or "").strip().lower()
452
+ if normalized not in _SUPPORTED_RUNTIMES:
453
+ raise ValueError("runtimeType is not supported")
454
+ raw_spec = payload.get("spec")
455
+ if not isinstance(raw_spec, dict):
456
+ raw_spec = {}
457
+ raw_instructions = raw_spec.get("instructions", payload.get("instructions"))
458
+ instructions = raw_instructions if isinstance(raw_instructions, dict) else {}
459
+ spec: dict[str, Any] = {
460
+ "instructions": {
461
+ key: str(value).strip()
462
+ for key in ("system", "task")
463
+ if isinstance((value := instructions.get(key)), str) and value.strip()
464
+ }
465
+ }
466
+ if isinstance(raw_spec.get("description"), str):
467
+ spec["description"] = raw_spec["description"].strip()
468
+ return {
469
+ key: copy.deepcopy(payload[key])
470
+ for key in ("name", "slug", "description")
471
+ if key in payload
472
+ } | {
473
+ "runtimeType": normalized,
474
+ "spec": spec,
475
+ }
476
+
477
+ @staticmethod
478
+ def parse_conversation_proposal(
479
+ content: str,
480
+ *,
481
+ base: ConversationProposal | dict[str, Any] | None = None,
482
+ runtime_type: str | None = None,
483
+ ) -> ConversationProposal:
303
484
  try:
304
- payload = json.loads(text)
485
+ payload = AgentAuthoringService._conversation_json_object(content)
486
+ for wrapper in ("proposal", "patch"):
487
+ wrapped = payload.get(wrapper)
488
+ if isinstance(wrapped, dict) and len(payload) == 1:
489
+ payload = cast(dict[str, Any], wrapped)
490
+ break
491
+ if runtime_type is not None:
492
+ payload = AgentAuthoringService._studio_owned_conversation_patch(
493
+ payload,
494
+ runtime_type=runtime_type,
495
+ )
496
+ AgentAuthoringService._coerce_model_credential_ref(payload)
497
+ AgentAuthoringService._sanitize_model_block(payload)
498
+ AgentAuthoringService._coerce_runtime_type(payload)
499
+ if base is not None:
500
+ base_payload = (
501
+ base.model_dump(by_alias=True, mode="json")
502
+ if isinstance(base, ConversationProposal)
503
+ else base
504
+ )
505
+ payload = AgentAuthoringService._merge_conversation_patch(
506
+ base_payload, payload
507
+ )
508
+ if runtime_type is not None:
509
+ # The merge can reintroduce an old runtimeType from a previous
510
+ # Draft Patch; the live Studio selector remains authoritative.
511
+ payload["runtimeType"] = str(runtime_type).strip().lower()
512
+ if isinstance(payload.get("spec"), dict):
513
+ payload["spec"].pop("runtime", None)
305
514
  proposal = ConversationProposal.model_validate(payload)
306
515
  except (ValueError, ValidationError) as exc:
307
516
  raise StudioError(
@@ -314,7 +523,11 @@ class AgentAuthoringService:
314
523
  return proposal.model_copy(update={"slug": normalized_slug})
315
524
 
316
525
  @staticmethod
317
- def conversation_messages(messages: list[dict[str, str]]) -> list[dict[str, str]]:
526
+ def conversation_messages(
527
+ messages: list[dict[str, str]],
528
+ *,
529
+ runtime_type: str = "codex",
530
+ ) -> list[dict[str, str]]:
318
531
  if not messages:
319
532
  raise StudioError(
320
533
  "AUTHORING_CONVERSATION_EMPTY",
@@ -326,9 +539,14 @@ class AgentAuthoringService:
326
539
  "role": "system",
327
540
  "content": (
328
541
  "你是 AgentKit Studio 的 Agent 设计助手。根据对话生成一个 JSON Draft Patch,"
329
- "不得输出 Markdown。字段必须且只能包含 name、slug、runtimeType、description、"
330
- "instructions;runtimeType 只能是 codex、adk、langgraph;instructions 必须包含"
331
- " system 和 task。只提出配置,不写文件、不宣称已经创建。"
542
+ "不得输出 Markdown。只返回一个最小 JSON Draft Patch:首轮只包含 name、"
543
+ "slug、description、spec;spec 只包含 instructions,instructions 只允许"
544
+ "system 和 task。后续轮次只返回要更新的上述字段,由 Studio 与上一版 Patch"
545
+ "合并。当前 Runtime 已由 Studio 选择为 "
546
+ f"{runtime_type},不得输出 runtimeType、spec.runtime、execution、context、"
547
+ "memory、security、evaluation、model、bindings、capabilities 或任何资源 ID。"
548
+ "模型 Profile、运行 Runtime、模型参数、Tool、MCP、Skill、凭证、端点与"
549
+ "资源 ID 都由 Studio 按用户选择注入。只提出配置,不写文件、不宣称已经创建。"
332
550
  ),
333
551
  }
334
552
  ]
@@ -469,7 +687,11 @@ class AgentAuthoringService:
469
687
 
470
688
  @staticmethod
471
689
  def _tree_digest(root: Path, *, exclude: set[str] | None = None) -> str:
472
- ignored = exclude or set()
690
+ # 默认排除 .agentkit(Studio 自身状态)与常见忽略目录,避免 inspect 后写 token json
691
+ # 改变 digest 导致 commit 时 PROJECT_CHANGED_AFTER_INSPECTION 误报(方案 §6.1)。
692
+ ignored = set(exclude or [])
693
+ ignored.add(".agentkit")
694
+ ignored.add(".git")
473
695
  entries: list[dict[str, Any]] = []
474
696
  for path in sorted(root.rglob("*"), key=lambda item: item.as_posix()):
475
697
  if path.is_symlink():
@@ -481,7 +703,10 @@ class AgentAuthoringService:
481
703
  )
482
704
  if not path.is_file() or path.name in ignored:
483
705
  continue
706
+ # 跳过 .agentkit / .git 目录下的文件
484
707
  relative = path.relative_to(root).as_posix()
708
+ if any(relative.startswith(ignored_dir + "/") for ignored_dir in (".agentkit", ".git")):
709
+ continue
485
710
  content = path.read_bytes()
486
711
  entries.append(
487
712
  {