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
@@ -0,0 +1,317 @@
1
+ """Content-addressed preflight for Studio Code bundles hosted by Agent Kernel.
2
+
3
+ This module deliberately proves only properties available in the local bundle:
4
+ the frozen Agent Kernel wire-contract digest and the launch layout generated by
5
+ Studio. Whether the target Server-selected image implements that digest remains
6
+ a separate control-plane/readiness check.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import io
12
+ import json
13
+ import zipfile
14
+ from dataclasses import dataclass
15
+ from pathlib import PurePosixPath
16
+ from typing import Any
17
+
18
+ from ksadk.studio.capabilities import canonical_json, sha256_digest
19
+ from ksadk.studio.errors import StudioError
20
+
21
+ AGENT_KERNEL_V1_CONTRACT_SET = "agent-kernel/v1"
22
+ # This mirrors contracts/agent-kernel/v1/manifest.json. A Studio test compares
23
+ # the two, so a frozen-contract update cannot leave packaged preflight stale.
24
+ AGENT_KERNEL_V1_CONTRACT_DIGEST = "47e1003e03d97abeba232cc3e03a14b9cbcf78b1109870ccd2ce371f073b6211"
25
+ HOSTED_KERNEL_REQUIREMENTS_PATH = "hosted-kernel-requirements.json"
26
+ HOSTED_KERNEL_REQUIREMENTS_FORMAT = "agentkit.hosted-kernel-requirements/v1"
27
+ HOSTED_KERNEL_RUNTIME_CONTRACT = "agentkit.runtime/v1"
28
+ HOSTED_KERNEL_BUNDLE_FORMAT = "agentkit.bundle/v2"
29
+ _RUNTIME_TYPES = frozenset({"adk", "codex", "langgraph"})
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class HostedKernelBundle:
34
+ """Facts proved from the exact ZIP bytes that will be uploaded."""
35
+
36
+ manifest: dict[str, Any]
37
+ provenance: dict[str, Any]
38
+ requirement: dict[str, Any]
39
+ requirement_digest: str
40
+
41
+
42
+ def build_hosted_kernel_requirement(
43
+ *,
44
+ runtime_type: str,
45
+ entry_point: str | None,
46
+ agent_variable: str | None,
47
+ launch_config: bytes | None,
48
+ ) -> dict[str, Any]:
49
+ """Build the immutable local requirement embedded into every Studio ZIP."""
50
+
51
+ return {
52
+ "format": HOSTED_KERNEL_REQUIREMENTS_FORMAT,
53
+ "kernelContract": {
54
+ "set": AGENT_KERNEL_V1_CONTRACT_SET,
55
+ "digest": AGENT_KERNEL_V1_CONTRACT_DIGEST,
56
+ },
57
+ "bundleLayout": HOSTED_KERNEL_BUNDLE_FORMAT,
58
+ "runtimeContract": HOSTED_KERNEL_RUNTIME_CONTRACT,
59
+ "runtime": {
60
+ "type": runtime_type,
61
+ "entryPoint": entry_point or "",
62
+ "agentVariable": agent_variable or "",
63
+ "launchConfig": "runtime/agentengine.yaml" if launch_config is not None else "",
64
+ "launchConfigSha256": sha256_digest(launch_config) if launch_config is not None else "",
65
+ },
66
+ }
67
+
68
+
69
+ def hosted_kernel_requirement_digest(requirement: dict[str, Any]) -> str:
70
+ return sha256_digest(canonical_json(requirement))
71
+
72
+
73
+ def preflight_hosted_kernel_bundle(bundle: bytes) -> HostedKernelBundle:
74
+ """Reject a ZIP that cannot be proved compatible before CreateAgent.
75
+
76
+ This validates the uploaded bytes, including their file manifest, instead
77
+ of trusting a writable sibling directory from a local Build.
78
+ """
79
+
80
+ try:
81
+ with zipfile.ZipFile(io.BytesIO(bundle)) as archive:
82
+ names = _validated_zip_names(archive)
83
+ entries = {name: archive.read(name) for name in names}
84
+ except (OSError, zipfile.BadZipFile, zipfile.LargeZipFile) as error:
85
+ raise _integrity_error("Bundle 不是可验证的 ZIP 文件", reason="invalid_zip") from error
86
+
87
+ manifest = _json_object(entries, "manifest.json")
88
+ provenance = _json_object(entries, "provenance.json")
89
+ requirement = _json_object(entries, HOSTED_KERNEL_REQUIREMENTS_PATH)
90
+ requirement_digest = hosted_kernel_requirement_digest(requirement)
91
+
92
+ _validate_requirement(manifest, provenance, requirement, requirement_digest, entries)
93
+ _validate_content_manifest(manifest, entries)
94
+ _validate_declared_bundle_digest(manifest)
95
+ return HostedKernelBundle(
96
+ manifest=manifest,
97
+ provenance=provenance,
98
+ requirement=requirement,
99
+ requirement_digest=requirement_digest,
100
+ )
101
+
102
+
103
+ def _validated_zip_names(archive: zipfile.ZipFile) -> list[str]:
104
+ names = archive.namelist()
105
+ if not names:
106
+ raise _integrity_error("Bundle 为空", reason="empty_zip")
107
+ if len(names) != len(set(names)):
108
+ raise _integrity_error("Bundle 含有重复文件名", reason="duplicate_path")
109
+ for name in names:
110
+ path = PurePosixPath(name)
111
+ if (
112
+ not name
113
+ or "\\" in name
114
+ or name.endswith("/")
115
+ or path.is_absolute()
116
+ or any(part in {"", ".", ".."} for part in path.parts)
117
+ ):
118
+ raise _integrity_error("Bundle 含有不安全文件路径", reason="unsafe_path")
119
+ return names
120
+
121
+
122
+ def _json_object(entries: dict[str, bytes], path: str) -> dict[str, Any]:
123
+ raw = entries.get(path)
124
+ if raw is None:
125
+ raise _incompatible_error(f"Bundle 缺少 {path}", reason="missing_required_file")
126
+ try:
127
+ parsed = json.loads(raw.decode("utf-8"))
128
+ except (UnicodeDecodeError, json.JSONDecodeError) as error:
129
+ raise _incompatible_error(
130
+ f"Bundle 中的 {path} 不是 JSON 对象", reason="invalid_json"
131
+ ) from error
132
+ if not isinstance(parsed, dict):
133
+ raise _incompatible_error(f"Bundle 中的 {path} 不是 JSON 对象", reason="invalid_json")
134
+ return parsed
135
+
136
+
137
+ def _validate_requirement(
138
+ manifest: dict[str, Any],
139
+ provenance: dict[str, Any],
140
+ requirement: dict[str, Any],
141
+ requirement_digest: str,
142
+ entries: dict[str, bytes],
143
+ ) -> None:
144
+ if manifest.get("bundleFormat") != HOSTED_KERNEL_BUNDLE_FORMAT:
145
+ raise _incompatible_error(
146
+ "Bundle 不是受支持的 Studio Code bundle 格式", reason="bundle_format"
147
+ )
148
+ if manifest.get("runtimeContract") != HOSTED_KERNEL_RUNTIME_CONTRACT:
149
+ raise _incompatible_error(
150
+ "Bundle runtime contract 不受 Hosted Agent Kernel 支持", reason="runtime_contract"
151
+ )
152
+ if requirement.get("format") != HOSTED_KERNEL_REQUIREMENTS_FORMAT:
153
+ raise _incompatible_error(
154
+ "Bundle 缺少 Hosted Agent Kernel requirement 格式声明", reason="requirement_format"
155
+ )
156
+ if requirement.get("bundleLayout") != manifest.get("bundleFormat"):
157
+ raise _incompatible_error(
158
+ "Bundle requirement 与 bundle layout 不一致", reason="bundle_layout"
159
+ )
160
+ if requirement.get("runtimeContract") != manifest.get("runtimeContract"):
161
+ raise _incompatible_error(
162
+ "Bundle requirement 与 runtime contract 不一致", reason="runtime_contract"
163
+ )
164
+
165
+ contract = requirement.get("kernelContract")
166
+ if not isinstance(contract, dict):
167
+ raise _incompatible_error(
168
+ "Bundle 缺少 Agent Kernel contract requirement", reason="missing_contract"
169
+ )
170
+ if contract.get("set") != AGENT_KERNEL_V1_CONTRACT_SET:
171
+ raise _incompatible_error(
172
+ "Bundle Agent Kernel contract set 不受支持", reason="contract_set"
173
+ )
174
+ if contract.get("digest") != AGENT_KERNEL_V1_CONTRACT_DIGEST:
175
+ raise _incompatible_error(
176
+ "Bundle Agent Kernel contract digest 与当前 KsADK 不一致,请重新 Build 后部署",
177
+ reason="contract_digest",
178
+ )
179
+
180
+ hosted = provenance.get("hostedKernel")
181
+ if not isinstance(hosted, dict):
182
+ raise _incompatible_error(
183
+ "Bundle provenance 缺少 Hosted Agent Kernel requirement", reason="missing_provenance"
184
+ )
185
+ if hosted.get("requirementsPath") != HOSTED_KERNEL_REQUIREMENTS_PATH:
186
+ raise _incompatible_error(
187
+ "Bundle provenance 的 requirement 路径不一致", reason="provenance_path"
188
+ )
189
+ if hosted.get("requirementDigest") != requirement_digest:
190
+ raise _incompatible_error(
191
+ "Bundle provenance 的 requirement digest 不一致", reason="provenance_digest"
192
+ )
193
+ if hosted.get("contractSet") != contract.get("set") or hosted.get(
194
+ "contractDigest"
195
+ ) != contract.get("digest"):
196
+ raise _incompatible_error(
197
+ "Bundle provenance 的 Agent Kernel contract 不一致", reason="provenance_contract"
198
+ )
199
+ if manifest.get("hostedKernelRequirementDigest") != requirement_digest:
200
+ raise _incompatible_error(
201
+ "Bundle manifest 的 requirement digest 不一致", reason="manifest_digest"
202
+ )
203
+
204
+ runtime = requirement.get("runtime")
205
+ if not isinstance(runtime, dict):
206
+ raise _incompatible_error(
207
+ "Bundle 缺少受支持的 runtime 启动 requirement", reason="missing_runtime"
208
+ )
209
+ runtime_type = str(runtime.get("type") or "").strip().lower()
210
+ entry_point = str(runtime.get("entryPoint") or "").strip()
211
+ agent_variable = str(runtime.get("agentVariable") or "").strip()
212
+ launch_path = str(runtime.get("launchConfig") or "").strip()
213
+ launch_digest = str(runtime.get("launchConfigSha256") or "").strip()
214
+ if runtime_type not in _RUNTIME_TYPES:
215
+ raise _incompatible_error(
216
+ "Bundle runtime 类型不受 Hosted Agent Kernel 支持", reason="runtime_type"
217
+ )
218
+ if (
219
+ runtime_type != str(manifest.get("runtimeType") or "").strip().lower()
220
+ or not entry_point
221
+ or not agent_variable
222
+ or launch_path != "runtime/agentengine.yaml"
223
+ or not launch_digest
224
+ ):
225
+ raise _incompatible_error("Bundle 缺少可验证的 runtime 启动配置", reason="runtime_launch")
226
+ if not _safe_relative_file(entry_point) or f"runtime/{entry_point}" not in entries:
227
+ raise _incompatible_error("Bundle runtime entryPoint 不存在或不安全", reason="entry_point")
228
+ launch_bytes = entries.get(launch_path)
229
+ if launch_bytes is None or sha256_digest(launch_bytes) != launch_digest:
230
+ raise _incompatible_error("Bundle runtime 启动配置 digest 不一致", reason="launch_digest")
231
+ launch = _json_object(entries, launch_path)
232
+ if (
233
+ str(launch.get("framework") or "").strip().lower() != runtime_type
234
+ or launch.get("entry_point") != entry_point
235
+ or launch.get("agent_variable") != agent_variable
236
+ or launch.get("package") != "."
237
+ ):
238
+ raise _incompatible_error(
239
+ "Bundle runtime 启动配置与 requirement 不一致", reason="launch_config"
240
+ )
241
+ runtime_lock = _json_object(entries, "runtime-lock.json")
242
+ if (
243
+ str(runtime_lock.get("type") or "").strip().lower() != runtime_type
244
+ or runtime_lock.get("entryPoint") != entry_point
245
+ or runtime_lock.get("agentVariable") != agent_variable
246
+ ):
247
+ raise _incompatible_error(
248
+ "Bundle runtime lock 与 requirement 不一致", reason="runtime_lock"
249
+ )
250
+
251
+
252
+ def _validate_content_manifest(manifest: dict[str, Any], entries: dict[str, bytes]) -> None:
253
+ files = manifest.get("files")
254
+ if not isinstance(files, list):
255
+ raise _integrity_error("Bundle manifest 缺少文件清单", reason="missing_file_manifest")
256
+ recorded: dict[str, dict[str, Any]] = {}
257
+ for item in files:
258
+ if not isinstance(item, dict) or not isinstance(item.get("path"), str):
259
+ raise _integrity_error("Bundle manifest 的文件清单无效", reason="invalid_file_manifest")
260
+ path = item["path"]
261
+ if path in recorded or not _safe_relative_file(path) or path == "manifest.json":
262
+ raise _integrity_error("Bundle manifest 的文件路径无效", reason="invalid_file_manifest")
263
+ recorded[path] = item
264
+ if set(entries) != set(recorded) | {"manifest.json"}:
265
+ raise _integrity_error("Bundle ZIP 与 manifest 文件清单不一致", reason="file_membership")
266
+ for path, item in recorded.items():
267
+ content = entries[path]
268
+ if item.get("sha256") != sha256_digest(content) or item.get("size") != len(content):
269
+ raise _integrity_error("Bundle 文件摘要与 manifest 不一致", reason="file_digest")
270
+
271
+
272
+ def _validate_declared_bundle_digest(manifest: dict[str, Any]) -> None:
273
+ declared = manifest.get("bundleDigest")
274
+ digest_payload = dict(manifest)
275
+ digest_payload.pop("bundleDigest", None)
276
+ if declared != sha256_digest(canonical_json(digest_payload)):
277
+ raise _integrity_error("Bundle manifest 的 bundle digest 不一致", reason="bundle_digest")
278
+
279
+
280
+ def _safe_relative_file(value: str) -> bool:
281
+ path = PurePosixPath(value)
282
+ return (
283
+ bool(value)
284
+ and "\\" not in value
285
+ and not path.is_absolute()
286
+ and all(part not in {"", ".", ".."} for part in path.parts)
287
+ )
288
+
289
+
290
+ def _incompatible_error(message: str, *, reason: str) -> StudioError:
291
+ return StudioError(
292
+ "HOSTED_KERNEL_BUNDLE_INCOMPATIBLE",
293
+ message,
294
+ status_code=422,
295
+ details={"reason": reason},
296
+ )
297
+
298
+
299
+ def _integrity_error(message: str, *, reason: str) -> StudioError:
300
+ return StudioError(
301
+ "HOSTED_KERNEL_BUNDLE_INTEGRITY_INVALID",
302
+ message,
303
+ status_code=422,
304
+ details={"reason": reason},
305
+ )
306
+
307
+
308
+ __all__ = [
309
+ "AGENT_KERNEL_V1_CONTRACT_DIGEST",
310
+ "AGENT_KERNEL_V1_CONTRACT_SET",
311
+ "HOSTED_KERNEL_REQUIREMENTS_FORMAT",
312
+ "HOSTED_KERNEL_REQUIREMENTS_PATH",
313
+ "HostedKernelBundle",
314
+ "build_hosted_kernel_requirement",
315
+ "hosted_kernel_requirement_digest",
316
+ "preflight_hosted_kernel_bundle",
317
+ ]
@@ -0,0 +1,131 @@
1
+ """ManifestResolver —— 统一识别工作区根 Manifest 的种类(方案 §6.1)。
2
+
3
+ 当前问题(方案 §2.4 第 1 点):标准 LangGraph/ADK 项目作为 Studio workspace 启动后,根
4
+ ``agentengine.yaml`` 可能先进入 Codex Manifest 解析,导致 ``CODEX_MANIFEST_INVALID``。
5
+
6
+ 本模块提供统一入口:根 manifest 存在时先读 ``framework`` / ``runtime.type`` 字段决定 kind,
7
+ 只有明确 ``framework: codex`` 才走 ``CodexAgentManifest`` 解析;明确为 ADK/LangGraph 等框架时
8
+ 返回 framework kind(交给 ``FrameworkDetector`` 与标准 Code 项目导入);无法判定时返回
9
+ ``MANIFEST_KIND_AMBIGUOUS``,列出候选,不猜测为 Codex。
10
+
11
+ 解析顺序(方案 §6.1):
12
+ 1. 根 manifest 不存在 → ``none``
13
+ 2. 显式 ``framework: codex`` 或 ``runtime.name: codex`` → ``codex``
14
+ 3. 显式 ``framework: adk|langgraph|...`` 或 ``runtime.type: adk|langgraph|...`` → ``framework``
15
+ 4. 仍无法判定 → ``ambiguous``(列出已读到的关键字段,便于诊断)
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from dataclasses import dataclass, field
21
+ from pathlib import Path
22
+ from typing import Any, Literal
23
+
24
+ import yaml
25
+
26
+ ManifestKind = Literal["none", "codex", "framework", "ambiguous"]
27
+
28
+ # framework/runtime 字段的已知 codex / framework 取值。
29
+ _CODEX_FRAMEWORK_VALUES = frozenset({"codex"})
30
+ _CODEX_RUNTIME_VALUES = frozenset({"codex"})
31
+ _FRAMEWORK_VALUES = frozenset({"adk", "langgraph", "langchain", "deepagents", "hermes", "openclaw"})
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class ManifestKindResult:
36
+ """根 manifest 识别结果。"""
37
+
38
+ kind: ManifestKind
39
+ path: Path
40
+ framework: str = ""
41
+ runtime_type: str = ""
42
+ artifact_type: str = ""
43
+ # ambiguous 时列出已读字段,供诊断与错误返回。
44
+ detected_fields: dict[str, Any] = field(default_factory=dict)
45
+
46
+ @property
47
+ def is_codex(self) -> bool:
48
+ return self.kind == "codex"
49
+
50
+
51
+ def _safe_yaml(path: Path) -> dict[str, Any]:
52
+ try:
53
+ with open(path, "r", encoding="utf-8-sig") as f:
54
+ payload = yaml.safe_load(f)
55
+ except (OSError, yaml.YAMLError):
56
+ return {}
57
+ if not isinstance(payload, dict):
58
+ return {}
59
+ return payload
60
+
61
+
62
+ def detect_manifest_kind(workspace_root: Path | str) -> ManifestKindResult:
63
+ """识别工作区根 manifest 的种类(方案 §6.1)。
64
+
65
+ ``workspace_root`` 指工作区目录;本函数查其下的 ``agentengine.yaml``(不存在时返回 ``none``)。
66
+ """
67
+ root = Path(workspace_root)
68
+ path = root / "agentengine.yaml"
69
+ if not path.is_file():
70
+ return ManifestKindResult(kind="none", path=path)
71
+
72
+ payload = _safe_yaml(path)
73
+ if not payload:
74
+ # 文件存在但无法解析为 dict → ambiguous(不猜测为 codex)
75
+ return ManifestKindResult(kind="ambiguous", path=path, detected_fields={"unparsable": True})
76
+
77
+ framework = str(payload.get("framework") or "").strip().lower()
78
+ runtime = payload.get("runtime") or {}
79
+ runtime_type = (
80
+ str(runtime.get("type") or runtime.get("name") or "").strip().lower()
81
+ if isinstance(runtime, dict)
82
+ else ""
83
+ )
84
+ artifact_type = str(payload.get("artifact_type") or "").strip().lower()
85
+ detected = {
86
+ "framework": framework,
87
+ "runtimeType": runtime_type,
88
+ "artifactType": artifact_type,
89
+ "topLevelKeys": sorted(payload.keys()),
90
+ }
91
+
92
+ # 2. 显式 codex
93
+ if framework in _CODEX_FRAMEWORK_VALUES or runtime_type in _CODEX_RUNTIME_VALUES:
94
+ return ManifestKindResult(
95
+ kind="codex",
96
+ path=path,
97
+ framework=framework,
98
+ runtime_type=runtime_type,
99
+ artifact_type=artifact_type,
100
+ )
101
+ # 3. 显式 framework
102
+ if framework in _FRAMEWORK_VALUES or runtime_type in _FRAMEWORK_VALUES:
103
+ return ManifestKindResult(
104
+ kind="framework",
105
+ path=path,
106
+ framework=framework or runtime_type,
107
+ runtime_type=runtime_type,
108
+ artifact_type=artifact_type,
109
+ )
110
+ # 4. 无法判定(例如只有 name/version 但无 framework/runtime)
111
+ return ManifestKindResult(
112
+ kind="ambiguous",
113
+ path=path,
114
+ framework=framework,
115
+ runtime_type=runtime_type,
116
+ artifact_type=artifact_type,
117
+ detected_fields=detected,
118
+ )
119
+
120
+
121
+ def root_manifest_is_codex(workspace_root: Path | str) -> bool:
122
+ """便捷判定:根 manifest 是否应走 Codex 解析(方案 §6.1)。"""
123
+ return detect_manifest_kind(workspace_root).is_codex
124
+
125
+
126
+ __all__ = [
127
+ "ManifestKind",
128
+ "ManifestKindResult",
129
+ "detect_manifest_kind",
130
+ "root_manifest_is_codex",
131
+ ]
@@ -23,6 +23,12 @@ _DENIED_METADATA_HOSTS = {
23
23
  "fd00:ec2::254",
24
24
  }
