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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (476) hide show
  1. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.3.dist-info}/METADATA +25 -12
  2. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.3.dist-info}/RECORD +421 -232
  3. ksadk/_build_provenance.json +1 -0
  4. ksadk/a2a/_space_client_events.py +381 -0
  5. ksadk/a2a/event_adapter.py +156 -64
  6. ksadk/a2a/executor.py +179 -63
  7. ksadk/a2a/space_client.py +6 -333
  8. ksadk/a2ui/core.py +222 -53
  9. ksadk/agui/_agent_helpers.py +247 -0
  10. ksadk/agui/a2ui_projection.py +12 -5
  11. ksadk/agui/agent.py +443 -259
  12. ksadk/api/client.py +528 -27
  13. ksadk/builders/code_builder.py +284 -8
  14. ksadk/builders/container_builder.py +8 -1
  15. ksadk/builders/managed_runtime_builder.py +42 -35
  16. ksadk/cli/__init__.py +23 -0
  17. ksadk/cli/cmd_dashboard.py +15 -1
  18. ksadk/cli/cmd_deploy.py +10 -2
  19. ksadk/cli/cmd_eval.py +66 -9
  20. ksadk/cli/cmd_evalset.py +284 -0
  21. ksadk/cli/cmd_files.py +5 -1
  22. ksadk/cli/cmd_hermes.py +1 -126
  23. ksadk/cli/cmd_invoke.py +28 -38
  24. ksadk/cli/cmd_managed_runtime.py +95 -0
  25. ksadk/cli/cmd_observe.py +85 -0
  26. ksadk/cli/cmd_openclaw.py +1 -435
  27. ksadk/cli/cmd_plugin.py +657 -0
  28. ksadk/cli/cmd_replay.py +28 -4
  29. ksadk/cli/cmd_studio.py +99 -8
  30. ksadk/cli/cmd_web.py +10 -2
  31. ksadk/cli/hermes_env.py +151 -0
  32. ksadk/cli/invoke_payload.py +49 -0
  33. ksadk/cli/openclaw_env.py +458 -0
  34. ksadk/codex/client.py +180 -30
  35. ksadk/codex/runtime.py +475 -331
  36. ksadk/configs/env_registry.py +94 -12
  37. ksadk/configs/env_registry_pcm.py +181 -0
  38. ksadk/configs/env_var_spec.py +18 -0
  39. ksadk/configs/global_config.py +5 -0
  40. ksadk/configs/settings.py +9 -0
  41. ksadk/context_engine/__init__.py +98 -0
  42. ksadk/context_engine/assembler.py +176 -0
  43. ksadk/context_engine/baseline.py +419 -0
  44. ksadk/context_engine/cache_observability.py +228 -0
  45. ksadk/context_engine/capabilities.py +443 -0
  46. ksadk/context_engine/contributors.py +409 -0
  47. ksadk/context_engine/hosted_pipeline.py +417 -0
  48. ksadk/context_engine/models.py +117 -0
  49. ksadk/context_engine/planner.py +698 -0
  50. ksadk/context_engine/policies.py +319 -0
  51. ksadk/context_engine/projection.py +31 -0
  52. ksadk/context_engine/shadow_plan.py +266 -0
  53. ksadk/context_engine/tokenizer.py +164 -0
  54. ksadk/conversations/__init__.py +19 -0
  55. ksadk/conversations/context.py +230 -30
  56. ksadk/conversations/contracts.py +266 -0
  57. ksadk/conversations/message_projection.py +308 -26
  58. ksadk/conversations/model_context.py +49 -3
  59. ksadk/conversations/projector.py +616 -0
  60. ksadk/conversations/reducer.py +98 -0
  61. ksadk/conversations/runtime_compaction.py +207 -5
  62. ksadk/conversations/runtime_input.py +184 -2
  63. ksadk/conversations/runtime_invocation.py +81 -3
  64. ksadk/conversations/runtime_observability.py +141 -0
  65. ksadk/conversations/runtime_payloads.py +42 -0
  66. ksadk/conversations/runtime_preparation.py +272 -2
  67. ksadk/conversations/runtime_resume.py +20 -1
  68. ksadk/conversations/runtime_stream_events.py +94 -9
  69. ksadk/conversations/semantic_summary.py +311 -1
  70. ksadk/conversations/session_lock.py +51 -0
  71. ksadk/deployment/env_forward.py +72 -0
  72. ksadk/deployment/managed_runtime.py +5 -0
  73. ksadk/deployment/providers/serverless.py +143 -69
  74. ksadk/evaluation/__init__.py +32 -0
  75. ksadk/evaluation/a2a_adapter.py +9 -1
  76. ksadk/evaluation/adapters.py +18 -5
  77. ksadk/evaluation/agent_eval_client.py +395 -0
  78. ksadk/evaluation/cloud_binding.py +80 -0
  79. ksadk/evaluation/cloud_converter.py +194 -0
  80. ksadk/evaluation/cloud_service.py +199 -0
  81. ksadk/evaluation/contracts.py +73 -4
  82. ksadk/evaluation/evaluators.py +183 -17
  83. ksadk/evaluation/evidence.py +258 -0
  84. ksadk/evaluation/executor.py +31 -6
  85. ksadk/evaluation/local_adapter.py +550 -0
  86. ksadk/evaluation/service_env.py +27 -0
  87. ksadk/evaluation/studio_build_adapter.py +253 -0
  88. ksadk/evaluation/target.py +15 -6
  89. ksadk/events/__init__.py +13 -13
  90. ksadk/events/_v1_compat/__init__.py +1 -0
  91. ksadk/events/_v1_compat/models.py +299 -0
  92. ksadk/events/_v1_compat/parser.py +247 -0
  93. ksadk/events/_v1_compat/projection.py +755 -0
  94. ksadk/events/adapters/__init__.py +34 -0
  95. ksadk/events/adapters/_a2a_snapshot.py +622 -0
  96. ksadk/events/adapters/_a2a_support.py +275 -0
  97. ksadk/events/adapters/_codex_interactions.py +375 -0
  98. ksadk/events/adapters/_codex_items.py +801 -0
  99. ksadk/events/adapters/_codex_validators.py +311 -0
  100. ksadk/events/adapters/_langgraph_support.py +785 -0
  101. ksadk/events/adapters/a2a.py +841 -0
  102. ksadk/events/adapters/adk.py +503 -0
  103. ksadk/events/adapters/codex.py +907 -0
  104. ksadk/events/adapters/langgraph.py +745 -0
  105. ksadk/events/canonical.py +440 -0
  106. ksadk/events/canonical_replay.py +497 -0
  107. ksadk/events/canonical_store.py +468 -0
  108. ksadk/events/cold_recovery.py +257 -0
  109. ksadk/events/content.py +89 -0
  110. ksadk/events/identity.py +86 -0
  111. ksadk/events/pipeline.py +455 -0
  112. ksadk/events/projections.py +66 -0
  113. ksadk/events/reducer.py +661 -0
  114. ksadk/events/replay.py +3 -36
  115. ksadk/events/runtime_event.py +20 -265
  116. ksadk/events/session_event.py +324 -0
  117. ksadk/events/store.py +3 -316
  118. ksadk/events/v1_compat.py +41 -0
  119. ksadk/harness/__init__.py +45 -35
  120. ksadk/harness/runtime.py +220 -58
  121. ksadk/identity/resolver.py +4 -3
  122. ksadk/interaction/__init__.py +28 -0
  123. ksadk/interaction/contracts.py +136 -0
  124. ksadk/interaction/ledger.py +189 -0
  125. ksadk/interaction/provider.py +122 -0
  126. ksadk/interaction/providers/__init__.py +52 -0
  127. ksadk/interaction/providers/adk.py +40 -0
  128. ksadk/interaction/providers/codex.py +94 -0
  129. ksadk/interaction/providers/langgraph.py +58 -0
  130. ksadk/kernel/__init__.py +117 -0
  131. ksadk/kernel/authorization.py +258 -0
  132. ksadk/kernel/bootstrap.py +1074 -0
  133. ksadk/kernel/contract_fingerprints.py +62 -0
  134. ksadk/kernel/contracts.py +370 -0
  135. ksadk/kernel/control.py +220 -0
  136. ksadk/kernel/errors.py +79 -0
  137. ksadk/kernel/ingress.py +1123 -0
  138. ksadk/kernel/mapping.py +111 -0
  139. ksadk/kernel/memory_store.py +1134 -0
  140. ksadk/kernel/postgres_store.py +1757 -0
  141. ksadk/kernel/recovery.py +452 -0
  142. ksadk/kernel/runtime_identity.py +103 -0
  143. ksadk/kernel/sql/001_agent_kernel.sql +120 -0
  144. ksadk/kernel/sqlite_store.py +1411 -0
  145. ksadk/kernel/state.py +145 -0
  146. ksadk/kernel/store.py +232 -0
  147. ksadk/kernel/worker.py +964 -0
  148. ksadk/knowledge_base/client.py +7 -0
  149. ksadk/knowledge_base/service.py +32 -5
  150. ksadk/memory/__init__.py +61 -0
  151. ksadk/memory/adk/backends/base_ltm_backend.py +96 -1
  152. ksadk/memory/adk/backends/http_ltm_backend.py +14 -5
  153. ksadk/memory/adk/backends/inmemory_ltm_backend.py +136 -3
  154. ksadk/memory/adk/backends/sdk_ltm_backend.py +384 -2
  155. ksadk/memory/adk/backends/sqlite_ltm_backend.py +95 -0
  156. ksadk/memory/coordinator.py +461 -0
  157. ksadk/memory/events.py +202 -0
  158. ksadk/memory/extraction.py +228 -0
  159. ksadk/memory/ltm_backend_factory.py +16 -3
  160. ksadk/memory/models.py +316 -0
  161. ksadk/memory/policy.py +215 -0
  162. ksadk/memory/provider.py +47 -0
  163. ksadk/memory/provider_adapter.py +159 -0
  164. ksadk/memory/provider_resolver.py +71 -0
  165. ksadk/memory/providers/__init__.py +5 -0
  166. ksadk/memory/providers/local_sqlite.py +497 -0
  167. ksadk/memory/resolved_policy.py +145 -0
  168. ksadk/memory/service.py +188 -6
  169. ksadk/model_proxy/bootstrap.py +16 -9
  170. ksadk/model_proxy/detect.py +202 -9
  171. ksadk/model_proxy/namespace.py +39 -15
  172. ksadk/model_proxy/server.py +24 -5
  173. ksadk/model_proxy/transform.py +60 -2
  174. ksadk/observability/__init__.py +25 -0
  175. ksadk/observability/session_log.py +420 -0
  176. ksadk/observability/trajectory.py +105 -0
  177. ksadk/plugins/__init__.py +167 -0
  178. ksadk/plugins/bridges/__init__.py +45 -0
  179. ksadk/plugins/bridges/codex.py +552 -0
  180. ksadk/plugins/bridges/dsh.py +1116 -0
  181. ksadk/plugins/builtins.py +747 -0
  182. ksadk/plugins/bundle.py +246 -0
  183. ksadk/plugins/bundle_security.py +167 -0
  184. ksadk/plugins/composition.py +529 -0
  185. ksadk/plugins/context_contributor.py +437 -0
  186. ksadk/plugins/contracts.py +451 -0
  187. ksadk/plugins/dsh_toolchain.py +935 -0
  188. ksadk/plugins/ecosystem_bridge.py +1105 -0
  189. ksadk/plugins/ecosystem_probe.py +191 -0
  190. ksadk/plugins/host.py +724 -0
  191. ksadk/plugins/providers/__init__.py +85 -0
  192. ksadk/plugins/providers/bundles/ksadk-codex/cordis.patch.yml +3 -0
  193. ksadk/plugins/providers/bundles/ksadk-codex/index.mjs +18 -0
  194. ksadk/plugins/providers/bundles/ksadk-codex/package.json +19 -0
  195. ksadk/plugins/providers/bundles/ksadk-harness/cordis.patch.yml +3 -0
  196. ksadk/plugins/providers/bundles/ksadk-harness/index.mjs +19 -0
  197. ksadk/plugins/providers/bundles/ksadk-harness/package.json +19 -0
  198. ksadk/plugins/providers/codex.py +722 -0
  199. ksadk/plugins/providers/codex_dsh.py +83 -0
  200. ksadk/plugins/providers/dsh.py +951 -0
  201. ksadk/plugins/providers/dsh_descriptor_host.py +226 -0
  202. ksadk/plugins/providers/harness.py +627 -0
  203. ksadk/plugins/providers/harness_dsh.py +91 -0
  204. ksadk/plugins/providers/legacy.py +215 -0
  205. ksadk/plugins/providers/legacy_catalog.py +100 -0
  206. ksadk/plugins/providers/shipped_dsh.py +370 -0
  207. ksadk/plugins/resolver.py +331 -0
  208. ksadk/plugins/subagent_providers/__init__.py +12 -0
  209. ksadk/plugins/subagent_providers/codex.py +443 -0
  210. ksadk/plugins/subagents.py +333 -0
  211. ksadk/prompts/__init__.py +71 -0
  212. ksadk/prompts/compiler.py +215 -0
  213. ksadk/prompts/models.py +82 -0
  214. ksadk/prompts/projection.py +89 -0
  215. ksadk/prompts/resolved.py +137 -0
  216. ksadk/prompts/sources.py +233 -0
  217. ksadk/runners/_langgraph_runner_streams.py +822 -0
  218. ksadk/runners/adk_runner.py +5 -5
  219. ksadk/runners/base_runner.py +16 -0
  220. ksadk/runners/factory.py +1 -2
  221. ksadk/runners/langgraph_runner.py +198 -437
  222. ksadk/runtime/_runner_adapter/__init__.py +1 -0
  223. ksadk/runtime/_runner_adapter/stream_mapping.py +797 -0
  224. ksadk/runtime/adapter.py +119 -2
  225. ksadk/runtime/conversation_execution.py +316 -182
  226. ksadk/runtime/executor.py +144 -3
  227. ksadk/runtime/factory.py +268 -8
  228. ksadk/runtime/framework_adapters.py +18 -0
  229. ksadk/runtime/hosted_finalizer.py +268 -0
  230. ksadk/runtime/launch.py +14 -1
  231. ksadk/runtime/preprocessing.py +25 -3
  232. ksadk/runtime/runner_adapter.py +293 -508
  233. ksadk/runtime/usage.py +33 -0
  234. ksadk/sandbox/__init__.py +2 -1
  235. ksadk/sandbox/backends/e2b.py +35 -2
  236. ksadk/sandbox/factory.py +72 -0
  237. ksadk/scheduler/__init__.py +18 -0
  238. ksadk/scheduler/calendar.py +102 -0
  239. ksadk/scheduler/contracts.py +254 -0
  240. ksadk/scheduler/dispatcher.py +129 -0
  241. ksadk/scheduler/engine.py +416 -0
  242. ksadk/scheduler/sqlite_store.py +563 -0
  243. ksadk/server/composition.py +8 -0
  244. ksadk/server/factory.py +69 -6
  245. ksadk/server/routes/kernel_ingress.py +258 -0
  246. ksadk/server/routes/openai_compat.py +88 -8
  247. ksadk/server/routes/projection.py +35 -20
  248. ksadk/server/routes/run.py +94 -0
  249. ksadk/server/routes/sessions.py +2 -0
  250. ksadk/server/routes/streaming.py +5 -2
  251. ksadk/server/static/assets/{ArtifactsPanel-CakCqxEN.js → ArtifactsPanel-10GPXIJ6.js} +1 -1
  252. ksadk/server/static/assets/{CodeBlock-CynLknyO.js → CodeBlock-BXk1l-G1.js} +4 -4
  253. ksadk/server/static/assets/MathMessageMarkdown-BorAY7qD.css +1 -0
  254. ksadk/server/static/assets/{MathMessageMarkdown-3sQdOysS.js → MathMessageMarkdown-D9uB55Oh.js} +4 -4
  255. ksadk/server/static/assets/MermaidBlock--OEYoXIJ.js +312 -0
  256. ksadk/server/static/assets/NativeTerminalPanel-DrEe1syq.js +1 -0
  257. ksadk/server/static/assets/abnfDiagram-VCTEODGH-eeTFJLvo.js +1 -0
  258. ksadk/server/static/assets/arc-S3ZNbIAd.js +1 -0
  259. ksadk/server/static/assets/architecture-7GRP2DOG-Dk9nRQ6-.js +1 -0
  260. ksadk/server/static/assets/architectureDiagram-5GKGNRK7-CRjgEJDX.js +36 -0
  261. ksadk/server/static/assets/blockDiagram-NRAW4CY4-C7bsBoNH.js +129 -0
  262. ksadk/server/static/assets/c4Diagram-UCG6FXSJ-BX3c33Ob.js +38 -0
  263. ksadk/server/static/assets/channel-s354Yo6o.js +1 -0
  264. ksadk/server/static/assets/chunk-2Q5K7J3B-CDpPpKR5.js +1 -0
  265. ksadk/server/static/assets/{chunk-FMBD7UC4-B_fH2YuH.js → chunk-5VM5RSS4-bZ7ws-8m.js} +1 -1
  266. ksadk/server/static/assets/{chunk-ND2GUHAM-D6rFLaVV.js → chunk-F27PBJKO-D2r0pvhY.js} +1 -1
  267. ksadk/server/static/assets/chunk-G27WJ6UU-CZwF3oUP.js +231 -0
  268. ksadk/server/static/assets/{chunk-4BX2VUAB-B9drTgOW.js → chunk-JWPE2WC7-DigFYCML.js} +1 -1
  269. ksadk/server/static/assets/chunk-LCL6LL3I-CnorGV5q.js +206 -0
  270. ksadk/server/static/assets/chunk-POPQ4Y6H-CexntQA-.js +1 -0
  271. ksadk/server/static/assets/chunk-RHFEMEQ7-CHTC7UxA.js +168 -0
  272. ksadk/server/static/assets/chunk-SVP7TREG-BC1EWt_-.js +88 -0
  273. ksadk/server/static/assets/chunk-XXDRQBXY-BTG24xN0.js +1 -0
  274. ksadk/server/static/assets/classDiagram-DTDB5LWJ-Dd0nucl9.js +1 -0
  275. ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-Dd0nucl9.js +1 -0
  276. ksadk/server/static/assets/{cose-bilkent-S5V4N54A-ChNs_Mdk.js → cose-bilkent-JH36ORCC-CkonRdAS.js} +1 -1
  277. ksadk/server/static/assets/cynefin-OW5HDTMX-BBlT4uMf.js +1 -0
  278. ksadk/server/static/assets/cynefinDiagram-5FMLGOSQ-DEOCBe15.js +62 -0
  279. ksadk/server/static/assets/cytoscape.esm-CyCl8rPi.js +321 -0
  280. ksadk/server/static/assets/dagre-3AP2YEHR-DBThPuLa.js +4 -0
  281. ksadk/server/static/assets/dagre-ClTPrTht.js +1 -0
  282. ksadk/server/static/assets/diagram-S7CK7UJ4-DsZHrnpJ.js +30 -0
  283. ksadk/server/static/assets/diagram-UQ7AKVKN-BJR4nhVr.js +41 -0
  284. ksadk/server/static/assets/diagram-VSXAHHWV-xzcERX0a.js +3 -0
  285. ksadk/server/static/assets/diagram-VX7I27RA-tn2xM1xu.js +24 -0
  286. ksadk/server/static/assets/diagram-Z3DM3KII-BZPGBkw5.js +24 -0
  287. ksadk/server/static/assets/{dist-gLnrGsoU.js → dist-4zN1_tq5.js} +1 -1
  288. ksadk/server/static/assets/{dist-C1sz5Eo3.js → dist-8VWI-2bZ.js} +1 -1
  289. ksadk/server/static/assets/{dist-BjOdlDjj.js → dist-B-46q3Hk.js} +1 -1
  290. ksadk/server/static/assets/{dist-o2OVmKvt.js → dist-B0gdk4rl.js} +1 -1
  291. ksadk/server/static/assets/{dist-4N2NMVYg.js → dist-B1oWRmrH.js} +1 -1
  292. ksadk/server/static/assets/{dist-CmZHzlwZ.js → dist-BI1qQH4n.js} +1 -1
  293. ksadk/server/static/assets/{dist-X4TnqwOK.js → dist-BJLUwH6W.js} +1 -1
  294. ksadk/server/static/assets/{dist-B5oS1zcj.js → dist-Bbwqa8TG.js} +1 -1
  295. ksadk/server/static/assets/{dist-j6Dxmf_j.js → dist-Btyo4vX4.js} +1 -1
  296. ksadk/server/static/assets/{dist-DQU9EWw-.js → dist-C71CsujO.js} +1 -1
  297. ksadk/server/static/assets/{dist-BIlKoW0k.js → dist-CXpH_Rdd.js} +1 -1
  298. ksadk/server/static/assets/{dist-DJN_6Y39.js → dist-Cq4OL6nO.js} +1 -1
  299. ksadk/server/static/assets/{dist-Dli7qW-B.js → dist-D1V3f1B4.js} +1 -1
  300. ksadk/server/static/assets/{dist-fhOuWds5.js → dist-DJmCPRVB.js} +1 -1
  301. ksadk/server/static/assets/{dist-CWV4lDr1.js → dist-DQAB0qn_.js} +2 -2
  302. ksadk/server/static/assets/{dist-D6MpCZtz.js → dist-DUwKa_l6.js} +1 -1
  303. ksadk/server/static/assets/dist-DdRBIceA.js +1 -0
  304. ksadk/server/static/assets/{dist-DWKByxWV.js → dist-DfGFrb0v.js} +1 -1
  305. ksadk/server/static/assets/{dist-DsHPdk3W.js → dist-DjhwGUgZ.js} +1 -1
  306. ksadk/server/static/assets/{dist-B-jNR6Vn.js → dist-DyqV4fL4.js} +1 -1
  307. ksadk/server/static/assets/{dist-BB3-OwTQ.js → dist-M67Hwu0n.js} +1 -1
  308. ksadk/server/static/assets/{dist-_pHPTyZ-.js → dist-u8tVH5Ak.js} +1 -1
  309. ksadk/server/static/assets/ebnfDiagram-PWID7BFC-DVfMsLge.js +1 -0
  310. ksadk/server/static/assets/erDiagram-SSCWMZ5O-CWEZSYaw.js +99 -0
  311. ksadk/server/static/assets/eventmodeling-NTZA5JFV-Dr5okd8V.js +1 -0
  312. ksadk/server/static/assets/flowDiagram-A5DVABFB-VEO-cVcO.js +1 -0
  313. ksadk/server/static/assets/ganttDiagram-EL5Y4UJY-TWIxoyYL.js +292 -0
  314. ksadk/server/static/assets/gitGraph-4MIJSDKK-CKiKY9Cf.js +1 -0
  315. ksadk/server/static/assets/gitGraphDiagram-WWUBYQGX-DDh2hd2S.js +106 -0
  316. ksadk/server/static/assets/index-B2k_urY8.js +241 -0
  317. ksadk/server/static/assets/index-ILPt6IWF.css +2 -0
  318. ksadk/server/static/assets/info-A6RAGUB7-ubA4Nm_p.js +1 -0
  319. ksadk/server/static/assets/infoDiagram-RXCK75RN-CgoyWiDR.js +2 -0
  320. ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-K0OOfVna.js → ishikawaDiagram-5VMMS53U-CyRIu_om.js} +6 -6
  321. ksadk/server/static/assets/{journeyDiagram-JHISSGLW-8LeOJaZp.js → journeyDiagram-EYS64GPL-DN5JXHzN.js} +6 -6
  322. ksadk/server/static/assets/{kanban-definition-UN3LZRKU-C6uCgZrW.js → kanban-definition-3QL26DDD-CNP3ui66.js} +10 -10
  323. ksadk/server/static/assets/katex-DWEzQy1d.js +257 -0
  324. ksadk/server/static/assets/{linear-hdBcYzM8.js → linear-C-qHp7d2.js} +1 -1
  325. ksadk/server/static/assets/mermaid-parser.core-Cl-K943T.js +166 -0
  326. ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-Bjy_2v4_.js → mindmap-definition-FBJOCRG2-D5DB5mYm.js} +29 -29
  327. ksadk/server/static/assets/packet-AYTQ26CC-C7R3NUlK.js +1 -0
  328. ksadk/server/static/assets/pegDiagram-XKGWAZYB-BQVc9ia_.js +1 -0
  329. ksadk/server/static/assets/pie-WAS4IAKB-BIT6qjr3.js +1 -0
  330. ksadk/server/static/assets/pieDiagram-E7YTZNPT-B6UCAvtY.js +39 -0
  331. ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-BmkRbwCg.js → quadrantDiagram-AXDQQJYC-mIr2d3eb.js} +6 -6
  332. ksadk/server/static/assets/radar-RG4KPBEZ-l5noef9X.js +1 -0
  333. ksadk/server/static/assets/railroad-74A4TZTK-Baea6ZvM.js +1 -0
  334. ksadk/server/static/assets/railroad-abnf-HS5TGJTU-OKn8zZnU.js +1 -0
  335. ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-DdKAKC4l.js +1 -0
  336. ksadk/server/static/assets/railroad-peg-WCYAUIDC-Bds8udlG.js +1 -0
  337. ksadk/server/static/assets/railroadDiagram-O6MQD6OU-Dt3FFDmK.js +1 -0
  338. ksadk/server/static/assets/requirementDiagram-EFPCY7ZU-Cz_vQtgF.js +84 -0
  339. ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-l6iXmU65.js → sankeyDiagram-P5KCCOFB-wlO1TsH5.js} +7 -7
  340. ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-Cq6UBeQB.js → sequenceDiagram-WJ2MYXX4-CHRbElYq.js} +10 -10
  341. ksadk/server/static/assets/sizeCapture-X5ZJPWSS-DXX54iyO.js +1 -0
  342. ksadk/server/static/assets/stateDiagram-HBIQ2CUA-Dp_lw9II.js +1 -0
  343. ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-5b-8o2iS.js +1 -0
  344. ksadk/server/static/assets/swimlanes-XN3QIQJK-D8O5UlLG.js +1 -0
  345. ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-uf6WPRA4.js +8 -0
  346. ksadk/server/static/assets/{timeline-definition-PNZ67QCA-DEz24vV0.js → timeline-definition-24CTP7MA-8mfKcxvG.js} +7 -7
  347. ksadk/server/static/assets/treeView-Q6P3EWNA-CK5CSrSO.js +1 -0
  348. ksadk/server/static/assets/treemap-WGGIJYW6-Ch2BKgAM.js +1 -0
  349. ksadk/server/static/assets/vennDiagram-4TSXK5OY-EnNl0DE7.js +34 -0
  350. ksadk/server/static/assets/wardley-WFR3VGLG-YW3Q0ml_.js +1 -0
  351. ksadk/server/static/assets/wardleyDiagram-VM6X3IG4-DBu5fbYr.js +78 -0
  352. ksadk/server/static/assets/xychartDiagram-S5SC5T6Z-BSVXs4IY.js +7 -0
  353. ksadk/server/static/index.html +2 -2
  354. ksadk/sessions/__init__.py +26 -9
  355. ksadk/sessions/_local_service_sync.py +688 -0
  356. ksadk/sessions/_local_tables.py +21 -0
  357. ksadk/sessions/_postgres_schema.py +305 -0
  358. ksadk/sessions/_postgres_tables.py +15 -0
  359. ksadk/sessions/base.py +80 -1
  360. ksadk/sessions/in_memory.py +53 -2
  361. ksadk/sessions/local_service.py +63 -604
  362. ksadk/sessions/postgres_service.py +58 -148
  363. ksadk/sessions/resilient.py +35 -1
  364. ksadk/skills/service_client.py +48 -0
  365. ksadk/studio/agent_lifecycle.py +26 -20
  366. ksadk/studio/api.py +1063 -69
  367. ksadk/studio/api_catalog_routes.py +5 -0
  368. ksadk/studio/api_contracts.py +148 -6
  369. ksadk/studio/api_memory_routes.py +82 -0
  370. ksadk/studio/api_plugin_routes.py +480 -0
  371. ksadk/studio/attachment_store.py +187 -0
  372. ksadk/studio/authoring.py +259 -28
  373. ksadk/studio/authoring_coordinator.py +809 -67
  374. ksadk/studio/builder.py +197 -40
  375. ksadk/studio/capabilities.py +17 -0
  376. ksadk/studio/cloud.py +1920 -95
  377. ksadk/studio/codex_agent_service.py +137 -15
  378. ksadk/studio/codex_authoring.py +469 -0
  379. ksadk/studio/codex_builder.py +177 -25
  380. ksadk/studio/codex_manifest.py +71 -22
  381. ksadk/studio/codex_run.py +59 -4
  382. ksadk/studio/compatibility_report.py +445 -0
  383. ksadk/studio/compiler.py +7 -1
  384. ksadk/studio/contracts.py +234 -4
  385. ksadk/studio/dsh_provider_registration.py +768 -0
  386. ksadk/studio/event_store.py +79 -101
  387. ksadk/studio/framework_run.py +264 -3
  388. ksadk/studio/hosted_kernel.py +317 -0
  389. ksadk/studio/manifest_resolver.py +131 -0
  390. ksadk/studio/model_client.py +95 -9
  391. ksadk/studio/model_profile_service.py +1 -1
  392. ksadk/studio/operations.py +47 -5
  393. ksadk/studio/otel_trace.py +348 -42
  394. ksadk/studio/pcm_memory.py +85 -0
  395. ksadk/studio/plugin_composition.py +301 -0
  396. ksadk/studio/plugin_kernel_adapter.py +128 -0
  397. ksadk/studio/plugin_runtime.py +652 -0
  398. ksadk/studio/repository.py +23 -2
  399. ksadk/studio/resource_catalog.py +49 -1
  400. ksadk/studio/run_service.py +1266 -202
  401. ksadk/studio/runtime_source.py +51 -17
  402. ksadk/studio/scheduler_runtime.py +374 -0
  403. ksadk/studio/scheduler_service.py +211 -0
  404. ksadk/studio/service.py +1815 -117
  405. ksadk/studio/shared_web.py +199 -50
  406. ksadk/studio/soul.py +52 -0
  407. ksadk/studio/static/assets/index-DXs1Eq4M.css +1 -0
  408. ksadk/studio/static/assets/index-DeDqwTsD.js +283 -0
  409. ksadk/studio/static/index.html +2 -2
  410. ksadk/studio/templates.py +100 -8
  411. ksadk/studio/validator.py +20 -1
  412. ksadk/studio/workspace.py +31 -32
  413. ksadk/tracing/setup.py +304 -3
  414. ksadk/version.py +1 -1
  415. ksadk_runtime_common/schemas/runtime_event_v1.json +37 -0
  416. ksadk_runtime_common/schemas/runtime_event_v2.json +271 -0
  417. ksadk_runtime_common/workspace_files/path_utils.py +41 -0
  418. ksadk/events/parser.py +0 -191
  419. ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +0 -1
  420. ksadk/server/static/assets/MermaidBlock-NERD_ef7.js +0 -303
  421. ksadk/server/static/assets/NativeTerminalPanel-CiHWf82q.js +0 -1
  422. ksadk/server/static/assets/arc-DrgjcKhl.js +0 -1
  423. ksadk/server/static/assets/architecture-7EHR7CIX-CpZLRCcY.js +0 -1
  424. ksadk/server/static/assets/architectureDiagram-3BPJPVTR-BXoVRbQ6.js +0 -36
  425. ksadk/server/static/assets/blockDiagram-GPEHLZMM-BnaB3Ywv.js +0 -132
  426. ksadk/server/static/assets/c4Diagram-AAUBKEIU-Dw4weZcp.js +0 -10
  427. ksadk/server/static/assets/channel-Dt3rQTFI.js +0 -1
  428. ksadk/server/static/assets/chunk-2J33WTMH-ChjpLK7j.js +0 -1
  429. ksadk/server/static/assets/chunk-55IACEB6-CK4ROATy.js +0 -1
  430. ksadk/server/static/assets/chunk-727SXJPM-B9KtTTgw.js +0 -206
  431. ksadk/server/static/assets/chunk-AQP2D5EJ-1O1mki3m.js +0 -231
  432. ksadk/server/static/assets/chunk-QZHKN3VN-iXEZtKQc.js +0 -1
  433. ksadk/server/static/assets/classDiagram-4FO5ZUOK-DIveV17Q.js +0 -1
  434. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-DIveV17Q.js +0 -1
  435. ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +0 -321
  436. ksadk/server/static/assets/dagre-BM42HDAG-D0QXg_MQ.js +0 -4
  437. ksadk/server/static/assets/dagre-Bx709z4p.js +0 -1
  438. ksadk/server/static/assets/diagram-2AECGRRQ-DBDDKwBQ.js +0 -43
  439. ksadk/server/static/assets/diagram-5GNKFQAL-BkwDOxmO.js +0 -10
  440. ksadk/server/static/assets/diagram-KO2AKTUF-Bv8waYvY.js +0 -3
  441. ksadk/server/static/assets/diagram-LMA3HP47-e1VECDdu.js +0 -24
  442. ksadk/server/static/assets/diagram-OG6HWLK6-Ds9adfRl.js +0 -24
  443. ksadk/server/static/assets/dist-B5J2uhNk.js +0 -1
  444. ksadk/server/static/assets/erDiagram-TEJ5UH35-BPItFKH6.js +0 -85
  445. ksadk/server/static/assets/eventmodeling-FCH6USID-DYFSxbqr.js +0 -1
  446. ksadk/server/static/assets/flowDiagram-I6XJVG4X-BbYB9w5Y.js +0 -162
  447. ksadk/server/static/assets/ganttDiagram-6RSMTGT7-TQXa0KXv.js +0 -292
  448. ksadk/server/static/assets/gitGraph-WXDBUCRP-DyNYp6BN.js +0 -1
  449. ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-bKblCnpa.js +0 -106
  450. ksadk/server/static/assets/graphlib-B8gBHxth.js +0 -1
  451. ksadk/server/static/assets/index-R52pJJvg.js +0 -177
  452. ksadk/server/static/assets/index-jJr3_955.css +0 -2
  453. ksadk/server/static/assets/info-J43DQDTF-WUx7qTde.js +0 -1
  454. ksadk/server/static/assets/infoDiagram-5YYISTIA-BGVT7J_i.js +0 -2
  455. ksadk/server/static/assets/katex-CYWseY5E.js +0 -265
  456. ksadk/server/static/assets/mermaid-parser.core-BWrDjSZM.js +0 -161
  457. ksadk/server/static/assets/packet-YPE3B663-BN13y_xl.js +0 -1
  458. ksadk/server/static/assets/pie-LRSECV5Y-ftSLXhhc.js +0 -1
  459. ksadk/server/static/assets/pieDiagram-4H26LBE5-GmmqNiVH.js +0 -30
  460. ksadk/server/static/assets/radar-GUYGQ44K-jaICkIeO.js +0 -1
  461. ksadk/server/static/assets/requirementDiagram-4Y6WPE33-DSWFqL7c.js +0 -84
  462. ksadk/server/static/assets/stateDiagram-AJRCARHV-qzTQpg3Y.js +0 -1
  463. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-BYCoYGAN.js +0 -1
  464. ksadk/server/static/assets/treeView-BLDUP644-Cpib_vlz.js +0 -1
  465. ksadk/server/static/assets/treemap-LRROVOQU-DQxfmVHK.js +0 -1
  466. ksadk/server/static/assets/vennDiagram-CIIHVFJN-CMCixki2.js +0 -34
  467. ksadk/server/static/assets/wardley-L42UT6IY-BQYHnnkU.js +0 -1
  468. ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-CVzB8F9K.js +0 -78
  469. ksadk/server/static/assets/xychartDiagram-2RQKCTM6-BFBqBLc1.js +0 -7
  470. ksadk/studio/evaluation.py +0 -192
  471. ksadk/studio/static/assets/index-CaEN0eTR.css +0 -1
  472. ksadk/studio/static/assets/index-DI2eY_NH.js +0 -252
  473. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.3.dist-info}/WHEEL +0 -0
  474. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.3.dist-info}/entry_points.txt +0 -0
  475. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.3.dist-info}/licenses/LICENSE +0 -0
  476. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,164 @@
