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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (494) hide show
  1. {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/METADATA +43 -7
  2. agentengine_sdk_python-0.8.2.dist-info/RECORD +778 -0
  3. {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/WHEEL +1 -1
  4. ksadk/a2a/__init__.py +2 -0
  5. ksadk/a2a/_space_client_events.py +381 -0
  6. ksadk/a2a/bootstrap.py +0 -2
  7. ksadk/a2a/card.py +6 -111
  8. ksadk/a2a/control_plane.py +239 -1
  9. ksadk/a2a/event_adapter.py +156 -64
  10. ksadk/a2a/executor.py +195 -172
  11. ksadk/a2a/routes.py +12 -5
  12. ksadk/a2a/server.py +1 -8
  13. ksadk/a2a/service_env.py +33 -0
  14. ksadk/a2a/space_client.py +43 -349
  15. ksadk/a2a_card.py +101 -0
  16. ksadk/a2ui/core.py +222 -53
  17. ksadk/agui/_agent_helpers.py +243 -0
  18. ksadk/agui/a2ui_projection.py +12 -5
  19. ksadk/agui/agent.py +474 -275
  20. ksadk/agui/config.py +11 -5
  21. ksadk/agui/routes.py +7 -15
  22. ksadk/api/client.py +530 -40
  23. ksadk/api/environment.py +27 -0
  24. ksadk/builders/code_builder.py +352 -53
  25. ksadk/builders/container_builder.py +71 -44
  26. ksadk/builders/framework_requirements.py +2 -1
  27. ksadk/builders/ks3_uploader.py +2 -2
  28. ksadk/builders/managed_runtime_builder.py +60 -28
  29. ksadk/cli/__init__.py +25 -0
  30. ksadk/cli/cmd_a2a.py +18 -31
  31. ksadk/cli/cmd_create.py +13 -26
  32. ksadk/cli/cmd_dashboard.py +15 -1
  33. ksadk/cli/cmd_deploy.py +28 -3
  34. ksadk/cli/cmd_eval.py +443 -0
  35. ksadk/cli/cmd_evalset.py +284 -0
  36. ksadk/cli/cmd_files.py +5 -1
  37. ksadk/cli/cmd_hermes.py +137 -163
  38. ksadk/cli/cmd_invoke.py +28 -38
  39. ksadk/cli/cmd_launch.py +12 -0
  40. ksadk/cli/cmd_managed_runtime.py +95 -0
  41. ksadk/cli/cmd_observe.py +85 -0
  42. ksadk/cli/cmd_openclaw.py +85 -479
  43. ksadk/cli/cmd_replay.py +28 -4
  44. ksadk/cli/cmd_run.py +29 -67
  45. ksadk/cli/cmd_studio.py +201 -0
  46. ksadk/cli/cmd_web.py +15 -6
  47. ksadk/cli/dry_run.py +2 -1
  48. ksadk/cli/env_options.py +83 -0
  49. ksadk/cli/global_options.py +3 -1
  50. ksadk/cli/hermes_env.py +151 -0
  51. ksadk/cli/invoke_payload.py +49 -0
  52. ksadk/cli/network_options.py +7 -5
  53. ksadk/cli/openclaw_env.py +458 -0
  54. ksadk/cli/runtime_bootstrap.py +51 -0
  55. ksadk/cli/ui.py +6 -0
  56. ksadk/cli/workflow_common.py +5 -5
  57. ksadk/codex/__init__.py +3 -3
  58. ksadk/codex/client.py +833 -49
  59. ksadk/codex/phase.py +1 -1
  60. ksadk/codex/runtime.py +720 -127
  61. ksadk/common/kop_client.py +232 -0
  62. ksadk/configs/__init__.py +1 -3
  63. ksadk/configs/env_registry.py +244 -45
  64. ksadk/configs/env_registry_pcm.py +181 -0
  65. ksadk/configs/env_var_spec.py +18 -0
  66. ksadk/configs/global_config.py +6 -1
  67. ksadk/configs/settings.py +5 -108
  68. ksadk/context_engine/__init__.py +98 -0
  69. ksadk/context_engine/assembler.py +176 -0
  70. ksadk/context_engine/baseline.py +419 -0
  71. ksadk/context_engine/cache_observability.py +228 -0
  72. ksadk/context_engine/capabilities.py +443 -0
  73. ksadk/context_engine/contributors.py +344 -0
  74. ksadk/context_engine/hosted_pipeline.py +417 -0
  75. ksadk/context_engine/models.py +117 -0
  76. ksadk/context_engine/planner.py +698 -0
  77. ksadk/context_engine/policies.py +319 -0
  78. ksadk/context_engine/projection.py +31 -0
  79. ksadk/context_engine/shadow_plan.py +266 -0
  80. ksadk/context_engine/tokenizer.py +164 -0
  81. ksadk/conversations/__init__.py +85 -94
  82. ksadk/conversations/context.py +126 -6
  83. ksadk/conversations/message_projection.py +292 -28
  84. ksadk/conversations/model_context.py +49 -3
  85. ksadk/conversations/runtime_compaction.py +207 -5
  86. ksadk/conversations/runtime_input.py +193 -3
  87. ksadk/conversations/runtime_invocation.py +81 -3
  88. ksadk/conversations/runtime_observability.py +157 -4
  89. ksadk/conversations/runtime_payloads.py +42 -0
  90. ksadk/conversations/runtime_preparation.py +280 -2
  91. ksadk/conversations/runtime_resume.py +71 -32
  92. ksadk/conversations/runtime_stream_events.py +94 -9
  93. ksadk/conversations/runtime_streaming.py +175 -22
  94. ksadk/conversations/semantic_summary.py +311 -1
  95. ksadk/conversations/session_lock.py +51 -0
  96. ksadk/deployment/env_forward.py +72 -0
  97. ksadk/deployment/managed_runtime.py +38 -0
  98. ksadk/deployment/manager.py +24 -8
  99. ksadk/deployment/providers/serverless.py +228 -92
  100. ksadk/evaluation/__init__.py +116 -0
  101. ksadk/evaluation/a2a_adapter.py +452 -0
  102. ksadk/evaluation/adapters.py +67 -0
  103. ksadk/evaluation/agent_eval_client.py +395 -0
  104. ksadk/evaluation/cloud_binding.py +80 -0
  105. ksadk/evaluation/cloud_converter.py +194 -0
  106. ksadk/evaluation/cloud_service.py +199 -0
  107. ksadk/evaluation/contracts.py +478 -0
  108. ksadk/evaluation/evalset.py +351 -0
  109. ksadk/evaluation/evaluators.py +627 -0
  110. ksadk/evaluation/evidence.py +258 -0
  111. ksadk/evaluation/executor.py +143 -0
  112. ksadk/evaluation/local_adapter.py +550 -0
  113. ksadk/evaluation/service_env.py +27 -0
  114. ksadk/evaluation/storage.py +68 -0
  115. ksadk/evaluation/studio_build_adapter.py +253 -0
  116. ksadk/evaluation/target.py +82 -0
  117. ksadk/events/__init__.py +13 -13
  118. ksadk/events/_v1_compat/__init__.py +1 -0
  119. ksadk/events/_v1_compat/models.py +299 -0
  120. ksadk/events/_v1_compat/parser.py +247 -0
  121. ksadk/events/_v1_compat/projection.py +753 -0
  122. ksadk/events/adapters/__init__.py +34 -0
  123. ksadk/events/adapters/_a2a_snapshot.py +622 -0
  124. ksadk/events/adapters/_a2a_support.py +275 -0
  125. ksadk/events/adapters/_codex_interactions.py +375 -0
  126. ksadk/events/adapters/_codex_items.py +801 -0
  127. ksadk/events/adapters/_codex_validators.py +311 -0
  128. ksadk/events/adapters/_langgraph_support.py +785 -0
  129. ksadk/events/adapters/a2a.py +841 -0
  130. ksadk/events/adapters/adk.py +503 -0
  131. ksadk/events/adapters/codex.py +717 -0
  132. ksadk/events/adapters/langgraph.py +745 -0
  133. ksadk/events/canonical.py +440 -0
  134. ksadk/events/canonical_replay.py +497 -0
  135. ksadk/events/canonical_store.py +468 -0
  136. ksadk/events/cold_recovery.py +257 -0
  137. ksadk/events/content.py +89 -0
  138. ksadk/events/identity.py +86 -0
  139. ksadk/events/pipeline.py +455 -0
  140. ksadk/events/projections.py +66 -0
  141. ksadk/events/reducer.py +661 -0
  142. ksadk/events/replay.py +3 -36
  143. ksadk/events/runtime_event.py +20 -256
  144. ksadk/events/session_event.py +324 -0
  145. ksadk/events/store.py +3 -316
  146. ksadk/events/v1_compat.py +41 -0
  147. ksadk/harness/__init__.py +3 -2
  148. ksadk/harness/app.py +114 -123
  149. ksadk/harness/config.py +1 -1
  150. ksadk/harness/runtime.py +467 -0
  151. ksadk/hermes_terminal.py +5 -0
  152. ksadk/identity/resolver.py +4 -3
  153. ksadk/interaction/__init__.py +28 -0
  154. ksadk/interaction/contracts.py +136 -0
  155. ksadk/interaction/ledger.py +189 -0
  156. ksadk/interaction/provider.py +122 -0
  157. ksadk/interaction/providers/__init__.py +52 -0
  158. ksadk/interaction/providers/adk.py +40 -0
  159. ksadk/interaction/providers/codex.py +94 -0
  160. ksadk/interaction/providers/langgraph.py +58 -0
  161. ksadk/kernel/__init__.py +117 -0
  162. ksadk/kernel/authorization.py +258 -0
  163. ksadk/kernel/bootstrap.py +1074 -0
  164. ksadk/kernel/contract_fingerprints.py +61 -0
  165. ksadk/kernel/contracts.py +362 -0
  166. ksadk/kernel/control.py +219 -0
  167. ksadk/kernel/errors.py +79 -0
  168. ksadk/kernel/ingress.py +1102 -0
  169. ksadk/kernel/mapping.py +111 -0
  170. ksadk/kernel/memory_store.py +1133 -0
  171. ksadk/kernel/postgres_store.py +1756 -0
  172. ksadk/kernel/recovery.py +452 -0
  173. ksadk/kernel/runtime_identity.py +103 -0
  174. ksadk/kernel/sql/001_agent_kernel.sql +120 -0
  175. ksadk/kernel/sqlite_store.py +1410 -0
  176. ksadk/kernel/state.py +145 -0
  177. ksadk/kernel/store.py +232 -0
  178. ksadk/kernel/worker.py +953 -0
  179. ksadk/knowledge_base/client.py +22 -4
  180. ksadk/knowledge_base/service.py +32 -5
  181. ksadk/managed_a2a_card.py +160 -0
  182. ksadk/managed_runtime.py +16 -9
  183. ksadk/memory/__init__.py +61 -0
  184. ksadk/memory/adk/backends/base_ltm_backend.py +96 -1
  185. ksadk/memory/adk/backends/http_ltm_backend.py +14 -5
  186. ksadk/memory/adk/backends/inmemory_ltm_backend.py +136 -3
  187. ksadk/memory/adk/backends/sdk_ltm_backend.py +394 -5
  188. ksadk/memory/adk/backends/sqlite_ltm_backend.py +95 -0
  189. ksadk/memory/adk/long_term_memory.py +3 -2
  190. ksadk/memory/coordinator.py +461 -0
  191. ksadk/memory/events.py +202 -0
  192. ksadk/memory/extraction.py +228 -0
  193. ksadk/memory/ltm_backend_factory.py +16 -3
  194. ksadk/memory/models.py +316 -0
  195. ksadk/memory/policy.py +215 -0
  196. ksadk/memory/provider.py +47 -0
  197. ksadk/memory/provider_adapter.py +159 -0
  198. ksadk/memory/provider_resolver.py +71 -0
  199. ksadk/memory/providers/__init__.py +5 -0
  200. ksadk/memory/providers/local_sqlite.py +490 -0
  201. ksadk/memory/resolved_policy.py +145 -0
  202. ksadk/memory/service.py +198 -8
  203. ksadk/memory/tool.py +4 -0
  204. ksadk/model_proxy/bootstrap.py +16 -9
  205. ksadk/model_proxy/config.py +22 -1
  206. ksadk/model_proxy/detect.py +202 -9
  207. ksadk/model_proxy/namespace.py +67 -15
  208. ksadk/model_proxy/server.py +168 -6
  209. ksadk/model_proxy/transform.py +105 -3
  210. ksadk/observability/__init__.py +25 -0
  211. ksadk/observability/session_log.py +420 -0
  212. ksadk/observability/trajectory.py +105 -0
  213. ksadk/prompts/__init__.py +71 -0
  214. ksadk/prompts/compiler.py +215 -0
  215. ksadk/prompts/models.py +82 -0
  216. ksadk/prompts/projection.py +89 -0
  217. ksadk/prompts/resolved.py +137 -0
  218. ksadk/prompts/sources.py +233 -0
  219. ksadk/runners/_langgraph_runner_streams.py +822 -0
  220. ksadk/runners/adk_runner.py +14 -31
  221. ksadk/runners/base_runner.py +44 -3
  222. ksadk/runners/factory.py +3 -3
  223. ksadk/runners/langgraph_runner.py +252 -433
  224. ksadk/runners/utils/__init__.py +0 -10
  225. ksadk/runners/utils/loader.py +16 -0
  226. ksadk/runtime/__init__.py +67 -41
  227. ksadk/runtime/_runner_adapter/__init__.py +1 -0
  228. ksadk/runtime/_runner_adapter/stream_mapping.py +788 -0
  229. ksadk/runtime/adapter.py +199 -16
  230. ksadk/runtime/conversation_execution.py +687 -0
  231. ksadk/runtime/executor.py +392 -0
  232. ksadk/runtime/factory.py +342 -0
  233. ksadk/runtime/framework_adapters.py +6 -12
  234. ksadk/runtime/hosted_finalizer.py +268 -0
  235. ksadk/runtime/launch.py +61 -0
  236. ksadk/runtime/preprocessing.py +49 -14
  237. ksadk/runtime/runner_adapter.py +330 -391
  238. ksadk/runtime/runner_loading.py +36 -0
  239. ksadk/runtime/usage.py +33 -0
  240. ksadk/sandbox/backends/e2b.py +36 -3
  241. ksadk/server/__init__.py +39 -4
  242. ksadk/server/app.py +18 -492
  243. ksadk/server/composition.py +36 -2
  244. ksadk/server/factory.py +210 -149
  245. ksadk/server/routes/common.py +15 -46
  246. ksadk/server/routes/control.py +13 -9
  247. ksadk/server/routes/kernel_ingress.py +258 -0
  248. ksadk/server/routes/misc.py +10 -33
  249. ksadk/server/routes/models.py +14 -6
  250. ksadk/server/routes/openai_compat.py +129 -27
  251. ksadk/server/routes/projection.py +84 -16
  252. ksadk/server/routes/run.py +347 -460
  253. ksadk/server/routes/sessions.py +37 -40
  254. ksadk/server/routes/streaming.py +52 -7
  255. ksadk/server/routes/workspace.py +54 -21
  256. ksadk/server/static/assets/{ArtifactsPanel-Cjnqt-8W.js → ArtifactsPanel-DSU1sWD_.js} +1 -1
  257. ksadk/server/static/assets/{CodeBlock-BqyqZfuB.js → CodeBlock-DOH6MVcn.js} +4 -4
  258. ksadk/server/static/assets/{MathMessageMarkdown-BpH7jiFM.js → MathMessageMarkdown-BL8my1R2.js} +4 -4
  259. ksadk/server/static/assets/MathMessageMarkdown-BorAY7qD.css +1 -0
  260. ksadk/server/static/assets/MermaidBlock-Dz4IP-Tx.js +312 -0
  261. ksadk/server/static/assets/NativeTerminalPanel-DUrK0JpZ.js +1 -0
  262. ksadk/server/static/assets/abnfDiagram-VCTEODGH-g20pFzNV.js +1 -0
  263. ksadk/server/static/assets/arc-C4FzinUA.js +1 -0
  264. ksadk/server/static/assets/architecture-7GRP2DOG-DjKratdf.js +1 -0
  265. ksadk/server/static/assets/architectureDiagram-5GKGNRK7-DpIqL5h4.js +36 -0
  266. ksadk/server/static/assets/blockDiagram-NRAW4CY4-BjkljTNz.js +129 -0
  267. ksadk/server/static/assets/c4Diagram-UCG6FXSJ-Dk_ieq2X.js +38 -0
  268. ksadk/server/static/assets/channel-4cQHKtx1.js +1 -0
  269. ksadk/server/static/assets/chunk-2Q5K7J3B-DmgWkESh.js +1 -0
  270. ksadk/server/static/assets/{chunk-FMBD7UC4-BGhvzDYt.js → chunk-5VM5RSS4-BVHAchFb.js} +1 -1
  271. ksadk/server/static/assets/{chunk-ND2GUHAM-D98vPd-E.js → chunk-F27PBJKO-C-ipQzuS.js} +1 -1
  272. ksadk/server/static/assets/chunk-G27WJ6UU-C2EyJbIK.js +231 -0
  273. ksadk/server/static/assets/{chunk-4BX2VUAB-CsWIQRhc.js → chunk-JWPE2WC7-vYvVJb_M.js} +1 -1
  274. ksadk/server/static/assets/chunk-LCL6LL3I-BcI6ysPT.js +206 -0
  275. ksadk/server/static/assets/chunk-POPQ4Y6H-C030x_Z1.js +1 -0
  276. ksadk/server/static/assets/chunk-RHFEMEQ7-yyGpsz4n.js +168 -0
  277. ksadk/server/static/assets/chunk-SVP7TREG-BLTlmMU7.js +88 -0
  278. ksadk/server/static/assets/chunk-XXDRQBXY-C_32ArgP.js +1 -0
  279. ksadk/server/static/assets/classDiagram-DTDB5LWJ-DLFd9Xi0.js +1 -0
  280. ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-DLFd9Xi0.js +1 -0
  281. ksadk/server/static/assets/{cose-bilkent-S5V4N54A-CX3yotHq.js → cose-bilkent-JH36ORCC-9YQYwdl0.js} +1 -1
  282. ksadk/server/static/assets/cynefin-OW5HDTMX-DjEM48Qp.js +1 -0
  283. ksadk/server/static/assets/cynefinDiagram-5FMLGOSQ-ErLF5N13.js +62 -0
  284. ksadk/server/static/assets/cytoscape.esm-CyCl8rPi.js +321 -0
  285. ksadk/server/static/assets/dagre-3AP2YEHR-DFiQF-6f.js +4 -0
  286. ksadk/server/static/assets/dagre-BuhGnRI1.js +1 -0
  287. ksadk/server/static/assets/diagram-S7CK7UJ4-Bt1v8-GC.js +30 -0
  288. ksadk/server/static/assets/diagram-UQ7AKVKN-DSCxJdBK.js +41 -0
  289. ksadk/server/static/assets/diagram-VSXAHHWV-DHfYwp_9.js +3 -0
  290. ksadk/server/static/assets/diagram-VX7I27RA-DdzD-4Le.js +24 -0
  291. ksadk/server/static/assets/diagram-Z3DM3KII-Du-nAJ9F.js +24 -0
  292. ksadk/server/static/assets/{dist-D82mtD2s.js → dist-8hqcx0sU.js} +1 -1
  293. ksadk/server/static/assets/dist-B7seoj3d.js +1 -0
  294. ksadk/server/static/assets/{dist-DHn6nBzr.js → dist-BE7bu-DL.js} +1 -1
  295. ksadk/server/static/assets/{dist-eoW6tZXA.js → dist-BVDffZ0U.js} +1 -1
  296. ksadk/server/static/assets/{dist-C2xjE7cO.js → dist-BX8z72IC.js} +1 -1
  297. ksadk/server/static/assets/{dist-CgF3fj15.js → dist-Bf2M8m3N.js} +1 -1
  298. ksadk/server/static/assets/{dist-Bs-jQcRX.js → dist-BjU6y-A2.js} +1 -1
  299. ksadk/server/static/assets/{dist-Djgl2wVZ.js → dist-BytlxIDT.js} +1 -1
  300. ksadk/server/static/assets/{dist-CVQRZjPu.js → dist-CSh_DE14.js} +1 -1
  301. ksadk/server/static/assets/{dist-6CVKuTAe.js → dist-CXYYBw3_.js} +1 -1
  302. ksadk/server/static/assets/{dist-DlzCRy0S.js → dist-C_wsv-Qd.js} +1 -1
  303. ksadk/server/static/assets/{dist-DF06ORQo.js → dist-ClxGviKw.js} +1 -1
  304. ksadk/server/static/assets/{dist-Cf23NY4L.js → dist-CttYUqzS.js} +1 -1
  305. ksadk/server/static/assets/{dist-ivOya1LD.js → dist-DNp6rLRA.js} +1 -1
  306. ksadk/server/static/assets/{dist-BibcG1V9.js → dist-DUX-id_F.js} +1 -1
  307. ksadk/server/static/assets/{dist-BUHRxvDG.js → dist-DmldrHd_.js} +1 -1
  308. ksadk/server/static/assets/{dist-BZ9n9AjO.js → dist-DnfXs8Vn.js} +2 -2
  309. ksadk/server/static/assets/{dist-DqNnNFrw.js → dist-Dq9gLD7L.js} +1 -1
  310. ksadk/server/static/assets/{dist-CGcp2ex8.js → dist-W-TIVntx.js} +1 -1
  311. ksadk/server/static/assets/{dist-Cwt7koer.js → dist-dDdADKFA.js} +1 -1
  312. ksadk/server/static/assets/{dist-C7-3jInC.js → dist-dydu68Fl.js} +1 -1
  313. ksadk/server/static/assets/{dist-C99THtIA.js → dist-ngtN0DJB.js} +1 -1
  314. ksadk/server/static/assets/ebnfDiagram-PWID7BFC-Da9C9NO1.js +1 -0
  315. ksadk/server/static/assets/erDiagram-SSCWMZ5O-EAGqqR79.js +99 -0
  316. ksadk/server/static/assets/eventmodeling-NTZA5JFV-CMgMaJrC.js +1 -0
  317. ksadk/server/static/assets/flowDiagram-A5DVABFB-BBKL0x3P.js +1 -0
  318. ksadk/server/static/assets/ganttDiagram-EL5Y4UJY-DqJsKb59.js +292 -0
  319. ksadk/server/static/assets/gitGraph-4MIJSDKK-Dw63mrhw.js +1 -0
  320. ksadk/server/static/assets/gitGraphDiagram-WWUBYQGX-B6g4dtDi.js +106 -0
  321. ksadk/server/static/assets/index-8ipRcQ-M.js +240 -0
  322. ksadk/server/static/assets/index-ByFCqjlh.css +2 -0
  323. ksadk/server/static/assets/info-A6RAGUB7-B6iAblII.js +1 -0
  324. ksadk/server/static/assets/infoDiagram-RXCK75RN-Djm_nbd5.js +2 -0
  325. ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-DLOHGvWw.js → ishikawaDiagram-5VMMS53U-DWya9SG2.js} +6 -6
  326. ksadk/server/static/assets/{journeyDiagram-JHISSGLW-BIzKeejT.js → journeyDiagram-EYS64GPL-DutxhSNI.js} +6 -6
  327. ksadk/server/static/assets/{kanban-definition-UN3LZRKU-BWHoFSw2.js → kanban-definition-3QL26DDD-DJdOF8Fm.js} +10 -10
  328. ksadk/server/static/assets/katex-vFWytM5c.js +257 -0
  329. ksadk/server/static/assets/{linear-CvMBw9cA.js → linear-BHjG8b-J.js} +1 -1
  330. ksadk/server/static/assets/mermaid-parser.core-KGSy4jWT.js +166 -0
  331. ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-CSAQxEAe.js → mindmap-definition-FBJOCRG2-9-HC88D4.js} +29 -29
  332. ksadk/server/static/assets/packet-AYTQ26CC-BOYAipuq.js +1 -0
  333. ksadk/server/static/assets/pegDiagram-XKGWAZYB-DrEZd4fD.js +1 -0
  334. ksadk/server/static/assets/pie-WAS4IAKB-Ctq0Kbku.js +1 -0
  335. ksadk/server/static/assets/pieDiagram-E7YTZNPT-DpjqOFFp.js +39 -0
  336. ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-CxIR4JXI.js → quadrantDiagram-AXDQQJYC-BiNhgOXX.js} +6 -6
  337. ksadk/server/static/assets/radar-RG4KPBEZ-DN0-ul_0.js +1 -0
  338. ksadk/server/static/assets/railroad-74A4TZTK-C6cC_to0.js +1 -0
  339. ksadk/server/static/assets/railroad-abnf-HS5TGJTU-BimjIuRc.js +1 -0
  340. ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-CvY65ABq.js +1 -0
  341. ksadk/server/static/assets/railroad-peg-WCYAUIDC-D72Iq_Tv.js +1 -0
  342. ksadk/server/static/assets/railroadDiagram-O6MQD6OU-_Y3Ojg6G.js +1 -0
  343. ksadk/server/static/assets/requirementDiagram-EFPCY7ZU-DeCFdg9K.js +84 -0
  344. ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-DpGa6EAV.js → sankeyDiagram-P5KCCOFB-CPH75rhw.js} +7 -7
  345. ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-NTKNGGe5.js → sequenceDiagram-WJ2MYXX4-OEpQQdr4.js} +10 -10
  346. ksadk/server/static/assets/sizeCapture-X5ZJPWSS-heUErzhI.js +1 -0
  347. ksadk/server/static/assets/stateDiagram-HBIQ2CUA-DvA3jSMB.js +1 -0
  348. ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BgQY03nz.js +1 -0
  349. ksadk/server/static/assets/swimlanes-XN3QIQJK-CR-dfN_t.js +1 -0
  350. ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-DLVw3q5Q.js +8 -0
  351. ksadk/server/static/assets/{timeline-definition-PNZ67QCA-jqoYs3jQ.js → timeline-definition-24CTP7MA-DAVJf1TX.js} +7 -7
  352. ksadk/server/static/assets/treeView-Q6P3EWNA-BMpO0wsB.js +1 -0
  353. ksadk/server/static/assets/treemap-WGGIJYW6-Et0aqzU3.js +1 -0
  354. ksadk/server/static/assets/vennDiagram-4TSXK5OY-DwM8eDkh.js +34 -0
  355. ksadk/server/static/assets/wardley-WFR3VGLG-CbY0mhpl.js +1 -0
  356. ksadk/server/static/assets/wardleyDiagram-VM6X3IG4-DZL-Zz2t.js +78 -0
  357. ksadk/server/static/assets/xychartDiagram-S5SC5T6Z-Dy6Yh_hu.js +7 -0
  358. ksadk/server/static/index.html +2 -2
  359. ksadk/sessions/__init__.py +26 -9
  360. ksadk/sessions/_local_service_sync.py +688 -0
  361. ksadk/sessions/_local_tables.py +21 -0
  362. ksadk/sessions/_postgres_schema.py +305 -0
  363. ksadk/sessions/_postgres_tables.py +15 -0
  364. ksadk/sessions/base.py +80 -1
  365. ksadk/sessions/in_memory.py +53 -2
  366. ksadk/sessions/local_service.py +63 -604
  367. ksadk/sessions/postgres_service.py +58 -148
  368. ksadk/sessions/resilient.py +35 -1
  369. ksadk/skills/runtime/backends/e2b.py +1 -1
  370. ksadk/skills/service_client.py +13 -3
  371. ksadk/studio/__init__.py +5 -0
  372. ksadk/studio/agent_avatar_assets.py +120 -0
  373. ksadk/studio/agent_lifecycle.py +51 -0
  374. ksadk/studio/api.py +1598 -0
  375. ksadk/studio/api_catalog_routes.py +324 -0
  376. ksadk/studio/api_contracts.py +286 -0
  377. ksadk/studio/api_helpers.py +132 -0
  378. ksadk/studio/api_memory_routes.py +82 -0
  379. ksadk/studio/authoring.py +749 -0
  380. ksadk/studio/authoring_coordinator.py +1017 -0
  381. ksadk/studio/builder.py +397 -0
  382. ksadk/studio/capabilities.py +368 -0
  383. ksadk/studio/cloud.py +2099 -0
  384. ksadk/studio/codex_agent_service.py +694 -0
  385. ksadk/studio/codex_authoring.py +469 -0
  386. ksadk/studio/codex_builder.py +437 -0
  387. ksadk/studio/codex_manifest.py +307 -0
  388. ksadk/studio/codex_run.py +395 -0
  389. ksadk/studio/compiler.py +190 -0
  390. ksadk/studio/contracts.py +755 -0
  391. ksadk/studio/errors.py +47 -0
  392. ksadk/studio/event_store.py +214 -0
  393. ksadk/studio/framework_run.py +342 -0
  394. ksadk/studio/hosted_kernel.py +317 -0
  395. ksadk/studio/identifiers.py +33 -0
  396. ksadk/studio/manifest_resolver.py +131 -0
  397. ksadk/studio/mcp_runtime.py +229 -0
  398. ksadk/studio/model_client.py +629 -0
  399. ksadk/studio/model_profile_service.py +214 -0
  400. ksadk/studio/operations.py +275 -0
  401. ksadk/studio/otel_trace.py +1060 -0
  402. ksadk/studio/pagination.py +182 -0
  403. ksadk/studio/pcm_memory.py +85 -0
  404. ksadk/studio/python_tool_inspection.py +183 -0
  405. ksadk/studio/repository.py +327 -0
  406. ksadk/studio/resource_catalog.py +1260 -0
  407. ksadk/studio/run_service.py +1133 -0
  408. ksadk/studio/runtime_catalog.py +74 -0
  409. ksadk/studio/runtime_source.py +365 -0
  410. ksadk/studio/service.py +2249 -0
  411. ksadk/studio/shared_web.py +1004 -0
  412. ksadk/studio/skill_discovery.py +628 -0
  413. ksadk/studio/static/assets/PrismRenderer-IYN-ffww.js +3 -0
  414. ksadk/studio/static/assets/index-BLpcdrgW.css +1 -0
  415. ksadk/studio/static/assets/index-CYekR2Xv.js +260 -0
  416. ksadk/studio/static/assets/react-vendor-BNmTiJ-I.js +9 -0
  417. ksadk/studio/static/assets/rolldown-runtime-hePW80VL.js +1 -0
  418. ksadk/studio/static/index.html +15 -0
  419. ksadk/studio/templates.py +722 -0
  420. ksadk/studio/validator.py +265 -0
  421. ksadk/studio/workspace.py +135 -0
  422. ksadk/tools/gateway.py +9 -0
  423. ksadk/toolsets/__init__.py +27 -1
  424. ksadk/toolsets/a2a.py +426 -0
  425. ksadk/tracing/__init__.py +19 -21
  426. ksadk/tracing/setup.py +317 -403
  427. ksadk/tracing/span_utils.py +0 -44
  428. ksadk/version.py +1 -1
  429. ksadk_runtime_common/memory_backend/registry.py +56 -56
  430. ksadk_runtime_common/schemas/runtime_event_v1.json +37 -0
  431. ksadk_runtime_common/schemas/runtime_event_v2.json +271 -0
  432. ksadk_runtime_common/workspace_files/router.py +8 -2
  433. agentengine_sdk_python-0.8.0.dist-info/RECORD +0 -578
  434. ksadk/a2a/card_builder.py +0 -64
  435. ksadk/a2a/client.py +0 -309
  436. ksadk/events/parser.py +0 -191
  437. ksadk/harness/runner.py +0 -211
  438. ksadk/runners/codex_runner.py +0 -132
  439. ksadk/runners/utils/langfuse.py +0 -230
  440. ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +0 -1
  441. ksadk/server/static/assets/MermaidBlock-DWuxETaJ.js +0 -303
  442. ksadk/server/static/assets/NativeTerminalPanel-MDTopWMO.js +0 -1
  443. ksadk/server/static/assets/arc-CFKwSk_B.js +0 -1
  444. ksadk/server/static/assets/architecture-7EHR7CIX-CynqOWjZ.js +0 -1
  445. ksadk/server/static/assets/architectureDiagram-3BPJPVTR-Dej8jX8c.js +0 -36
  446. ksadk/server/static/assets/blockDiagram-GPEHLZMM-1F64jqvy.js +0 -132
  447. ksadk/server/static/assets/c4Diagram-AAUBKEIU-9q7AUAKb.js +0 -10
  448. ksadk/server/static/assets/channel-iUGv2N24.js +0 -1
  449. ksadk/server/static/assets/chunk-2J33WTMH-6qgwJm7Q.js +0 -1
  450. ksadk/server/static/assets/chunk-55IACEB6-BK8paA20.js +0 -1
  451. ksadk/server/static/assets/chunk-727SXJPM-B-JEhy7h.js +0 -206
  452. ksadk/server/static/assets/chunk-AQP2D5EJ-DYBvMHAa.js +0 -231
  453. ksadk/server/static/assets/chunk-QZHKN3VN-YQIEXeF6.js +0 -1
  454. ksadk/server/static/assets/classDiagram-4FO5ZUOK-CDAUuoXK.js +0 -1
  455. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-CDAUuoXK.js +0 -1
  456. ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +0 -321
  457. ksadk/server/static/assets/dagre-BM42HDAG-C7TguAWo.js +0 -4
  458. ksadk/server/static/assets/dagre-Bx709z4p.js +0 -1
  459. ksadk/server/static/assets/diagram-2AECGRRQ-Dd17_kws.js +0 -43
  460. ksadk/server/static/assets/diagram-5GNKFQAL-DVIClIil.js +0 -10
  461. ksadk/server/static/assets/diagram-KO2AKTUF-Df_3gYQa.js +0 -3
  462. ksadk/server/static/assets/diagram-LMA3HP47-BhLk6ZtA.js +0 -24
  463. ksadk/server/static/assets/diagram-OG6HWLK6-D4GNGvuu.js +0 -24
  464. ksadk/server/static/assets/dist-DyvYLMW9.js +0 -1
  465. ksadk/server/static/assets/erDiagram-TEJ5UH35-CnfRcY2M.js +0 -85
  466. ksadk/server/static/assets/eventmodeling-FCH6USID-ChYO-ijM.js +0 -1
  467. ksadk/server/static/assets/flowDiagram-I6XJVG4X-DGaJjGNJ.js +0 -162
  468. ksadk/server/static/assets/ganttDiagram-6RSMTGT7-DpCoB2g9.js +0 -292
  469. ksadk/server/static/assets/gitGraph-WXDBUCRP-BN_0Fk-j.js +0 -1
  470. ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-9IKVFBVT.js +0 -106
  471. ksadk/server/static/assets/graphlib-B8gBHxth.js +0 -1
  472. ksadk/server/static/assets/index-QSwCHuB8.js +0 -177
  473. ksadk/server/static/assets/index-jOPmTOZH.css +0 -2
  474. ksadk/server/static/assets/info-J43DQDTF-Cp6o3vId.js +0 -1
  475. ksadk/server/static/assets/infoDiagram-5YYISTIA-CYIIsb8y.js +0 -2
  476. ksadk/server/static/assets/katex-DATssjp1.js +0 -265
  477. ksadk/server/static/assets/mermaid-parser.core-mGAcEwcq.js +0 -161
  478. ksadk/server/static/assets/packet-YPE3B663-wQzct3cT.js +0 -1
  479. ksadk/server/static/assets/pie-LRSECV5Y-BgTQiv-9.js +0 -1
  480. ksadk/server/static/assets/pieDiagram-4H26LBE5-By-ra07M.js +0 -30
  481. ksadk/server/static/assets/radar-GUYGQ44K-BIq5fXsn.js +0 -1
  482. ksadk/server/static/assets/requirementDiagram-4Y6WPE33-DpW90iu3.js +0 -84
  483. ksadk/server/static/assets/stateDiagram-AJRCARHV-DGpd9yFG.js +0 -1
  484. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-B3o579IQ.js +0 -1
  485. ksadk/server/static/assets/treeView-BLDUP644-Dqi-E0EM.js +0 -1
  486. ksadk/server/static/assets/treemap-LRROVOQU-ClQP9CJ9.js +0 -1
  487. ksadk/server/static/assets/vennDiagram-CIIHVFJN-l3Z7-wbP.js +0 -34
  488. ksadk/server/static/assets/wardley-L42UT6IY-8XLvnNIs.js +0 -1
  489. ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-BR54GPmr.js +0 -78
  490. ksadk/server/static/assets/xychartDiagram-2RQKCTM6-SkiH8GsK.js +0 -7
  491. ksadk/tracing/exporters/langfuse_exporter.py +0 -460
  492. {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/entry_points.txt +0 -0
  493. {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/licenses/LICENSE +0 -0
  494. {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,749 @@
1
+ """Inspect-first Agent authoring workflows used by AgentKit Studio.
2
+
3
+ Every flow returns a proposal or inspection before it mutates the canonical
4
+ Agent registry. Quick creation is the only one-step flow and still delegates
5
+ ID allocation to the server.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import copy
11
+ import hashlib
12
+ import io
13
+ import json
14
+ import re
15
+ import shutil
16
+ import stat
17
+ import zipfile
18
+ from dataclasses import dataclass
19
+ from pathlib import Path, PurePosixPath
20
+ from typing import Any, Literal, cast
21
+ from urllib.parse import urlparse
22
+ from uuid import uuid4
23
+
24
+ import yaml # type: ignore[import-untyped]
25
+ from pydantic import BaseModel, ConfigDict, Field, ValidationError, model_validator
26
+
27
+ from ksadk.detection.detector import FrameworkDetector
28
+ from ksadk.managed_runtime import installed_runtime_version
29
+ from ksadk.studio.capabilities import canonical_json, sha256_digest
30
+ from ksadk.studio.codex_manifest import CodexAgentManifest
31
+ from ksadk.studio.contracts import (
32
+ AgentDraft,
33
+ AgentSpec,
34
+ NetworkPolicy,
35
+ RuntimeRef,
36
+ )
37
+ from ksadk.studio.errors import StudioError
38
+ from ksadk.studio.workspace import Workspace
39
+
40
+ _SLUG = re.compile(r"[^a-z0-9]+")
41
+ _MAX_IMPORT_BYTES = 100 * 1024 * 1024
42
+ _MAX_IMPORT_FILES = 2000
43
+ _SUPPORTED_RUNTIMES = frozenset({"codex", "adk", "langgraph"})
44
+
45
+
46
+ class ConversationProposal(BaseModel):
47
+ model_config = ConfigDict(extra="forbid")
48
+
49
+ name: str = Field(min_length=1, max_length=128)
50
+ slug: str = Field(min_length=1, max_length=63)
51
+ runtimeType: Literal["codex", "adk", "langgraph"]
52
+ description: str = Field(default="", max_length=1024)
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
80
+
81
+
82
+ @dataclass(frozen=True)
83
+ class ImportInspection:
84
+ token: str
85
+ kind: Literal["agent-draft", "codex-manifest"]
86
+ payload: dict[str, Any]
87
+ runtime_type: str
88
+ display_name: str
89
+ source_digest: str
90
+ files: tuple[str, ...]
91
+ staging_directory: Path
92
+ manifest_directory: PurePosixPath
93
+
94
+
95
+ class AgentAuthoringService:
96
+ """Pure inspection and staging support; StudioService owns final commits."""
97
+
98
+ def __init__(self, workspace: Workspace) -> None:
99
+ self.workspace = workspace
100
+ self.root = workspace.resolve(".agentkit/authoring")
101
+ self.import_root = workspace.resolve(".agentkit/authoring/imports")
102
+ self.project_root = workspace.resolve(".agentkit/authoring/projects")
103
+ self.import_root.mkdir(parents=True, exist_ok=True)
104
+ self.project_root.mkdir(parents=True, exist_ok=True)
105
+
106
+ @staticmethod
107
+ def normalize_slug(value: str) -> str:
108
+ normalized = _SLUG.sub("-", str(value or "").strip().lower()).strip("-")
109
+ if not normalized or not normalized[0].isalpha():
110
+ normalized = f"agent-{normalized}" if normalized else "agent"
111
+ return normalized[:48].rstrip("-")
112
+
113
+ def allocate_agent_id(self, slug: str) -> str:
114
+ base = self.normalize_slug(slug)
115
+ for _attempt in range(100):
116
+ candidate = f"{base}-{uuid4().hex[:12]}"
117
+ if not self.workspace.resolve(Path("agents") / candidate).exists():
118
+ return candidate
119
+ raise StudioError(
120
+ "AGENT_ID_ALLOCATION_FAILED",
121
+ "无法分配唯一 Agent ID",
122
+ status_code=500,
123
+ )
124
+
125
+ @staticmethod
126
+ def runtime_ref(agent_id: str, runtime_type: str) -> RuntimeRef:
127
+ normalized = str(runtime_type or "").strip().lower()
128
+ if normalized not in _SUPPORTED_RUNTIMES:
129
+ raise StudioError(
130
+ "RUNTIME_NOT_SUPPORTED",
131
+ "创建方式仅支持 Codex、ADK 和 LangGraph",
132
+ status_code=422,
133
+ field="runtimeType",
134
+ details={"runtimeType": normalized},
135
+ )
136
+ if normalized == "codex":
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
+ )
144
+ return RuntimeRef(
145
+ type=cast(Any, normalized),
146
+ project_path=f"agents/{agent_id}/source",
147
+ entry_point="agent.py",
148
+ agent_variable="graph" if normalized == "langgraph" else "root_agent",
149
+ )
150
+
151
+ def inspect_import(self, content: bytes, *, filename: str) -> dict[str, Any]:
152
+ if not content:
153
+ raise StudioError(
154
+ "AGENT_IMPORT_EMPTY",
155
+ "导入文件不能为空",
156
+ status_code=422,
157
+ )
158
+ if len(content) > _MAX_IMPORT_BYTES:
159
+ raise StudioError(
160
+ "AGENT_IMPORT_TOO_LARGE",
161
+ "Agent 导入包不能超过 100 MiB",
162
+ status_code=413,
163
+ )
164
+ token = f"imp_{uuid4().hex}"
165
+ staging = self.workspace.resolve(self.import_root / token)
166
+ staging.mkdir(parents=True, exist_ok=False)
167
+ try:
168
+ files, manifest_path = self._stage_import(staging, content, filename=filename)
169
+ payload = self._load_yaml_object(manifest_path)
170
+ kind, runtime_type, display_name = self._classify_import(payload)
171
+ manifest_relative = manifest_path.relative_to(staging)
172
+ record = {
173
+ "format": "agentkit.authoring-import/v1",
174
+ "token": token,
175
+ "kind": kind,
176
+ "runtimeType": runtime_type,
177
+ "displayName": display_name,
178
+ "sourceDigest": self._tree_digest(staging, exclude={"inspection.json"}),
179
+ "files": files,
180
+ "manifestPath": manifest_relative.as_posix(),
181
+ "manifestDirectory": manifest_relative.parent.as_posix(),
182
+ "payload": payload,
183
+ }
184
+ self.workspace.atomic_write_text(
185
+ staging / "inspection.json",
186
+ json.dumps(record, ensure_ascii=False, sort_keys=True, indent=2) + "\n",
187
+ )
188
+ return self._public_import_record(record)
189
+ except BaseException:
190
+ shutil.rmtree(staging, ignore_errors=True)
191
+ raise
192
+
193
+ def load_import(self, token: str) -> ImportInspection:
194
+ staging = self._token_directory(self.import_root, token, prefix="imp_")
195
+ record_path = staging / "inspection.json"
196
+ try:
197
+ record = json.loads(record_path.read_text(encoding="utf-8"))
198
+ except (OSError, ValueError) as exc:
199
+ raise StudioError(
200
+ "AGENT_IMPORT_INSPECTION_NOT_FOUND",
201
+ "导入检查不存在或已失效",
202
+ status_code=404,
203
+ ) from exc
204
+ expected = str(record.get("sourceDigest") or "")
205
+ actual = self._tree_digest(staging, exclude={"inspection.json"})
206
+ if not expected or expected != actual:
207
+ raise StudioError(
208
+ "AGENT_IMPORT_CHANGED",
209
+ "导入暂存内容在确认前发生变化,请重新检查",
210
+ status_code=409,
211
+ )
212
+ manifest_directory = PurePosixPath(str(record.get("manifestDirectory") or "."))
213
+ return ImportInspection(
214
+ token=token,
215
+ kind=cast(Any, record["kind"]),
216
+ payload=cast(dict[str, Any], record["payload"]),
217
+ runtime_type=str(record["runtimeType"]),
218
+ display_name=str(record["displayName"]),
219
+ source_digest=expected,
220
+ files=tuple(str(item) for item in record.get("files") or []),
221
+ staging_directory=staging,
222
+ manifest_directory=manifest_directory,
223
+ )
224
+
225
+ def consume_import(self, token: str) -> None:
226
+ staging = self._token_directory(self.import_root, token, prefix="imp_")
227
+ shutil.rmtree(staging, ignore_errors=True)
228
+
229
+ def source_directory(self, inspection: ImportInspection) -> Path | None:
230
+ if inspection.kind != "agent-draft":
231
+ return None
232
+ draft = AgentDraft.model_validate(inspection.payload)
233
+ runtime = draft.spec.runtime
234
+ if runtime is None or not runtime.project_path:
235
+ return None
236
+ candidates = [
237
+ inspection.staging_directory / inspection.manifest_directory / runtime.project_path,
238
+ inspection.staging_directory / runtime.project_path,
239
+ inspection.staging_directory / inspection.manifest_directory / "source",
240
+ ]
241
+ for candidate in candidates:
242
+ resolved = candidate.resolve()
243
+ try:
244
+ resolved.relative_to(inspection.staging_directory.resolve())
245
+ except ValueError:
246
+ continue
247
+ if resolved.is_dir():
248
+ return resolved
249
+ return None
250
+
251
+ def inspect_project(self, project_path: str) -> dict[str, Any]:
252
+ project = self.workspace.resolve(project_path, must_exist=True)
253
+ if not project.is_dir():
254
+ raise StudioError(
255
+ "PROJECT_PATH_INVALID",
256
+ "项目识别路径必须是工作区内的目录",
257
+ status_code=422,
258
+ )
259
+ detection = FrameworkDetector(str(project)).detect()
260
+ runtime_type = detection.type.value
261
+ if not detection.is_valid or runtime_type not in _SUPPORTED_RUNTIMES:
262
+ raise StudioError(
263
+ "PROJECT_RUNTIME_UNSUPPORTED",
264
+ "没有识别到可由 Studio 管理的 Codex、ADK 或 LangGraph Runtime",
265
+ status_code=422,
266
+ details={"detected": runtime_type},
267
+ )
268
+ token = f"prj_{uuid4().hex}"
269
+ relative = self.workspace.relative(project)
270
+ record = {
271
+ "format": "agentkit.authoring-project/v1",
272
+ "token": token,
273
+ "projectPath": relative,
274
+ "runtimeType": runtime_type,
275
+ "name": detection.name or project.name,
276
+ "entryPoint": detection.entry_point or None,
277
+ "agentVariable": detection.agent_variable or None,
278
+ "confidence": float(detection.confidence),
279
+ "evidence": {
280
+ "config": dict(detection.raw_config or {}),
281
+ "packagePath": detection.package_path,
282
+ },
283
+ "sourceDigest": self._tree_digest(project),
284
+ "requiresConfirmation": True,
285
+ }
286
+ self.workspace.atomic_write_text(
287
+ self.project_root / f"{token}.json",
288
+ json.dumps(record, ensure_ascii=False, sort_keys=True, indent=2) + "\n",
289
+ )
290
+ return {**record, "inspectionToken": token}
291
+
292
+ def load_project(self, token: str) -> dict[str, Any]:
293
+ if not re.fullmatch(r"prj_[0-9a-f]{32}", token):
294
+ raise StudioError(
295
+ "PROJECT_INSPECTION_NOT_FOUND",
296
+ "项目识别检查不存在或已失效",
297
+ status_code=404,
298
+ )
299
+ path = self.workspace.resolve(self.project_root / f"{token}.json")
300
+ try:
301
+ record = json.loads(path.read_text(encoding="utf-8"))
302
+ except (OSError, ValueError) as exc:
303
+ raise StudioError(
304
+ "PROJECT_INSPECTION_NOT_FOUND",
305
+ "项目识别检查不存在或已失效",
306
+ status_code=404,
307
+ ) from exc
308
+ project = self.workspace.resolve(str(record["projectPath"]), must_exist=True)
309
+ if self._tree_digest(project) != record.get("sourceDigest"):
310
+ raise StudioError(
311
+ "PROJECT_CHANGED_AFTER_INSPECTION",
312
+ "项目在确认前发生变化,请重新识别",
313
+ status_code=409,
314
+ )
315
+ return cast(dict[str, Any], record)
316
+
317
+ def consume_project(self, token: str) -> None:
318
+ path = self.workspace.resolve(self.project_root / f"{token}.json")
319
+ if path.is_file():
320
+ path.unlink()
321
+
322
+ @staticmethod
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
+
397
+ text = str(content or "").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:
484
+ try:
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)
514
+ proposal = ConversationProposal.model_validate(payload)
515
+ except (ValueError, ValidationError) as exc:
516
+ raise StudioError(
517
+ "AUTHORING_MODEL_OUTPUT_INVALID",
518
+ "对话构建模型没有返回合法的 Agent Draft Patch",
519
+ status_code=502,
520
+ details={"reason": str(exc)},
521
+ ) from exc
522
+ normalized_slug = AgentAuthoringService.normalize_slug(proposal.slug)
523
+ return proposal.model_copy(update={"slug": normalized_slug})
524
+
525
+ @staticmethod
526
+ def conversation_messages(
527
+ messages: list[dict[str, str]],
528
+ *,
529
+ runtime_type: str = "codex",
530
+ ) -> list[dict[str, str]]:
531
+ if not messages:
532
+ raise StudioError(
533
+ "AUTHORING_CONVERSATION_EMPTY",
534
+ "对话构建至少需要一条消息",
535
+ status_code=422,
536
+ )
537
+ normalized: list[dict[str, str]] = [
538
+ {
539
+ "role": "system",
540
+ "content": (
541
+ "你是 AgentKit Studio 的 Agent 设计助手。根据对话生成一个 JSON Draft Patch,"
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 按用户选择注入。只提出配置,不写文件、不宣称已经创建。"
550
+ ),
551
+ }
552
+ ]
553
+ for item in messages:
554
+ role = str(item.get("role") or "").strip()
555
+ content = str(item.get("content") or "").strip()
556
+ if role not in {"user", "assistant"} or not content:
557
+ raise StudioError(
558
+ "AUTHORING_CONVERSATION_INVALID",
559
+ "对话消息只允许非空 user/assistant 文本",
560
+ status_code=422,
561
+ )
562
+ normalized.append({"role": role, "content": content})
563
+ return normalized
564
+
565
+ @staticmethod
566
+ def authoring_network_policy(endpoint_url: str) -> NetworkPolicy:
567
+ host = (urlparse(endpoint_url).hostname or "").lower().rstrip(".")
568
+ return NetworkPolicy(
569
+ mode="restricted",
570
+ allowed_hosts=[host] if host else [],
571
+ allow_private_network=False,
572
+ )
573
+
574
+ def _stage_import(
575
+ self,
576
+ staging: Path,
577
+ content: bytes,
578
+ *,
579
+ filename: str,
580
+ ) -> tuple[list[str], Path]:
581
+ if filename.lower().endswith(".zip"):
582
+ return self._extract_zip(staging, content)
583
+ if not filename.lower().endswith((".yaml", ".yml")):
584
+ raise StudioError(
585
+ "AGENT_IMPORT_FORMAT_UNSUPPORTED",
586
+ "仅支持 Agent YAML 或 ZIP",
587
+ status_code=422,
588
+ )
589
+ path = staging / "agent.yaml"
590
+ path.write_bytes(content)
591
+ return ["agent.yaml"], path
592
+
593
+ def _extract_zip(self, staging: Path, content: bytes) -> tuple[list[str], Path]:
594
+ try:
595
+ archive = zipfile.ZipFile(io.BytesIO(content))
596
+ except zipfile.BadZipFile as exc:
597
+ raise StudioError(
598
+ "AGENT_IMPORT_ARCHIVE_INVALID",
599
+ "Agent ZIP 无法解析",
600
+ status_code=422,
601
+ ) from exc
602
+ with archive:
603
+ entries = [item for item in archive.infolist() if not item.is_dir()]
604
+ if not entries or len(entries) > _MAX_IMPORT_FILES:
605
+ raise StudioError(
606
+ "AGENT_IMPORT_ARCHIVE_INVALID",
607
+ "Agent ZIP 文件数量不合法",
608
+ status_code=422,
609
+ )
610
+ expanded = sum(item.file_size for item in entries)
611
+ if expanded > _MAX_IMPORT_BYTES:
612
+ raise StudioError(
613
+ "AGENT_IMPORT_TOO_LARGE",
614
+ "Agent ZIP 解压后不能超过 100 MiB",
615
+ status_code=413,
616
+ )
617
+ files: list[str] = []
618
+ manifests: list[Path] = []
619
+ for item in entries:
620
+ path = PurePosixPath(item.filename)
621
+ mode = (item.external_attr >> 16) & 0o170000
622
+ if (
623
+ path.is_absolute()
624
+ or ".." in path.parts
625
+ or mode in {stat.S_IFLNK, stat.S_IFCHR, stat.S_IFBLK, stat.S_IFIFO}
626
+ or (item.compress_size == 0 and item.file_size > 0)
627
+ or (
628
+ item.compress_size > 0
629
+ and item.file_size / item.compress_size > 200
630
+ )
631
+ ):
632
+ raise StudioError(
633
+ "AGENT_IMPORT_ARCHIVE_UNSAFE",
634
+ "Agent ZIP 包含不安全路径、链接或压缩条目",
635
+ status_code=422,
636
+ details={"entry": item.filename},
637
+ )
638
+ target = staging / Path(*path.parts)
639
+ target.parent.mkdir(parents=True, exist_ok=True)
640
+ target.write_bytes(archive.read(item))
641
+ files.append(path.as_posix())
642
+ if path.name in {"agent.yaml", "agentengine.yaml"}:
643
+ manifests.append(target)
644
+ if len(manifests) != 1:
645
+ raise StudioError(
646
+ "AGENT_IMPORT_MANIFEST_REQUIRED",
647
+ "Agent ZIP 必须且只能包含一个 agent.yaml 或 agentengine.yaml",
648
+ status_code=422,
649
+ )
650
+ return sorted(files), manifests[0]
651
+
652
+ @staticmethod
653
+ def _load_yaml_object(path: Path) -> dict[str, Any]:
654
+ try:
655
+ payload = yaml.safe_load(path.read_text(encoding="utf-8-sig"))
656
+ except (OSError, UnicodeDecodeError, yaml.YAMLError) as exc:
657
+ raise StudioError(
658
+ "AGENT_IMPORT_MANIFEST_INVALID",
659
+ "Agent YAML 无法解析",
660
+ status_code=422,
661
+ ) from exc
662
+ if not isinstance(payload, dict):
663
+ raise StudioError(
664
+ "AGENT_IMPORT_MANIFEST_INVALID",
665
+ "Agent YAML 根节点必须是对象",
666
+ status_code=422,
667
+ )
668
+ return cast(dict[str, Any], payload)
669
+
670
+ @staticmethod
671
+ def _classify_import(payload: dict[str, Any]) -> tuple[str, str, str]:
672
+ try:
673
+ if payload.get("kind") == "Agent":
674
+ draft = AgentDraft.model_validate(payload)
675
+ if draft.spec.runtime is None:
676
+ raise ValueError("Agent Draft 缺少 spec.runtime")
677
+ return "agent-draft", draft.spec.runtime.type, draft.metadata.name
678
+ manifest = CodexAgentManifest.model_validate(payload)
679
+ return "codex-manifest", "codex", manifest.name
680
+ except (ValueError, ValidationError) as exc:
681
+ raise StudioError(
682
+ "AGENT_IMPORT_CONTRACT_INVALID",
683
+ "导入内容不符合 Agent Draft 或 Codex agentengine.yaml 合同",
684
+ status_code=422,
685
+ details={"reason": str(exc)},
686
+ ) from exc
687
+
688
+ @staticmethod
689
+ def _tree_digest(root: Path, *, exclude: set[str] | None = None) -> str:
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")
695
+ entries: list[dict[str, Any]] = []
696
+ for path in sorted(root.rglob("*"), key=lambda item: item.as_posix()):
697
+ if path.is_symlink():
698
+ raise StudioError(
699
+ "AUTHORING_SOURCE_UNSAFE",
700
+ "待检查内容包含软链接",
701
+ status_code=422,
702
+ details={"path": str(path)},
703
+ )
704
+ if not path.is_file() or path.name in ignored:
705
+ continue
706
+ # 跳过 .agentkit / .git 目录下的文件
707
+ relative = path.relative_to(root).as_posix()
708
+ if any(relative.startswith(ignored_dir + "/") for ignored_dir in (".agentkit", ".git")):
709
+ continue
710
+ content = path.read_bytes()
711
+ entries.append(
712
+ {
713
+ "path": relative,
714
+ "sha256": hashlib.sha256(content).hexdigest(),
715
+ "size": len(content),
716
+ }
717
+ )
718
+ return sha256_digest(canonical_json(entries))
719
+
720
+ def _token_directory(self, root: Path, token: str, *, prefix: str) -> Path:
721
+ if not re.fullmatch(rf"{re.escape(prefix)}[0-9a-f]{{32}}", token):
722
+ raise StudioError(
723
+ "AGENT_IMPORT_INSPECTION_NOT_FOUND",
724
+ "导入检查不存在或已失效",
725
+ status_code=404,
726
+ )
727
+ path = self.workspace.resolve(root / token)
728
+ if not path.is_dir():
729
+ raise StudioError(
730
+ "AGENT_IMPORT_INSPECTION_NOT_FOUND",
731
+ "导入检查不存在或已失效",
732
+ status_code=404,
733
+ )
734
+ return path
735
+
736
+ @staticmethod
737
+ def _public_import_record(record: dict[str, Any]) -> dict[str, Any]:
738
+ return {
739
+ "inspectionToken": record["token"],
740
+ "kind": record["kind"],
741
+ "runtimeType": record["runtimeType"],
742
+ "displayName": record["displayName"],
743
+ "sourceDigest": record["sourceDigest"],
744
+ "files": record["files"],
745
+ "requiresConfirmation": True,
746
+ }
747
+
748
+
749
+ __all__ = ["AgentAuthoringService", "ConversationProposal", "ImportInspection"]