25
25
 
26
+ #: 表示输出被 token 上限截断的 finishReason 集合(chat 与 Responses 两种 wire)。
27
+ _LENGTH_FINISH_REASONS = {"length", "incomplete", "max_output_tokens"}
28
+ #: length 截断重试时把 max_tokens 提到的目标值;未配置 max_tokens 的 profile
29
+ #: 首次截断重试也用该值兜底(authoring 输出完整 JSON 需要宽松上限)。
30
+ _LENGTH_RETRY_TARGET_TOKENS = 16384
31
+
26
32
 
27
33
  @dataclass(frozen=True)
28
34
  class ToolCall:
@@ -325,36 +331,50 @@ class OpenAICompatibleModelClient:
325
331
  backoff_seconds: float,
326
332
  tools: list[dict[str, Any]] | None = None,
327
333
  allow_empty: bool = False,
334
+ response_format: dict[str, Any] | None = None,
335
+ retry_on_length: bool = False,
328
336
  ) -> ModelResponse:
329
337
  await self.network_guard.check(model.endpoint_url, network_policy)
330
338
  credential = self.credential_resolver.resolve(model.credential_ref)
331
339
  wire_api = (model.wire_api or "chat").strip().lower()
340
+ payload: dict[str, Any]
332
341
  if wire_api == "responses":