1
+ """TokenCounter 协议与启发式实现。
2
+
3
+ 对齐方案 8.9。实现顺序应是 provider 官方 tokenizer → 兼容 tokenizer → CJK+ASCII
4
+ 启发式。第一个 PR 只落地启发式实现(复用现有 ``estimate_text_tokens``),并记录所用
5
+ tokenizer 名称;只有 heuristic 可用时由调用方自行加安全系数。tiktoken/provider
6
+ tokenizer 接入留后续 PR。
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import os
12
+ from typing import Any, Protocol, Sequence
13
+
14
+ HEURISTIC_TOKENIZER_NAME = "heuristic_cjk_ascii"
15
+
16
+
17
+ class TokenCounter(Protocol):
18
+ """token 计数协议。"""
19
+
20
+ name: str
21
+
22
+ def count_text(self, text: str, *, model: str | None = None) -> int: ...
23
+
24
+ def count_messages(self, messages: Sequence[Any], *, model: str | None = None) -> int: ...
25
+
26
+
27
+ class HeuristicTokenCounter:
28
+ """复用 ``ksadk.conversations.model_context.estimate_text_tokens`` 的启发式计数器。
29
+
30
+ CJK 字符按约 1.5 token,其他按 4 chars ~= 1 token。不是真实 tokenizer,但比纯英文
31
+ 口径更接近本地中文使用体验。第一个 PR 的 shadow ContextPlan 只用它做可观测估算,
32
+ 不进任何决策路径。
33
+ """
34
+
35
+ name = HEURISTIC_TOKENIZER_NAME
36
+
37
+ def count_text(self, text: str, *, model: str | None = None) -> int:
38
+ from ksadk.conversations.model_context import estimate_text_tokens
39
+
40
+ return estimate_text_tokens(text)
41
+
42
+ def count_messages(self, messages: Sequence[Any], *, model: str | None = None) -> int:
43
+ from ksadk.conversations.model_context import estimate_text_tokens
44
+
45
+ total = 0
46
+ for message in messages:
47
+ total += self._count_message(message, estimate_text_tokens)
48
+ return total
49
+
50
+ @staticmethod
51
+ def _count_message(message: Any, estimator: Any) -> int:
52
+ if isinstance(message, str):
53
+ return estimator(message)
54
+ if isinstance(message, dict):
55
+ total = 0
56
+ for key in ("content", "text", "output"):
57
+ value = message.get(key)
58
+ if isinstance(value, str):
59
+ total += estimator(value)
60
+ elif isinstance(value, list):
61
+ for part in value:
62
+ if isinstance(part, dict):
63
+ text = part.get("text") or part.get("content")
64
+ if isinstance(text, str):
65
+ total += estimator(text)
66
+ elif isinstance(part, str):
67
+ total += estimator(part)
68
+ role = message.get("role")
69
+ if isinstance(role, str):
70
+ total += estimator(role)
71
+ return total
72
+ # LangChain/BaseMessage 风格对象:尽量取 content。
73
+ content = getattr(message, "content", None)
74
+ if isinstance(content, str):
75
+ return estimator(content)
76
+ if isinstance(content, list):
77
+ total = 0
78
+ for part in content:
79
+ if isinstance(part, dict):
80
+ text = part.get("text") or part.get("content")
81
+ if isinstance(text, str):
82
+ total += estimator(text)
83
+ elif isinstance(part, str):
84
+ total += estimator(part)
85
+ return total
86
+ return estimator(str(message))
87
+
88
+
89
+ _DEFAULT_COUNTER: HeuristicTokenCounter | None = None
90
+ _PROVIDER_COUNTER: "TokenCounter | None" = None
91
+
92
+
93
+ class _TiktokenTokenCounter:
94
+ """tiktoken 兼容 tokenizer(方案 §8.9 实现顺序 2)。
95
+
96
+ 用于 OpenAI cl100k_base/o200k 系模型;非该系模型回退到 heuristic。``name`` 记录实际
97
+ tokenizer,供 ContextPlan ``tokenizer`` 字段如实标注。
98
+ """
99
+
100
+ def __init__(self, encoding_name: str = "cl100k_base") -> None:
101
+ try:
102
+ import tiktoken # type: ignore
103
+
104
+ self._enc = tiktoken.get_encoding(encoding_name)
105
+ self._encoding_name = encoding_name
106
+ except Exception: # noqa: BLE001
107
+ self._enc = None
108
+ self._encoding_name = encoding_name
109
+
110
+ @property
111
+ def name(self) -> str:
112
+ if self._enc is None:
113
+ return HEURISTIC_TOKENIZER_NAME
114
+ return f"tiktoken:{self._encoding_name}"
115
+
116
+ def count_text(self, text: str, *, model: str | None = None) -> int:
117
+ if self._enc is None:
118
+ return HeuristicTokenCounter().count_text(text)
119
+ try:
120
+ return len(self._enc.encode(str(text or "")))
121
+ except Exception: # noqa: BLE001
122
+ return HeuristicTokenCounter().count_text(text)
123
+
124
+ def count_messages(self, messages: Sequence[Any], *, model: str | None = None) -> int:
125
+ total = 0
126
+ for message in messages:
127
+ total += HeuristicTokenCounter._count_message(message, self.count_text)
128
+ return total
129
+
130
+
131
+ def _provider_counter_enabled() -> bool:
132
+ """是否启用 provider/兼容 tokenizer(方案 §8.9)。
133
+
134
+ 默认 **关闭**(保持 heuristic baseline,不静默改变既有计数口径——方案 §8.9 "先观测后接管");
135
+ 显式 ``KSADK_TOKENIZER_PROVIDER=auto|tiktoken`` 才尝试 tiktoken,不可用时回退 heuristic。
136
+ """
137
+ raw = str(os.environ.get("KSADK_TOKENIZER_PROVIDER", "") or "").strip().lower()
138
+ return raw in ("auto", "tiktoken")
139
+
140
+
141
+ def get_default_token_counter() -> TokenCounter:
142
+ """返回进程级默认 TokenCounter(方案 §8.9)。
143
+
144
+ 优先 provider/兼容 tokenizer(``KSADK_TOKENIZER_PROVIDER=auto`` 时尝试 tiktoken,不可用
145
+ 回退 heuristic);``auto`` 之外显式 ``heuristic`` 则只用启发式。名称如实记录,偏差监控由
146
+ 调用方按 model 维度做(方案 §8.9 末)。
147
+ """
148
+ global _PROVIDER_COUNTER
149
+ if _provider_counter_enabled() and _PROVIDER_COUNTER is None:
150
+ _PROVIDER_COUNTER = _TiktokenTokenCounter()
151
+ if _PROVIDER_COUNTER is not None and _provider_counter_enabled():
152
+ return _PROVIDER_COUNTER
153
+ global _DEFAULT_COUNTER
154
+ if _DEFAULT_COUNTER is None:
155
+ _DEFAULT_COUNTER = HeuristicTokenCounter()
156
+ return _DEFAULT_COUNTER
157
+
158
+
159
+ def set_default_token_counter(counter: TokenCounter | None) -> None:
160
+ """测试/注入用:覆盖默认 counter。``None`` 恢复自动解析。"""
161
+ global _PROVIDER_COUNTER, _DEFAULT_COUNTER
162
+ _PROVIDER_COUNTER = counter
163
+ if counter is None:
164
+ _DEFAULT_COUNTER = None
@@ -11,6 +11,25 @@ def _exports(module: str, *names: str) -> dict[str, tuple[str, str]]:
11
11
 
12
12
 
13
13
  _EXPORTS = {
14
+ **_exports(
15
+ "ksadk.conversations.contracts",
16
+ "ConversationCapability",
17
+ "ConversationInput",
18
+ "ConversationAttachmentPart",
19
+ "ConversationItem",
20
+ "ConversationSurface",
21
+ "ConversationTextPart",
22
+ "validate_conversation_input",
23
+ "validate_surface_input",
24
+ ),
25
+ **_exports(
26
+ "ksadk.conversations.projector",
27
+ "project_conversation_item",
28
+ ),
29
+ **_exports(
30
+ "ksadk.conversations.reducer",
31
+ "ConversationItemReducer",
32
+ ),
14
33
  **_exports(
15
34
  "ksadk.conversations.attachments",
16
35
  "decode_inline_data",
@@ -5,7 +5,14 @@ import re
5
5
  from collections.abc import Mapping, Sequence
6
6
  from typing import Any, Dict, Iterable, List
7
7
 
8
+ from ksadk.events.canonical import ItemCompleted, parse_runtime_event
9
+ from ksadk.events.content import TextContent
8
10
  from ksadk.sessions.base import SessionEvent
11
+ from ksadk.tools.result_budget import (
12
+ ToolResultBudget,
13
+ budget_tool_output,
14
+ default_tool_result_budget,
15
+ )
9
16
 
10
17
  CANONICAL_EVENT_TYPES = {
11
18
  "user_message",
@@ -34,11 +41,23 @@ TRANSCRIPT_EVENT_TYPES = {
34
41
  "context_checkpoint",
35
42
  }
36
43
 
44
+ _RUNTIME_PLACEHOLDER_EVENT_TYPES = {
45
+ "tool_call",
46
+ "tool_result",
47
+ "approval_request",
48
+ "approval_response",
49
+ }
50
+
37
51
  DATA_URL_RE = re.compile(r"data:(?P<mime>[A-Za-z0-9.+-]+/[A-Za-z0-9.+-]+);base64,[A-Za-z0-9+/=_-]+")
38
52
  BASE64_FIELD_RE = re.compile(
39
53
  r"(?P<prefix>['\"](?P<field>file_data|data|bytes|base64)['\"]\s*:\s*['\"])(?P<value>[A-Za-z0-9+/=_-]{512,})(?P<suffix>['\"])",
40
54
  re.IGNORECASE,
41
55
  )
56
+ _CORRECTION_MARKER_RE = re.compile(
57
+ r"(?:修正|更正|改为|更新为|最新(?:的)?|废弃|作废|不再使用|不是.+而是|不要|不得|禁止)"
58
+ )
59
+ _LATEST_USER_INSTRUCTION_MAX_CHARS = 8192
60
+ _CORRECTION_SUMMARY_MAX_CHARS = 2048
42
61
 
43
62
 
44
63
  def sanitize_event_text_for_context(text: Any) -> str:
@@ -55,8 +74,7 @@ def sanitize_event_text_for_context(text: Any) -> str:
55
74
  value = DATA_URL_RE.sub(_replace_data_url, value)
56
75
  value = BASE64_FIELD_RE.sub(
57
76
  lambda match: (
58
- f"{match.group('prefix')}[base64 {match.group('field')} omitted]"
59
- f"{match.group('suffix')}"
77
+ f"{match.group('prefix')}[base64 {match.group('field')} omitted]{match.group('suffix')}"
60
78
  ),
61
79
  value,
62
80
  )
@@ -83,6 +101,60 @@ def extract_event_text(event: SessionEvent) -> str:
83
101
  )
84
102
 
85
103
 
104
+ def _canonical_completed_message(
105
+ event: SessionEvent,
106
+ ) -> tuple[str, str, tuple[str, str]] | None:
107
+ """Return one canonical assistant message without flattening its identity.
108
+
109
+ Canonical RuntimeEvents are persisted inside the existing ``SessionEvent``
110
+ carrier. Legacy transcript projection only inspects carrier-level fields,
111
+ so an ``item.completed`` message otherwise disappears from the next turn's
112
+ history. The canonical event id and ``(scope_id, item_id)`` are both kept:
113
+ replays are idempotent, while equal text from different items remains
114
+ distinct.
115
+ """
116
+
117
+ raw = (event.content or {}).get("runtime_event")
118
+ if not isinstance(raw, Mapping):
119
+ return None
120
+ try:
121
+ canonical = parse_runtime_event(dict(raw))
122
+ except (TypeError, ValueError):
123
+ return None
124
+ if (
125
+ not isinstance(canonical, ItemCompleted)
126
+ or canonical.item_kind != "message"
127
+ or event.event_type != canonical.event_type
128
+ ):
129
+ return None
130
+ text = sanitize_event_text_for_context(
131
+ "".join(
132
+ part.text for part in canonical.snapshot.parts if isinstance(part, TextContent)
133
+ )
134
+ )
135
+ if not text:
136
+ return None
137
+ return canonical.event_id, text, (canonical.scope_id, canonical.item_id)
138
+
139
+
140
+ def _transcript_event_projection(
141
+ event: SessionEvent,
142
+ ) -> tuple[str, str, tuple[str, tuple[str, str]] | None]:
143
+ canonical_message = _canonical_completed_message(event)
144
+ if canonical_message is not None:
145
+ event_id, text, item_identity = canonical_message
146
+ return "assistant_message", text, (event_id, item_identity)
147
+ return (
148
+ canonical_event_type(
149
+ event.event_type,
150
+ author=event.author,
151
+ role=str((event.content or {}).get("role") or ""),
152
+ ),
153
+ extract_event_text(event),
154
+ None,
155
+ )
156
+
157
+
86
158
  def canonical_event_type(
87
159
  event_type: str | None,
88
160
  *,
@@ -146,6 +218,70 @@ def _stringify_part_text(value: Any) -> str:
146
218
  return str(value)
147
219
 
148
220
 
221
+ def budget_tool_result_for_event(
222
+ *,
223
+ tool_name: str,
224
+ tool_output: Any,
225
+ tool_call_id: str | None,
226
+ enabled: bool,
227
+ budget: ToolResultBudget | None = None,
228
+ ) -> tuple[str, dict[str, Any]]:
229
+ """PR C:tool_result 落 SessionEvent 前的单项预算(ksadk_hosted 门控)。
230
+
231
+ 返回 ``(session_event_text, metadata_extras)``:
232
+
233
+ - ``enabled=False`` → ``(str(tool_output), {})``:与旧
234
+ ``text=str(tool_output)`` **字节级一致**,非ksadk_hosted / framework / native
235
+ 路径零行为变更。
236
+ - ``enabled=True``:先 ``_stringify_part_text`` 干净渲染(已预算的 toolset dict →
237
+ ``"preview\\n[persisted-output] path (mime)"``;裸串 → 原串),再若仍超 ``max_chars`` 则
238
+ ``budget_tool_output`` 落盘+截断,``text = "preview\\n[persisted-output] path (mime)"``,
239
+ ``extras = {"tool_result_budget": {truncated, original_chars, preview_chars, persisted}}``。
240
+ 未超阈值 → ``(rendered, {})``。
241
+
242
+ **不碰 ``metadata.tool_output``**:调用方保留原值(UI/Responses 读取方不受影响,
243
+ 会话存储节省留后续)。
244
+ 只 bound 进 ``content.parts[0].text``——即下一轮 ``extract_event_text`` → ``payload["history"]``
245
+ → 模型输入的那条 text。已预算 dict 经 ``_stringify_part_text`` 渲染后必小于阈值,不重复落盘。
246
+ """
247
+ if not enabled:
248
+ return str(tool_output), {}
249
+ active = budget or default_tool_result_budget()
250
+ rendered = _stringify_part_text(tool_output)
251
+ if len(rendered) <= active.max_chars:
252
+ return rendered, {}
253
+ budgeted = budget_tool_output(
254
+ tool_name=tool_name,
255
+ field_name="output",
256
+ value=tool_output,
257
+ metadata={"tool_call_id": tool_call_id or ""},
258
+ budget=active,
259
+ )
260
+ preview = str(budgeted.get("output") or "")
261
+ persisted = budgeted.get("persisted")
262
+ if not isinstance(persisted, Mapping) or not persisted.get("path"):
263
+ # 无落盘(不应发生,但兜底)→ 退回 rendered 截断标记,不谎报 persisted。
264
+ marker = f"\n[truncated {len(rendered) - active.max_chars} chars]"
265
+ return (rendered[: active.max_chars] + marker), {
266
+ "tool_result_budget": {
267
+ "truncated": True,
268
+ "original_chars": int(budgeted.get("original_chars") or len(rendered)),
269
+ "preview_chars": active.max_chars,
270
+ }
271
+ }
272
+ mime_type = persisted.get("mime_type") or "text/plain"
273
+ text = f"{preview}\n[persisted-output] {persisted['path']} ({mime_type})"
274
+ extras = {
275
+ "tool_result_budget": {
276
+ "truncated": bool(budgeted.get("truncated")),
277
+ "original_chars": int(budgeted.get("original_chars") or 0),
278
+ "preview_chars": int(budgeted.get("preview_chars") or len(preview)),
279
+ "persisted": dict(persisted),
280
+ }
281
+ }
282
+ return text, extras
283
+
284
+
149
285
  def build_request_history(messages: Iterable[Dict[str, Any]]) -> List[Dict[str, str]]:
150
286
  history: List[Dict[str, str]] = []
151
287
  for message in messages or []:
@@ -191,34 +327,67 @@ def summarize_event_groups(
191
327
  ) -> str:
192
328
  """把要折叠的旧轮次压成一段 checkpoint 文本。
