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,1102 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Agent Kernel ingress 收敛层(Phase 1 Task 8)。
3
+
4
+ 把 KsADK 现有五个入口(RunAgent / Responses / AG-UI / A2A / Studio)的
5
+ mutation 统一收敛到 ``AgentKernel.submit``:
6
+
7
+ - **opt-in 灰度**:只有 ``KSADK_AGENT_KERNEL=1`` 且进程内注册了 kernel
8
+ (``set_agent_kernel``)时才走 kernel 路径;默认保持旧 executor 路径,
9
+ 保证既有 public fixtures 不破。
10
+ - **mapper 只做 public request -> canonical command**:tenant / agent_instance /
11
+ authorization_ref 全部由 trusted runtime context 注入,不来自 public payload。
12
+ Responses request id、A2A task id、AG-UI run id 等保存为 correlation/source
13
+ ref,不改变 Session/Run canonical identity。
14
+ - **receipt -> HTTP**:``RECEIPT_HTTP_STATUS`` 是唯一映射表。
15
+ - **统一 cursor**:kernel 路径的 SSE 一律从
16
+ ``SessionEventSubscription(after_seq)`` 读取,reconnect cursor 源自同一
17
+ Session seq;各协议自己的 event shape 由 surface 内的 public projector
18
+ 保留,禁止第二个自增序列。
19
+
20
+ kernel 路径下命令的实际执行由 ``AgentWorker``(Task 6/7 交付)认领并驱动
21
+ RuntimeAdapter;ingress 只 submit + 订阅投影,不直接触碰 RuntimeExecutor。
22
+ """
23
+ from __future__ import annotations
24
+
25
+ import hashlib
26
+ import json
27
+ import logging
28
+ import os
29
+ import uuid
30
+ from collections.abc import AsyncIterator, Awaitable, Callable, Mapping
31
+ from dataclasses import dataclass
32
+ from datetime import datetime, timedelta, timezone
33
+ from typing import Any
34
+
35
+ from fastapi import APIRouter, Request
36
+ from fastapi.responses import JSONResponse, StreamingResponse
37
+
38
+ from ksadk.kernel.authorization import (
39
+ AgentControlPermitVerifier,
40
+ JwksSource,
41
+ sign_permit,
42
+ )
43
+ from ksadk.kernel.contracts import (
44
+ AgentControlCommand,
45
+ AgentControlPermit,
46
+ AgentControlReceipt,
47
+ ControlSource,
48
+ SessionEventEnvelope,
49
+ SessionEventSubscription,
50
+ )
51
+
52
+ # ---------------------------------------------------------------------------
53
+ # opt-in 开关与 kernel 注册
54
+ # ---------------------------------------------------------------------------
55
+
56
+ ENV_KERNEL_ENABLED = "KSADK_AGENT_KERNEL"
57
+ # Operator 注入的开关名(AGENT_KERNEL_ENABLED=1);与 SDK 本地灰度开关等价。
58
+ ENV_KERNEL_ENABLED_PLATFORM = "AGENT_KERNEL_ENABLED"
59
+
60
+ _TRUTHY = {"1", "true", "yes", "on"}
61
+
62
+ logger = logging.getLogger(__name__)
63
+
64
+ _kernel: Any | None = None
65
+
66
+
67
+ def kernel_ingress_enabled() -> bool:
68
+ """kernel 路径是灰度 opt-in:默认关闭,旧路径不变。
69
+
70
+ 认 ``KSADK_AGENT_KERNEL``(SDK 本地)或 ``AGENT_KERNEL_ENABLED``
71
+ (Operator 平台注入)任一为真。
72
+ """
73
+
74
+ for name in (ENV_KERNEL_ENABLED, ENV_KERNEL_ENABLED_PLATFORM):
75
+ if os.environ.get(name, "").strip().lower() in _TRUTHY:
76
+ return True
77
+ return False
78
+
79
+
80
+ def set_agent_kernel(kernel: Any) -> None:
81
+ """注册进程级 AgentKernel(server bootstrap / 测试 harness 调用)。"""
82
+
83
+ global _kernel
84
+ _kernel = kernel
85
+
86
+
87
+ def clear_agent_kernel() -> None:
88
+ global _kernel
89
+ _kernel = None
90
+
91
+
92
+ def get_agent_kernel() -> Any | None:
93
+ return _kernel
94
+
95
+
96
+ def kernel_route_active() -> bool:
97
+ """当前请求是否走 kernel ingress(开关开 且 kernel 已注册)。"""
98
+
99
+ return kernel_ingress_enabled() and get_agent_kernel() is not None
100
+
101
+
102
+ # ---------------------------------------------------------------------------
103
+ # trusted runtime context
104
+ # ---------------------------------------------------------------------------
105
+
106
+
107
+ @dataclass(frozen=True)
108
+ class TrustedRuntimeContext:
109
+ """由 runtime 注入的信任事实;public request 永远不提供这些字段。"""
110
+
111
+ tenant_id: str
112
+ agent_instance_id: str
113
+ source: ControlSource
114
+ permit: AgentControlPermit
115
+ received_at: str
116
+
117
+
118
+ class _LocalJwks:
119
+ def __init__(self, key_id: str, public_b64: str) -> None:
120
+ self._keys = {key_id: public_b64}
121
+
122
+ async def fetch_verification_keys(self) -> Mapping[str, str]:
123
+ return dict(self._keys)
124
+
125
+
126
+ class InProcessPermitIssuer:
127
+ """本地 opt-in 模式的进程内签发方(Ed25519,密钥不落盘)。
128
+
129
+ 托管部署(agentengine-server Task 9+)会换成 server 签发的 permit;
130
+ SDK 本地灰度只需要一个诚实的、可被同一个 kernel verifier 验签的 issuer。
131
+ """
132
+
133
+ # TTL 与 kernel verifier 的 PERMIT_MAX_TTL_SECONDS(300s)对齐;
134
+ # 超过 300s 的 permit 在严格 verifier 下必然被拒。
135
+ def __init__(self, *, ttl_seconds: int = 300, key_id: str = "ksadk-local-kernel") -> None:
136
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
137
+
138
+ from ksadk.kernel.authorization import b64url_encode
139
+
140
+ self._private = Ed25519PrivateKey.generate()
141
+ self.key_id = key_id
142
+ self._public_b64 = b64url_encode(self._private.public_key().public_bytes_raw())
143
+ self._ttl = int(ttl_seconds)
144
+ self._jwks: JwksSource = _LocalJwks(key_id, self._public_b64)
145
+
146
+ def verifier(self, **kwargs: Any) -> AgentControlPermitVerifier:
147
+ return AgentControlPermitVerifier(self._jwks, **kwargs)
148
+
149
+ def issue(
150
+ self,
151
+ *,
152
+ tenant_id: str,
153
+ agent_instance_id: str,
154
+ operations: tuple[str, ...] | list[str],
155
+ session_id: str | None = None,
156
+ subject_ref: str = "ksadk-local-runtime",
157
+ now: datetime | None = None,
158
+ ) -> AgentControlPermit:
159
+ issued = now or datetime.now(timezone.utc)
160
+ expires = issued + timedelta(seconds=self._ttl)
161
+ claims = {
162
+ "tenant_id": tenant_id,
163
+ "agent_instance_id": agent_instance_id,
164
+ "session_id": session_id,
165
+ "operations": sorted(operations),
166
+ }
167
+ unsigned = AgentControlPermit(
168
+ permit_id=f"permit_{uuid.uuid4().hex}",
169
+ subject_ref=subject_ref,
170
+ tenant_id=tenant_id,
171
+ agent_instance_id=agent_instance_id,
172
+ session_id=session_id,
173
+ allowed_operations=list(operations),
174
+ issued_at=_rfc3339(issued),
175
+ expires_at=_rfc3339(expires),
176
+ nonce=uuid.uuid4().hex,
177
+ key_id=self.key_id,
178
+ claims_digest=hashlib.sha256(
179
+ json.dumps(claims, sort_keys=True, separators=(",", ":")).encode()
180
+ ).hexdigest(),
181
+ signature="",
182
+ )
183
+ return unsigned.model_copy(update={"signature": sign_permit(unsigned, self._private)})
184
+
185
+
186
+ def _rfc3339(value: datetime) -> str:
187
+ return value.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
188
+
189
+
190
+ def trusted_context(
191
+ *,
192
+ source_kind: str,
193
+ source_ref: str,
194
+ tenant_id: str = "local",
195
+ agent_instance_id: str = "local-agent",
196
+ session_id: str | None = None,
197
+ operations: tuple[str, ...] | list[str] = ("enqueue",),
198
+ issuer: InProcessPermitIssuer | None = None,
199
+ launch_context: Any | None = None,
200
+ ) -> TrustedRuntimeContext:
201
+ """从 trusted runtime 侧(env / launch config)构造上下文并签 permit。"""
202
+
203
+ # Local web is still a real per-AgentInstance kernel runtime. Without
204
+ # this projection its compatibility routes self-sign commands for the
205
+ # synthetic ``local-agent`` while the worker owns ``AGENT_INSTANCE_ID``;
206
+ # accepted Inbox rows would then never be leased or consumed. In hosted
207
+ # mode the local signature remains unverifiable against Server JWKS, so
208
+ # this does not create a Server-admission bypass.
209
+ if agent_instance_id == "local-agent":
210
+ agent_instance_id = (
211
+ os.environ.get("AGENT_INSTANCE_ID", "").strip() or agent_instance_id
212
+ )
213
+ if launch_context is not None:
214
+ config = getattr(launch_context, "config", None) or {}
215
+ tenant_id = str(config.get("tenant_id") or tenant_id)
216
+ agent_instance_id = str(
217
+ config.get("agent_instance_id") or agent_instance_id
218
+ )
219
+ issuer = issuer or _default_issuer()
220
+ permit = issuer.issue(
221
+ tenant_id=tenant_id,
222
+ agent_instance_id=agent_instance_id,
223
+ operations=operations,
224
+ session_id=session_id,
225
+ )
226
+ return TrustedRuntimeContext(
227
+ tenant_id=tenant_id,
228
+ agent_instance_id=agent_instance_id,
229
+ source=ControlSource(kind=source_kind, ref=source_ref),
230
+ permit=permit,
231
+ received_at=_rfc3339(datetime.now(timezone.utc)),
232
+ )
233
+
234
+
235
+ _default_issuer_singleton: InProcessPermitIssuer | None = None
236
+
237
+
238
+ def _default_issuer() -> InProcessPermitIssuer:
239
+ global _default_issuer_singleton
240
+ if _default_issuer_singleton is None:
241
+ _default_issuer_singleton = InProcessPermitIssuer()
242
+ return _default_issuer_singleton
243
+
244
+
245
+ # ---------------------------------------------------------------------------
246
+ # receipt -> HTTP
247
+ # ---------------------------------------------------------------------------
248
+
249
+ RECEIPT_HTTP_STATUS: dict[str, int] = {
250
+ "accepted": 202,
251
+ "duplicate": 200,
252
+ "rejected": 400,
253
+ "unsupported": 409,
254
+ "queue_full": 429,
255
+ "persistence_uncertain": 503,
256
+ }
257
+
258
+
259
+ def receipt_http_status(receipt: AgentControlReceipt) -> int:
260
+ return RECEIPT_HTTP_STATUS.get(receipt.status, 400)
261
+
262
+
263
+ def receipt_response_headers(receipt: AgentControlReceipt) -> dict[str, str]:
264
+ """新 header:contract/capability 语义由 kernel digest header 承载。"""
265
+
266
+ headers = {
267
+ "X-Ksadk-Agent-Kernel": "1",
268
+ "X-Ksadk-Control-Status": receipt.status,
269
+ "X-Ksadk-Command-Id": str(receipt.command_id),
270
+ }
271
+ if receipt.message_id is not None:
272
+ headers["X-Ksadk-Control-Message-Id"] = str(receipt.message_id)
273
+ return headers
274
+
275
+
276
+ def receipt_error_payload(receipt: AgentControlReceipt) -> dict[str, Any]:
277
+ error = receipt.error
278
+ return {
279
+ "Code": (error.code if error else receipt.status),
280
+ "Message": (error.message if error else receipt.status),
281
+ "Retryable": bool(error.retryable) if error else False,
282
+ "ControlStatus": receipt.status,
283
+ "CommandId": str(receipt.command_id),
284
+ }
285
+
286
+
287
+ # ---------------------------------------------------------------------------
288
+ # mappers: public request -> AgentControlCommand
289
+ # ---------------------------------------------------------------------------
290
+
291
+
292
+ def _command(
293
+ *,
294
+ trusted: TrustedRuntimeContext,
295
+ command_type: str,
296
+ session_id: str,
297
+ idempotency_key: str,
298
+ payload: dict[str, Any],
299
+ correlation_id: str | None = None,
300
+ ) -> AgentControlCommand:
301
+ return AgentControlCommand(
302
+ command_id=uuid.uuid4(),
303
+ idempotency_key=idempotency_key,
304
+ tenant_id=trusted.tenant_id,
305
+ agent_instance_id=trusted.agent_instance_id,
306
+ session_id=session_id,
307
+ command_type=command_type,
308
+ payload=payload,
309
+ source=trusted.source,
310
+ authorization_ref=trusted.permit.permit_id,
311
+ submitted_at=trusted.received_at,
312
+ correlation_id=correlation_id,
313
+ )
314
+
315
+
316
+ def map_run_request(
317
+ *,
318
+ session_id: str,
319
+ idempotency_key: str,
320
+ content: Any,
321
+ invocation_id: str | None = None,
322
+ trusted: TrustedRuntimeContext,
323
+ runtime_options: Mapping[str, Any] | None = None,
324
+ ) -> AgentControlCommand:
325
+ """RunAgent(agentengine API)-> enqueue。InvocationId 是 source/correlation ref。
326
+
327
+ runtime_options 携带有界的 run 级选择(model 等);worker 侧按部署
328
+ defaults/白名单校验,不受调用方信任。
329
+ """
330
+
331
+ payload: dict[str, Any] = {"content": content}
332
+ if runtime_options:
333
+ payload["runtime_options"] = dict(runtime_options)
334
+ return _command(
335
+ trusted=trusted,
336
+ command_type="enqueue",
337
+ session_id=session_id,
338
+ idempotency_key=idempotency_key,
339
+ payload=payload,
340
+ correlation_id=invocation_id,
341
+ )
342
+
343
+
344
+ def map_responses_request(
345
+ *,
346
+ session_id: str,
347
+ idempotency_key: str,
348
+ content: Any,
349
+ response_id: str | None = None,
350
+ trusted: TrustedRuntimeContext,
351
+ ) -> AgentControlCommand:
352
+ """OpenAI Responses 兼容入口 -> enqueue;response id 保存在 correlation ref。"""
353
+
354
+ return _command(
355
+ trusted=trusted,
356
+ command_type="enqueue",
357
+ session_id=session_id,
358
+ idempotency_key=idempotency_key,
359
+ payload={"content": content},
360
+ correlation_id=response_id,
361
+ )
362
+
363
+
364
+ def map_agui_request(
365
+ *,
366
+ session_id: str,
367
+ idempotency_key: str,
368
+ content: Any,
369
+ run_id: str | None = None,
370
+ trusted: TrustedRuntimeContext,
371
+ ) -> AgentControlCommand:
372
+ """AG-UI run -> enqueue;AG-UI run id 保存在 correlation ref。"""
373
+
374
+ return _command(
375
+ trusted=trusted,
376
+ command_type="enqueue",
377
+ session_id=session_id,
378
+ idempotency_key=idempotency_key,
379
+ payload={"content": content},
380
+ correlation_id=run_id,
381
+ )
382
+
383
+
384
+ def map_a2a_task(
385
+ *,
386
+ session_id: str,
387
+ idempotency_key: str,
388
+ content: Any,
389
+ task_id: str | None = None,
390
+ trusted: TrustedRuntimeContext,
391
+ ) -> AgentControlCommand:
392
+ """A2A task -> enqueue;A2A task id 保存在 correlation ref。"""
393
+
394
+ return _command(
395
+ trusted=trusted,
396
+ command_type="enqueue",
397
+ session_id=session_id,
398
+ idempotency_key=idempotency_key,
399
+ payload={"content": content},
400
+ correlation_id=task_id,
401
+ )
402
+
403
+
404
+ def map_studio_request(
405
+ *,
406
+ session_id: str,
407
+ idempotency_key: str,
408
+ content: Any,
409
+ run_id: str | None = None,
410
+ trusted: TrustedRuntimeContext,
411
+ ) -> AgentControlCommand:
412
+ """Studio run -> enqueue;studio run id 保存在 correlation ref。"""
413
+
414
+ return _command(
415
+ trusted=trusted,
416
+ command_type="enqueue",
417
+ session_id=session_id,
418
+ idempotency_key=idempotency_key,
419
+ payload={"content": content},
420
+ correlation_id=run_id,
421
+ )
422
+
423
+
424
+ def map_control_request(
425
+ *,
426
+ command_type: str,
427
+ session_id: str,
428
+ idempotency_key: str,
429
+ payload: dict[str, Any],
430
+ trusted: TrustedRuntimeContext,
431
+ run_id: str | None = None,
432
+ ) -> AgentControlCommand:
433
+ """Cancel/Resume/Pause 等 control 动作 -> 对应 command_type。"""
434
+
435
+ return _command(
436
+ trusted=trusted,
437
+ command_type=command_type,
438
+ session_id=session_id,
439
+ idempotency_key=idempotency_key,
440
+ payload=payload,
441
+ correlation_id=run_id,
442
+ )
443
+
444
+
445
+ # ---------------------------------------------------------------------------
446
+ # submit + 统一 cursor 订阅
447
+ # ---------------------------------------------------------------------------
448
+
449
+
450
+ async def submit_command(
451
+ command: AgentControlCommand, *, permit: AgentControlPermit
452
+ ) -> AgentControlReceipt:
453
+ kernel = get_agent_kernel()
454
+ if kernel is None:
455
+ raise RuntimeError("agent kernel ingress is active but no kernel is registered")
456
+ # The admitted event is written through the Kernel's fenced shared log.
457
+ # Do not assume a legacy HTTP session service used the same namespace or
458
+ # connection pool; direct ingress (and the first RunAgent request) needs
459
+ # the session row in this exact log before the transactional admission.
460
+ await _ensure_shared_log_session(command)
461
+ return await kernel.submit(command, permit=permit)
462
+
463
+
464
+ async def subscribe_projected(
465
+ session_id: str,
466
+ *,
467
+ trusted: TrustedRuntimeContext,
468
+ after_seq: int = 0,
469
+ projector: Callable[[SessionEventEnvelope], Any] | None = None,
470
+ should_stop: Callable[[], Awaitable[bool]] | None = None,
471
+ timeout: float | None = None,
472
+ ) -> AsyncIterator[tuple[int, Any]]:
473
+ """统一 cursor 订阅:所有 SSE 的 reconnect cursor 都源自同一 Session seq。
474
+
475
+ projector 返回 None 表示该 envelope 在该协议下不投影(跳过但 cursor 仍推进)。
476
+ """
477
+
478
+ kernel = get_agent_kernel()
479
+ if kernel is None:
480
+ raise RuntimeError("agent kernel ingress is active but no kernel is registered")
481
+ subscription = SessionEventSubscription(
482
+ tenant_id=trusted.tenant_id,
483
+ agent_instance_id=trusted.agent_instance_id,
484
+ session_id=session_id,
485
+ authorization_ref=trusted.permit.permit_id,
486
+ after_seq=after_seq,
487
+ )
488
+ # 兼容不同 AgentKernel 实现(含测试替身):只传其实际支持的参数。
489
+ import inspect as _inspect
490
+
491
+ subscribe_kwargs: dict[str, Any] = {}
492
+ try:
493
+ _params = _inspect.signature(kernel.subscribe).parameters
494
+ except (TypeError, ValueError): # pragma: no cover - defensive
495
+ _params = {}
496
+ if "should_stop" in _params:
497
+ subscribe_kwargs["should_stop"] = should_stop
498
+ if "timeout" in _params:
499
+ subscribe_kwargs["timeout"] = timeout
500
+ async for envelope in kernel.subscribe(
501
+ subscription, permit=trusted.permit, **subscribe_kwargs
502
+ ):
503
+ projected = envelope if projector is None else projector(envelope)
504
+ if projected is None:
505
+ continue
506
+ yield int(envelope.seq), projected
507
+
508
+
509
+ # ---------------------------------------------------------------------------
510
+ # canonical kernel HTTP ingress: /agent-kernel/v1/*
511
+ # ---------------------------------------------------------------------------
512
+
513
+ # 三边(agentengine-gateway 转发、agentengine-server runtime client、KsADK
514
+ # runtime)唯一一致的 kernel ingress 路径常量;契约测试锁定。
515
+ KERNEL_INGRESS_BASE_PATH = "/agent-kernel/v1"
516
+ KERNEL_INGRESS_SUBMIT_PATH = f"{KERNEL_INGRESS_BASE_PATH}/SubmitAgentControl"
517
+ KERNEL_INGRESS_STATUS_PATH = f"{KERNEL_INGRESS_BASE_PATH}/GetAgentStatus"
518
+ KERNEL_INGRESS_SESSION_EVENTS_PATH = f"{KERNEL_INGRESS_BASE_PATH}/SubscribeSessionEvents"
519
+ KERNEL_INGRESS_HEALTH_PATH = f"{KERNEL_INGRESS_BASE_PATH}/health"
520
+
521
+ ENV_KERNEL_STORE_DRIVER = "AGENT_KERNEL_STORE_DRIVER"
522
+ ENV_KERNEL_STORE_DSN = "AGENT_KERNEL_STORE_DSN"
523
+ ENV_JWKS_URL = "AGENT_CONTROL_JWKS_URL"
524
+ ENV_AUTHORITY_MODE = "AGENT_KERNEL_AUTHORITY_MODE"
525
+
526
+ _AUTHORITY_LOCAL = "local"
527
+ _AUTHORITY_HOSTED = "hosted"
528
+
529
+
530
+ def authority_mode() -> str:
531
+ """当前 permit authority 模式。
532
+
533
+ - ``AGENT_KERNEL_AUTHORITY_MODE=local``:显式本地授权(开发 / 灰度 /
534
+ canary)。允许进程内 issuer 自签 trusted-context permit,且 JWKS
535
+ 合并本地公钥。
536
+ - ``AGENT_KERNEL_AUTHORITY_MODE=hosted``:托管模式,fail closed——缺
537
+ permit 一律 401,本地自签 / 未知 key 一律 403,JWKS 不得合并本地
538
+ 公钥。
539
+ - 未显式配置时:配置了 ``AGENT_CONTROL_JWKS_URL`` 视为 hosted(server
540
+ 签发是唯一信任源),否则默认 local(保持本地灰度行为)。
541
+ """
542
+
543
+ explicit = os.environ.get(ENV_AUTHORITY_MODE, "").strip().lower()
544
+ if explicit in (_AUTHORITY_LOCAL, _AUTHORITY_HOSTED):
545
+ return explicit
546
+ if os.environ.get(ENV_JWKS_URL, "").strip():
547
+ return _AUTHORITY_HOSTED
548
+ return _AUTHORITY_LOCAL
549
+
550
+
551
+ def _is_hosted() -> bool:
552
+ return authority_mode() == _AUTHORITY_HOSTED
553
+
554
+
555
+ async def bootstrap_agent_kernel_from_env() -> Any | None:
556
+ """``AGENT_KERNEL_ENABLED=1`` 且能装配 store 时自动 ``set_agent_kernel``。
557
+
558
+ 避免"开了 env 也不生效":server lifespan 启动时调用;装配失败抛异常
559
+ (fail loud),不静默降级。已注册 kernel 时幂等返回。
560
+ """
561
+
562
+ existing = get_agent_kernel()
563
+ if existing is not None:
564
+ if _is_hosted():
565
+ # A caller may have registered a bare AgentKernel before entering
566
+ # this helper. Treat that exactly like a fresh half-runtime: an
567
+ # ingress facade without the production owner loops is not a
568
+ # healthy hosted deployment.
569
+ from ksadk.kernel.bootstrap import get_agent_kernel_runtime
570
+
571
+ runtime = get_agent_kernel_runtime()
572
+ if runtime is None or runtime.kernel is not existing:
573
+ raise RuntimeError(
574
+ "hosted agent kernel ingress requires the full production "
575
+ "composition root; a bare kernel is not allowed"
576
+ )
577
+ return existing
578
+ if not kernel_ingress_enabled():
579
+ return None
580
+ # This legacy helper only has enough context to build the ingress facade.
581
+ # In a hosted workload that would create a dangerous half-runtime: it can
582
+ # accept a Server permit, but no worker, lease owner or recovery loop will
583
+ # ever consume the durable command. Hosted applications must enter via
584
+ # ``bootstrap_agent_kernel_runtime_from_env`` from the FastAPI lifespan,
585
+ # where the real RuntimeAdapter provider is available.
586
+ if _is_hosted():
587
+ raise RuntimeError(
588
+ "hosted agent kernel ingress requires the full production "
589
+ "composition root; use bootstrap_agent_kernel_runtime_from_env"
590
+ )
591
+
592
+ from ksadk.events.session_event import SessionServiceEventStore
593
+ from ksadk.kernel.control import AgentKernel
594
+ from ksadk.sessions.in_memory import InMemorySessionService
595
+
596
+ driver = os.environ.get(ENV_KERNEL_STORE_DRIVER, "memory").strip().lower()
597
+ dsn = os.environ.get(ENV_KERNEL_STORE_DSN, "").strip()
598
+ session_service: Any = InMemorySessionService()
599
+ events = SessionServiceEventStore(session_service)
600
+ store: Any = None
601
+ nonce_store: Any = None
602
+
603
+ if driver == "postgres":
604
+ from ksadk.kernel.postgres_store import (
605
+ PostgresAgentKernelStore,
606
+ PostgresFencedSessionEventStore,
607
+ PostgresKernelEventLog,
608
+ PostgresNonceStore,
609
+ )
610
+ from ksadk.sessions.postgres_service import PostgresSessionService
611
+
612
+ if not dsn:
613
+ raise RuntimeError("postgres kernel store requires AGENT_KERNEL_STORE_DSN")
614
+ namespace = str(os.environ.get("KSADK_SESSION_NAMESPACE") or "default").strip()
615
+ tenant_id = str(
616
+ os.environ.get("KSADK_TENANT_ID")
617
+ or os.environ.get("AGENTENGINE_TENANT_ID")
618
+ or "default"
619
+ ).strip()
620
+ workspace_id = str(
621
+ os.environ.get("KSADK_WORKSPACE_ID")
622
+ or os.environ.get("AGENTENGINE_WORKSPACE_ID")
623
+ or "default"
624
+ ).strip()
625
+ # 事件与 session 走同一 PG(PG-backed SessionServiceEventStore),
626
+ # 使 worker 产生的 family=runtime/v2 事件对 canonical SSE 可见;
627
+ # nonce 用 PG durable 存储,跨 Pod / 重启防重放。
628
+ session_service = PostgresSessionService(
629
+ dsn=dsn,
630
+ namespace=namespace or "default",
631
+ tenant_id=tenant_id or "default",
632
+ workspace_id=workspace_id or "default",
633
+ )
634
+ await session_service._ensure_pool()
635
+ pool = session_service._pool
636
+ event_log = PostgresKernelEventLog(
637
+ pool,
638
+ namespace=session_service.namespace,
639
+ tenant_id=session_service.tenant_id,
640
+ workspace_id=session_service.workspace_id,
641
+ )
642
+ store = PostgresAgentKernelStore(pool, event_log, owns_pool=True)
643
+ # typed RuntimeEvent 写路径走 fenced store:ActivationWriteGuard
644
+ # append 与 activation 行验证同一事务(Task 4 Step 5)。
645
+ events = PostgresFencedSessionEventStore(store)
646
+ nonce_store = PostgresNonceStore(pool)
647
+ elif driver == "sqlite":
648
+ if dsn:
649
+ from ksadk.kernel.sqlite_store import SQLiteAgentKernelStore
650
+
651
+ store = SQLiteAgentKernelStore(dsn, events)
652
+ if store is None:
653
+ from ksadk.kernel.memory_store import InMemoryAgentKernelStore
654
+
655
+ store = InMemoryAgentKernelStore(events)
656
+
657
+ kernel = AgentKernel(
658
+ store, events, permit_verifier=_env_permit_verifier(nonce_store=nonce_store)
659
+ )
660
+ if hasattr(store, "ensure_schema"):
661
+ try:
662
+ await store.ensure_schema()
663
+ except Exception: # pragma: no cover - schema 已存在等场景
664
+ pass
665
+ set_agent_kernel(kernel)
666
+ return kernel
667
+
668
+
669
+ class _HttpJwks:
670
+ """Server JWKS source used only in hosted deployments."""
671
+
672
+ def __init__(self, url: str) -> None:
673
+ self._url = url
674
+
675
+ async def fetch_verification_keys(self) -> Mapping[str, str]:
676
+ import httpx
677
+
678
+ async with httpx.AsyncClient(timeout=5.0, follow_redirects=False) as client:
679
+ response = await client.get(self._url)
680
+ response.raise_for_status()
681
+ raw = response.json().get("keys") or {}
682
+ if isinstance(raw, Mapping):
683
+ return {str(k): str(v) for k, v in raw.items()}
684
+ # 标准 JWKS shape:[{"kty","crv","kid","x"}, ...]
685
+ return {
686
+ str(item["kid"]): str(item["x"])
687
+ for item in raw
688
+ if isinstance(item, Mapping) and "kid" in item and "x" in item
689
+ }
690
+
691
+
692
+ def _remote_jwks_source(jwks_url: str | None = None) -> JwksSource:
693
+ """构造唯一的 Server JWKS source;空值绝不回退本地 authority。"""
694
+
695
+ url = (jwks_url or os.environ.get(ENV_JWKS_URL) or "").strip()
696
+ if not url:
697
+ raise RuntimeError("hosted agent kernel runtime requires AGENT_CONTROL_JWKS_URL")
698
+ return _HttpJwks(url)
699
+
700
+
701
+ def _env_permit_verifier(*, nonce_store: Any = None) -> Any:
702
+ """JWKS URL 配置时用远端 verifier;否则用进程内 issuer(本地/灰度)。
703
+
704
+ authority mode 决定是否合并进程内 issuer 公钥:
705
+
706
+ - local:合并本地公钥——canonical ingress 的 status/subscribe 等本地
707
+ trusted-context permit 与 server permit 都能被同一个 verifier 验签,
708
+ fail closed 语义不变(两把 key 都必须真实签名)。
709
+ - hosted:禁止合并本地公钥/自签。JWKS 内的 server key 是唯一信任源,
710
+ 本地签发的 permit 得到 unknown_signing_key -> fail closed。
711
+ """
712
+
713
+ jwks_url = os.environ.get(ENV_JWKS_URL, "").strip()
714
+ if jwks_url:
715
+ from ksadk.kernel.authorization import AgentControlPermitVerifier
716
+ source = _remote_jwks_source(jwks_url)
717
+ if _is_hosted():
718
+ # hosted 模式:server JWKS 是唯一信任源,绝不合并本地公钥。
719
+ return AgentControlPermitVerifier(source, nonce_store=nonce_store)
720
+
721
+ class _LocalCompatibleJwks:
722
+ async def fetch_verification_keys(self) -> Mapping[str, str]:
723
+ merged = dict(await source.fetch_verification_keys())
724
+ local = _default_issuer()
725
+ merged[local.key_id] = local._public_b64
726
+ return merged
727
+
728
+ return AgentControlPermitVerifier(_LocalCompatibleJwks(), nonce_store=nonce_store)
729
+ if _is_hosted():
730
+ raise RuntimeError("hosted agent kernel runtime requires AGENT_CONTROL_JWKS_URL")
731
+ return _default_issuer().verifier(nonce_store=nonce_store)
732
+
733
+
734
+ async def _ensure_shared_log_session(command: Any) -> None:
735
+ """canonical submit 前确保 session 存在(共享 event log 前置条件)。
736
+
737
+ hosted 链路里会话目录由 server/runtime service 维护;对直接落到本
738
+ runtime ingress 的首个命令(RunAgent enqueue 等),用 kernel runtime 的
739
+ session service 幂等补齐,否则 postgres store 的 accept_command 会在
740
+ 第一个事件上以 ``invalid_command: session does not exist`` 拒绝。
741
+ 失败时静默放行——store 的显式错误仍是最终裁决。
742
+ """
743
+ session_id = str(getattr(command, "session_id", "") or "")
744
+ if not session_id:
745
+ return
746
+ from ksadk.kernel.bootstrap import get_agent_kernel_runtime
747
+
748
+ runtime = get_agent_kernel_runtime()
749
+ service = getattr(getattr(runtime, "config", None), "session_service", None)
750
+ if service is None:
751
+ return
752
+ try:
753
+ if await service.get_session(session_id) is None:
754
+ await service.create_session(
755
+ agent_id=str(getattr(command, "agent_instance_id", "") or "runtime"),
756
+ user_id=str(getattr(command, "tenant_id", "") or "tenant"),
757
+ session_id=session_id,
758
+ )
759
+ except Exception:
760
+ pass
761
+
762
+
763
+ def _build_kernel_router() -> Any:
764
+ from ksadk.kernel.contracts import (
765
+ AgentControlPermit,
766
+ AgentStatusQuery,
767
+ )
768
+
769
+ router = APIRouter()
770
+
771
+ def _unavailable() -> JSONResponse:
772
+ return JSONResponse(
773
+ status_code=503,
774
+ content={
775
+ "error": {
776
+ "Code": "kernel_not_enabled",
777
+ "Message": "agent kernel is not registered",
778
+ }
779
+ },
780
+ )
781
+
782
+ def _hosted_permit(
783
+ request: Request, permit_data: Any | None = None
784
+ ) -> AgentControlPermit | JSONResponse:
785
+ """Hosted ingress accepts only a Server-issued permit.
786
+
787
+ POST actions use the wrapper ``permit`` object; GET SSE uses the
788
+ internal ``X-Agent-Control-Permit`` JSON header. Gateway strips that
789
+ header at the public edge, so it can only originate from Server.
790
+ """
791
+
792
+ raw = permit_data
793
+ if raw is None:
794
+ raw_header = request.headers.get("x-agent-control-permit")
795
+ if raw_header:
796
+ try:
797
+ raw = json.loads(raw_header)
798
+ except json.JSONDecodeError:
799
+ return JSONResponse(
800
+ status_code=403,
801
+ content={
802
+ "error": {
803
+ "Code": "invalid_permit",
804
+ "Message": "invalid permit header",
805
+ }
806
+ },
807
+ )
808
+ if raw is None:
809
+ return JSONResponse(
810
+ status_code=401,
811
+ content={
812
+ "error": {
813
+ "Code": "missing_permit",
814
+ "Message": "hosted authority requires a server-issued permit",
815
+ }
816
+ },
817
+ )
818
+ try:
819
+ return AgentControlPermit.model_validate(raw)
820
+ except Exception:
821
+ logger.info("rejected malformed hosted permit", exc_info=True)
822
+ return JSONResponse(
823
+ status_code=403,
824
+ content={"error": {"Code": "invalid_permit", "Message": "permit 格式无效"}},
825
+ )
826
+
827
+ @router.post(KERNEL_INGRESS_SUBMIT_PATH)
828
+ async def submit_agent_control(request: Request) -> Any:
829
+ kernel = get_agent_kernel()
830
+ if kernel is None:
831
+ return _unavailable()
832
+ body = await request.json()
833
+ from ksadk.kernel.contracts import AgentControlCommand
834
+
835
+ permit_data = body.get("permit")
836
+ try:
837
+ command = AgentControlCommand.model_validate(body.get("command") or body)
838
+ except Exception:
839
+ logger.info("rejected malformed agent control command", exc_info=True)
840
+ return JSONResponse(
841
+ status_code=400,
842
+ content={"error": {"Code": "invalid_command", "Message": "command 格式无效"}},
843
+ )
844
+ if _is_hosted():
845
+ permit = _hosted_permit(request, permit_data)
846
+ if isinstance(permit, JSONResponse):
847
+ return permit
848
+ elif permit_data:
849
+ try:
850
+ permit = AgentControlPermit.model_validate(permit_data)
851
+ except Exception:
852
+ logger.info("rejected malformed local permit", exc_info=True)
853
+ return JSONResponse(
854
+ status_code=403,
855
+ content={"error": {"Code": "invalid_permit", "Message": "permit 格式无效"}},
856
+ )
857
+ else:
858
+ # 无 permit(gateway 内网转发 / 本地灰度):trusted context 进程内签发。
859
+ trusted = trusted_context(
860
+ source_kind="system",
861
+ source_ref=str(command.command_id),
862
+ session_id=command.session_id or None,
863
+ operations=(command.command_type,),
864
+ )
865
+ permit = trusted.permit
866
+ command = command.model_copy(
867
+ update={
868
+ "tenant_id": trusted.tenant_id,
869
+ "agent_instance_id": trusted.agent_instance_id,
870
+ "authorization_ref": permit.permit_id,
871
+ }
872
+ )
873
+ await _ensure_shared_log_session(command)
874
+ receipt = await kernel.submit(command, permit=permit)
875
+ status = receipt_http_status(receipt)
876
+ if (
877
+ _is_hosted()
878
+ and status != 202
879
+ and receipt.error is not None
880
+ and receipt.error.code == "invalid_permit"
881
+ ):
882
+ # hosted 模式 permit 验证失败是鉴权失败(403),不是普通 400。
883
+ status = 403
884
+ return JSONResponse(
885
+ status_code=status,
886
+ content=json.loads(receipt.model_dump_json()),
887
+ headers=receipt_response_headers(receipt),
888
+ )
889
+
890
+ @router.post(KERNEL_INGRESS_STATUS_PATH)
891
+ async def get_agent_status(request: Request) -> Any:
892
+ kernel = get_agent_kernel()
893
+ if kernel is None:
894
+ return _unavailable()
895
+ body = await request.json()
896
+ try:
897
+ query = AgentStatusQuery.model_validate(body.get("query") or body)
898
+ except Exception:
899
+ logger.info("rejected malformed agent status query", exc_info=True)
900
+ return JSONResponse(
901
+ status_code=400,
902
+ content={"error": {"Code": "invalid_query", "Message": "query 格式无效"}},
903
+ )
904
+ if _is_hosted():
905
+ permit = _hosted_permit(request, body.get("permit"))
906
+ if isinstance(permit, JSONResponse):
907
+ return permit
908
+ else:
909
+ trusted = trusted_context(
910
+ source_kind="system",
911
+ source_ref="status",
912
+ tenant_id=query.tenant_id,
913
+ agent_instance_id=query.agent_instance_id,
914
+ session_id=query.session_id,
915
+ operations=("get_status",),
916
+ )
917
+ # local 仅为开发便利自签,query 的 authorization_ref 必须同 permit
918
+ # 本体一致,避免错误地用 caller 自报值触发恒 fail-closed。
919
+ query = query.model_copy(
920
+ update={"authorization_ref": trusted.permit.permit_id}
921
+ )
922
+ permit = trusted.permit
923
+ snapshot = await kernel.status(query, permit=permit)
924
+ return JSONResponse(json.loads(snapshot.model_dump_json()))
925
+
926
+ @router.get(KERNEL_INGRESS_SESSION_EVENTS_PATH)
927
+ async def subscribe_session_events(request: Request) -> Any:
928
+ kernel = get_agent_kernel()
929
+ if kernel is None:
930
+ return _unavailable()
931
+ params = request.query_params
932
+ session_id = str(params.get("session_id") or "")
933
+ if not session_id:
934
+ return JSONResponse(
935
+ status_code=400,
936
+ content={
937
+ "error": {
938
+ "Code": "missing_session_id",
939
+ "Message": "session_id is required",
940
+ }
941
+ },
942
+ )
943
+ instance_id = str(params.get("agent_instance_id") or "").strip()
944
+ tenant_id = str(params.get("tenant_id") or "").strip()
945
+ if _is_hosted() and (not instance_id or not tenant_id):
946
+ return JSONResponse(
947
+ status_code=400,
948
+ content={
949
+ "error": {
950
+ "Code": "missing_resource_identity",
951
+ "Message": (
952
+ "hosted subscription requires tenant_id and "
953
+ "agent_instance_id"
954
+ ),
955
+ }
956
+ },
957
+ )
958
+ # Local development has no Server-issued identity projection. Keep
959
+ # its explicit compatibility defaults out of the hosted branch above.
960
+ instance_id = instance_id or "local-agent"
961
+ tenant_id = tenant_id or "local"
962
+ try:
963
+ after_seq = int(params.get("after_seq") or 0)
964
+ except ValueError:
965
+ after_seq = 0
966
+ # 可选订阅时长上限(秒):调用方(网关/测试)可显式限定 SSE 生命周期。
967
+ try:
968
+ subscribe_timeout = float(params.get("timeout") or 0) or None
969
+ except ValueError:
970
+ subscribe_timeout = None
971
+ if _is_hosted():
972
+ permit = _hosted_permit(request)
973
+ if isinstance(permit, JSONResponse):
974
+ return permit
975
+ trusted = TrustedRuntimeContext(
976
+ tenant_id=tenant_id,
977
+ agent_instance_id=instance_id,
978
+ source=ControlSource(kind="system", ref="server-subscribe"),
979
+ permit=permit,
980
+ received_at=datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
981
+ )
982
+ else:
983
+ trusted = trusted_context(
984
+ source_kind="system",
985
+ source_ref="events",
986
+ tenant_id=tenant_id,
987
+ agent_instance_id=instance_id,
988
+ session_id=session_id,
989
+ operations=("subscribe_events",),
990
+ )
991
+
992
+ async def _client_disconnected() -> bool:
993
+ # 客户端断开后及时收口 SSE,而不是轮询到订阅 timeout。
994
+ try:
995
+ return await request.is_disconnected()
996
+ except Exception: # pragma: no cover - defensive
997
+ return False
998
+
999
+ async def generator():
1000
+ async for seq, envelope in subscribe_projected(
1001
+ session_id,
1002
+ trusted=trusted,
1003
+ after_seq=after_seq,
1004
+ should_stop=_client_disconnected,
1005
+ timeout=subscribe_timeout,
1006
+ ):
1007
+ payload = (
1008
+ envelope.payload
1009
+ if isinstance(envelope, dict)
1010
+ else getattr(envelope, "payload", {})
1011
+ ) or {}
1012
+ # SSE 消费方(gateway / hosted UI)需要 family/event_type/seq
1013
+ # 判别事件流类别,payload 原样内嵌。
1014
+ frame = dict(payload)
1015
+ frame.setdefault("seq", seq)
1016
+ if not isinstance(envelope, dict):
1017
+ frame.setdefault("family", getattr(envelope, "family", None))
1018
+ frame.setdefault(
1019
+ "family_version", getattr(envelope, "family_version", None)
1020
+ )
1021
+ frame.setdefault("event_type", getattr(envelope, "event_type", None))
1022
+ if getattr(envelope, "run_id", None):
1023
+ frame.setdefault("run_id", envelope.run_id)
1024
+ yield f"id: {seq}\ndata: {json.dumps(frame, ensure_ascii=False)}\n\n"
1025
+
1026
+ return StreamingResponse(generator(), media_type="text/event-stream")
1027
+
1028
+ @router.get(KERNEL_INGRESS_HEALTH_PATH)
1029
+ async def kernel_health() -> Any:
1030
+ from ksadk.kernel.contract_fingerprints import (
1031
+ AGENT_KERNEL_V1_AGGREGATE_DIGEST,
1032
+ )
1033
+ from ksadk.kernel.runtime_identity import runtime_identity
1034
+
1035
+ kernel = get_agent_kernel()
1036
+ payload: dict[str, Any] = {
1037
+ "enabled": kernel_ingress_enabled(),
1038
+ "ready": kernel is not None,
1039
+ "store_driver": os.environ.get(ENV_KERNEL_STORE_DRIVER, "memory"),
1040
+ # A process without the full runtime cannot calculate Adapter
1041
+ # capabilities, so do not echo a caller-controlled env value.
1042
+ "contract_digest": AGENT_KERNEL_V1_AGGREGATE_DIGEST,
1043
+ "capability_digest": "",
1044
+ "authority_mode": authority_mode(),
1045
+ "runtime_identity": runtime_identity(),
1046
+ }
1047
+ from ksadk.kernel.bootstrap import get_agent_kernel_runtime
1048
+
1049
+ runtime = get_agent_kernel_runtime()
1050
+ if runtime is not None:
1051
+ # 生产 composition root 注册后,health 报告真实运行态:
1052
+ # 真实 store 查询 / worker 运行态 / activation lease 健康 / digest。
1053
+ health = await runtime.readiness.check()
1054
+ payload.update(health)
1055
+ return JSONResponse(payload)
1056
+
1057
+ return router
1058
+
1059
+
1060
+ _agent_kernel_router: Any | None = None
1061
+
1062
+
1063
+ def agent_kernel_router() -> Any:
1064
+ """kernel ingress HTTP 路由(/agent-kernel/v1/*);由 server 装配层 include。"""
1065
+
1066
+ global _agent_kernel_router
1067
+ if _agent_kernel_router is None:
1068
+ _agent_kernel_router = _build_kernel_router()
1069
+ return _agent_kernel_router
1070
+
1071
+
1072
+ __all__ = [
1073
+ "ENV_KERNEL_ENABLED",
1074
+ "InProcessPermitIssuer",
1075
+ "KERNEL_INGRESS_BASE_PATH",
1076
+ "KERNEL_INGRESS_HEALTH_PATH",
1077
+ "KERNEL_INGRESS_SESSION_EVENTS_PATH",
1078
+ "KERNEL_INGRESS_STATUS_PATH",
1079
+ "KERNEL_INGRESS_SUBMIT_PATH",
1080
+ "RECEIPT_HTTP_STATUS",
1081
+ "TrustedRuntimeContext",
1082
+ "agent_kernel_router",
1083
+ "authority_mode",
1084
+ "bootstrap_agent_kernel_from_env",
1085
+ "clear_agent_kernel",
1086
+ "get_agent_kernel",
1087
+ "kernel_ingress_enabled",
1088
+ "kernel_route_active",
1089
+ "map_a2a_task",
1090
+ "map_agui_request",
1091
+ "map_control_request",
1092
+ "map_responses_request",
1093
+ "map_run_request",
1094
+ "map_studio_request",
1095
+ "receipt_error_payload",
1096
+ "receipt_http_status",
1097
+ "receipt_response_headers",
1098
+ "set_agent_kernel",
1099
+ "submit_command",
1100
+ "subscribe_projected",
1101
+ "trusted_context",
1102
+ ]