333
342
  payload = self._responses_payload(model, messages, tools)
334
343
  else:
335
344
  payload = {
336
345
  "model": model.model,
337
346
  "messages": messages,
338
- "temperature": model.parameters.temperature,
339
- "max_tokens": model.parameters.max_tokens,
340
347
  "stream": False,
341
348
  }
349
+ # 未显式配置的采样参数一律不携带字段,交给服务端默认,
350
+ # 避免触碰各模型族的硬约束(kimi 温度、各家 max_tokens 上限等)。
351
+ if model.parameters.temperature is not None:
352
+ payload["temperature"] = model.parameters.temperature
353
+ if model.parameters.max_tokens is not None:
354
+ payload["max_tokens"] = model.parameters.max_tokens
342
355
  if model.parameters.top_p is not None:
343
356
  payload["top_p"] = model.parameters.top_p
344
357
  if tools:
345
358
  payload["tools"] = tools
346
359
  payload["tool_choice"] = "auto"
360
+ if response_format and model.parameters.allow_json_response_format:
361
+ payload["response_format"] = response_format
347
362
  headers = {
348
363
  "Authorization": f"Bearer {credential}",
349
364
  "Content-Type": "application/json",
350
365
  }
351
366
  timeout = httpx.Timeout(timeout_seconds, connect=min(10, timeout_seconds))