193
329
 
194
- 这里没有直接照搬 Claude Code 的 LLM summarizer,而是先落一个可预测、
195
- 可恢复的结构化摘要骨架,后续再替换成真正的 summarize agent 也不需要改
196
- event contract。
330
+ extractive fallback:结构化骨架 + 有界保留错误修正和最新长 user 指令。
331
+ 无摘要模型时,关键修正可能位于长消息尾部,也可能不是最后一条 user 消息;
332
+ 因此跨消息提取修正,并在预算上限内保留最新长指令首尾(方案 §9.4)。
197
333
  """
198
334
  lines: List[str] = []
335
+ seen_canonical_event_ids: set[str] = set()
336
+ seen_canonical_items: set[tuple[str, str]] = set()
199
337
  if previous_summary:
200
338
  lines.append(previous_summary)
201
339
  lines.append("Earlier conversation summary:")
340
+ last_user_text = ""
341
+ correction_snippets: list[str] = []
202
342
  for group in groups:
203
343
  snippets: List[str] = []
204
344
  for event in group:
205
- event_type = canonical_event_type(
206
- event.event_type,
207
- author=event.author,
208
- role=str((event.content or {}).get("role") or ""),
209
- )
345
+ event_type, text, canonical_identity = _transcript_event_projection(event)
346
+ if canonical_identity is not None:
347
+ event_id, item_identity = canonical_identity
348
+ if (
349
+ event_id in seen_canonical_event_ids
350
+ or item_identity in seen_canonical_items
351
+ ):
352
+ continue
353
+ seen_canonical_event_ids.add(event_id)
354
+ seen_canonical_items.add(item_identity)
210
355
  if event_type not in TRANSCRIPT_EVENT_TYPES or event_type == "context_checkpoint":
211
356
  continue
212
- text = extract_event_text(event)
213
357
  if not text:
214
358
  continue
215
359
  if event_type in {"assistant_message", "tool_call"}:
216
360
  role = "assistant"
217
361
  else:
218
362
  role = "user"
363
+ last_user_text = text
364
+ for sentence in re.split(r"[\n。;;]+", text):
365
+ normalized = sentence.strip()
366
+ if normalized and _CORRECTION_MARKER_RE.search(normalized):
367
+ correction_snippets.append(normalized[:512])
219
368
  snippets.append(f"{role}: {text[:180]}")
220
369
  if snippets:
221
370
  lines.append(" | ".join(snippets))
371
+ # 修正不一定是 compact 范围内最后一条 user 消息;单独形成结构化段,供
372
+ # Working State 确定性解析。总量有界,避免用“保留完整”重新撑爆上下文。
373
+ if correction_snippets:
374
+ unique: list[str] = []
375
+ for snippet in correction_snippets:
376
+ if snippet not in unique:
377
+ unique.append(snippet)
378
+ correction_text = ";".join(unique[-8:])[-_CORRECTION_SUMMARY_MAX_CHARS:]
379
+ lines.append(f"错误修正:{correction_text}")
380
+ # 末尾追加最新 user 指令的有界首尾内容。短消息已在摘要骨架里,不重复。
381
+ if last_user_text and len(last_user_text) > 180:
382
+ preserved = last_user_text
383
+ if len(preserved) > _LATEST_USER_INSTRUCTION_MAX_CHARS:
384
+ half = _LATEST_USER_INSTRUCTION_MAX_CHARS // 2
385
+ preserved = (
386
+ preserved[:half]
387
+ + "\n...[中间内容因上下文预算省略]...\n"
388
+ + preserved[-half:]
389
+ )
390
+ lines.append(f"最新用户指令(有界保留): {preserved}")
222
391
  return "\n".join(line for line in lines if line).strip()
223
392
 
224
393
 
@@ -235,6 +404,10 @@ def project_model_messages(
235
404
  3. tool/approval/attachment 仍保留成可解释的文本占位,避免状态丢失。
236
405
  """