367
+ dropped_response_format = False
368
+ length_retried = False
369
+ last_length_error: StudioError | None = None
352
370
  async with httpx.AsyncClient(
353
371
  transport=self.transport,
354
372
  timeout=timeout,
355
373
  follow_redirects=False,
356
374
  ) as client:
357
- for attempt in range(1, max_attempts + 1):
375
+ # 额外 1 次迭代仅用于 response_format 400 降级重发,
376
+ # 其余失败路径仍受 max_attempts 约束(会在原上限处 raise)。
377
+ for attempt in range(1, max_attempts + 2):
358
378
  try:
359
379
  response = await client.post(
360
380
  model.endpoint_url,
@@ -382,18 +402,83 @@ class OpenAICompatibleModelClient:
382
402
  if attempt < max_attempts:
383
403
  await self.sleep(backoff_seconds * attempt)
384
404
  continue
405
+ if (
406
+ response.status_code == 400
407
+ and "response_format" in payload
408
+ and not dropped_response_format
409
+ ):
410
+ # 网关不支持 response_format:去掉该字段重发一次。
411
+ dropped_response_format = True
412
+ payload.pop("response_format")
413
+ continue
385
414
  if response.status_code >= 400:
415
+ if response.status_code == 429:
416
+ # A selected authoring profile must not silently fall
417
+ # back to a different model. Preserve the actual
418
+ # upstream condition so Studio can offer the user a
419
+ # useful retry/switch decision instead of reporting a
420
+ # misleading generic 502.
421
+ raise StudioError(
422
+ "MODEL_RATE_LIMITED",
423
+ "所选生成模型当前限流,请稍后重试或切换模型 Profile",
424
+ status_code=429,
425
+ details={"upstreamStatus": response.status_code},
426
+ )
427
+ upstream_detail = ""
428
+ try:
429
+ upstream_detail = response.text[:200]
430
+ except Exception: # noqa: BLE001 - 诊断信息尽力而为
431
+ upstream_detail = ""
386
432
  raise StudioError(
387
433
  "MODEL_REQUEST_FAILED",
388
434
  "模型服务返回错误",
389
435
  status_code=502,
390
- details={"upstreamStatus": response.status_code},
436
+ details={
437
+ "upstreamStatus": response.status_code,
438
+ "upstreamError": upstream_detail,
439
+ },
391
440
  )
392
- if wire_api == "responses":
393
- return self._parse_responses_response(response, allow_empty=allow_empty)
394
- return self._parse_response(response, allow_empty=allow_empty)
441
+ try:
442
+ if wire_api == "responses":
443
+ parsed = self._parse_responses_response(response, allow_empty=allow_empty)
444
+ else:
445
+ parsed = self._parse_response(response, allow_empty=allow_empty)
446
+ except StudioError as exc:
447
+ # finishReason=length 的空响应(大 JSON 被 max_tokens 截断):
448
+ # 一次性扩容 max_tokens 重发;再次截断则按原错误上抛。
449
+ if retry_on_length and not length_retried and self._is_length_truncation(exc):
450
+ length_retried = True
451
+ last_length_error = exc
452
+ payload = self._expand_length_budget(payload)
453
+ continue
454
+ raise
455
+ if (
456
+ retry_on_length
457
+ and not length_retried
458
+ and parsed.finish_reason in _LENGTH_FINISH_REASONS
459
+ ):
460
+ # 有内容但被 length 截断(结构化输出必然残缺),同样扩容重发一次。
461
+ length_retried = True
462
+ payload = self._expand_length_budget(payload)
463
+ continue
464
+ return parsed
465
+ if last_length_error is not None:
466
+ raise last_length_error
395
467
  raise AssertionError("unreachable")
396
468
 
469
+ @staticmethod
470
+ def _is_length_truncation(exc: StudioError) -> bool:
471
+ if exc.code != "MODEL_EMPTY_RESPONSE":
472
+ return False
473
+ return str(exc.details.get("finishReason") or "") in _LENGTH_FINISH_REASONS
474
+
475
+ @staticmethod
476
+ def _expand_length_budget(payload: dict[str, Any]) -> dict[str, Any]:
477
+ key = "max_output_tokens" if "max_output_tokens" in payload else "max_tokens"
478
+ current = int(payload.get(key) or 0)
479
+ payload[key] = max(current, _LENGTH_RETRY_TARGET_TOKENS)
480
+ return payload
481
+
397
482
  @staticmethod
398
483
  def _responses_payload(
399
484
  model: ResolvedModel,
@@ -420,8 +505,9 @@ class OpenAICompatibleModelClient:
420
505
  payload: dict[str, Any] = {
421
506
  "model": model.model,
422
507
  "input": items,
423
- "max_output_tokens": model.parameters.max_tokens,
424
508
  }
509
+ if model.parameters.max_tokens is not None:
510
+ payload["max_output_tokens"] = model.parameters.max_tokens
425
511
  if instructions:
426
512
  payload["instructions"] = "\n\n".join(instructions)
427
513
  return payload
@@ -460,7 +546,7 @@ class OpenAICompatibleModelClient:
460
546
  "MODEL_EMPTY_RESPONSE",
461
547
  "模型未返回可用内容",
462
548
  status_code=502,
463
- details={"status": status},
549
+ details={"status": status, "finishReason": status},
464
550
  )
465
551
  raw_usage = payload.get("usage") or {}
466
552
  usage = Usage(
@@ -32,7 +32,7 @@ async def test_model_profile_connection(
32
32
  resolved.parameters = resolved.parameters.model_copy(
33
33
  update={
34
34
  "temperature": 0,
35
- "max_tokens": min(resolved.parameters.max_tokens, 64),
35
+ "max_tokens": min(resolved.parameters.max_tokens or 2048, 64),
36
36
  }
37
37
  )
38
38
  host = (urlparse(resolved.endpoint_url).hostname or "").lower().rstrip(".")