237
406
  projected: List[Dict[str, str]] = []
407
+ placeholder_flags: list[bool] = []
408
+ identity_boundary_flags: list[bool] = []
409
+ seen_canonical_event_ids: set[str] = set()
410
+ seen_canonical_items: set[tuple[str, str]] = set()
238
411
  compacted_until = compacted_until_seq_id(events)
239
412
  checkpoint = next(
240
413
  (
@@ -253,21 +426,27 @@ def project_model_messages(
253
426
  "content": summary_text,
254
427
  }
255
428
  )
429
+ placeholder_flags.append(False)
430
+ identity_boundary_flags.append(False)
256
431
 
257
432
  for event in events:
258
- event_type = canonical_event_type(
259
- event.event_type,
260
- author=event.author,
261
- role=str((event.content or {}).get("role") or ""),
262
- )
433
+ event_type, text, canonical_identity = _transcript_event_projection(event)
263
434
  if event.seq_id <= compacted_until and event_type != "context_checkpoint":
264
435
  continue
265
436
  if event_type not in TRANSCRIPT_EVENT_TYPES:
266
437
  continue
267
438
  if event_type in {"context_checkpoint", "compaction_boundary"}:
268
439
  continue
440
+ if canonical_identity is not None:
441
+ event_id, item_identity = canonical_identity
442
+ if (
443
+ event_id in seen_canonical_event_ids
444
+ or item_identity in seen_canonical_items
445
+ ):
446
+ continue
447
+ seen_canonical_event_ids.add(event_id)
448
+ seen_canonical_items.add(item_identity)
269
449
 
270
- text = extract_event_text(event)
271
450
  if not text:
272
451
  continue
273
452
 
@@ -291,10 +470,20 @@ def project_model_messages(
291
470
  else:
292
471
  role = "user"
293
472
 
294
- if projected and projected[-1]["role"] == role:
473
+ is_placeholder = event_type in _RUNTIME_PLACEHOLDER_EVENT_TYPES
474
+ if (
475
+ projected
476
+ and projected[-1]["role"] == role
477
+ and not placeholder_flags[-1]
478
+ and not identity_boundary_flags[-1]
479
+ and not is_placeholder
480
+ and canonical_identity is None
481
+ ):
295
482
  projected[-1]["content"] = f"{projected[-1]['content']}\n{text}".strip()
296
483
  else:
297
484
  projected.append({"role": role, "content": text})
485
+ placeholder_flags.append(is_placeholder)
486
+ identity_boundary_flags.append(canonical_identity is not None)
298
487
 
299
488
  return projected
300
489
 
@@ -430,9 +619,15 @@ def project_responses_history(events: List[SessionEvent]) -> List[dict[str, Any]
430
619
  from a text summary. Events without a reliable call id fall back to the
431
620
  existing explanatory message representation instead of emitting an invalid
432
621
  ``function_call_output`` item.
622
+
623
+ 公开承诺(契约声明见 ``ksadk/events/projections.py``):仅 OpenAI Responses
624
+ input item 形态(``type``/``call_id``/``output``/role 消息);内部不保证
625
+ compacted 前缀的重放方式与占位消息的具体措辞。
433
626
  """
434
627
  projected: List[dict[str, Any]] = []
435
628
  projected_call_ids: set[str] = set()
629
+ seen_canonical_event_ids: set[str] = set()
630
+ seen_canonical_items: set[tuple[str, str]] = set()
436
631
  compacted_until = compacted_until_seq_id(events)
437
632
  checkpoint = next(
438
633
  (
@@ -448,17 +643,22 @@ def project_responses_history(events: List[SessionEvent]) -> List[dict[str, Any]
448
643
  projected.append(summary_message)
449
644
 
450
645
  for event in events:
451
- event_type = canonical_event_type(
452
- event.event_type,
453
- author=event.author,
454
- role=str((event.content or {}).get("role") or ""),
455
- )
646
+ event_type, text, canonical_identity = _transcript_event_projection(event)
456
647
  if event.seq_id <= compacted_until and event_type != "context_checkpoint":
457
648
  continue
458
649
  if event_type not in TRANSCRIPT_EVENT_TYPES:
459
650
  continue
460
651
  if event_type in {"context_checkpoint", "compaction_boundary"}:
461
652
  continue
653
+ if canonical_identity is not None:
654
+ event_id, item_identity = canonical_identity
655
+ if (
656
+ event_id in seen_canonical_event_ids
657
+ or item_identity in seen_canonical_items
658
+ ):
659
+ continue
660
+ seen_canonical_event_ids.add(event_id)
661
+ seen_canonical_items.add(item_identity)
462
662
 
463
663
  if event_type == "tool_call":
464
664
  item = _response_tool_call_item(event)
@@ -466,25 +666,25 @@ def project_responses_history(events: List[SessionEvent]) -> List[dict[str, Any]
466
666
  projected.append(item)
467
667
  projected_call_ids.add(str(item["call_id"]))
468
668
  continue
469
- message = _responses_message("assistant", f"[tool_call] {extract_event_text(event)}")
669
+ message = _responses_message("assistant", f"[tool_call] {text}")
470
670
  elif event_type == "tool_result":
471
671
  item = _response_tool_output_item(event)
472
672
  if item and str(item["call_id"]) in projected_call_ids:
473
673
  projected.append(item)
474
674
  continue
475
- message = _responses_message("user", f"[tool_result] {extract_event_text(event)}")
675
+ message = _responses_message("user", f"[tool_result] {text}")
476
676
  elif event_type == "assistant_message":
477
- message = _responses_message("assistant", extract_event_text(event))
677
+ message = _responses_message("assistant", text)
478
678
  elif event_type == "approval_request":
479
679
  message = _responses_message(
480
- "assistant", f"[approval_request] {extract_event_text(event)}"
680
+ "assistant", f"[approval_request] {text}"
481
681
  )
482
682
  elif event_type == "approval_response":
483
- message = _responses_message("user", f"[approval_response] {extract_event_text(event)}")
683
+ message = _responses_message("user", f"[approval_response] {text}")
484
684
  elif event_type == "attachment_ref":
485
- message = _responses_message("user", f"[attachment] {extract_event_text(event)}")
685
+ message = _responses_message("user", f"[attachment] {text}")
486
686
  else:
487
- message = _responses_message("user", extract_event_text(event))
687
+ message = _responses_message("user", text)
488
688
  if message:
489
689
  projected.append(message)
490
690