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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (411) hide show
  1. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/METADATA +22 -2
  2. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/RECORD +356 -222
  3. ksadk/a2a/_space_client_events.py +381 -0
  4. ksadk/a2a/event_adapter.py +156 -64
  5. ksadk/a2a/executor.py +179 -63
  6. ksadk/a2a/space_client.py +6 -333
  7. ksadk/a2ui/core.py +222 -53
  8. ksadk/agui/_agent_helpers.py +243 -0
  9. ksadk/agui/a2ui_projection.py +12 -5
  10. ksadk/agui/agent.py +443 -259
  11. ksadk/api/client.py +525 -27
  12. ksadk/builders/code_builder.py +284 -8
  13. ksadk/builders/container_builder.py +8 -1
  14. ksadk/builders/managed_runtime_builder.py +39 -35
  15. ksadk/cli/__init__.py +17 -0
  16. ksadk/cli/cmd_dashboard.py +15 -1
  17. ksadk/cli/cmd_deploy.py +10 -2
  18. ksadk/cli/cmd_eval.py +66 -9
  19. ksadk/cli/cmd_evalset.py +284 -0
  20. ksadk/cli/cmd_files.py +5 -1
  21. ksadk/cli/cmd_hermes.py +1 -126
  22. ksadk/cli/cmd_invoke.py +28 -38
  23. ksadk/cli/cmd_managed_runtime.py +95 -0
  24. ksadk/cli/cmd_observe.py +85 -0
  25. ksadk/cli/cmd_openclaw.py +1 -435
  26. ksadk/cli/cmd_replay.py +28 -4
  27. ksadk/cli/cmd_studio.py +99 -8
  28. ksadk/cli/cmd_web.py +10 -2
  29. ksadk/cli/hermes_env.py +151 -0
  30. ksadk/cli/invoke_payload.py +49 -0
  31. ksadk/cli/openclaw_env.py +458 -0
  32. ksadk/codex/client.py +180 -30
  33. ksadk/codex/runtime.py +428 -326
  34. ksadk/configs/env_registry.py +66 -12
  35. ksadk/configs/env_registry_pcm.py +181 -0
  36. ksadk/configs/env_var_spec.py +18 -0
  37. ksadk/configs/global_config.py +5 -0
  38. ksadk/context_engine/__init__.py +98 -0
  39. ksadk/context_engine/assembler.py +176 -0
  40. ksadk/context_engine/baseline.py +419 -0
  41. ksadk/context_engine/cache_observability.py +228 -0
  42. ksadk/context_engine/capabilities.py +443 -0
  43. ksadk/context_engine/contributors.py +344 -0
  44. ksadk/context_engine/hosted_pipeline.py +417 -0
  45. ksadk/context_engine/models.py +117 -0
  46. ksadk/context_engine/planner.py +698 -0
  47. ksadk/context_engine/policies.py +319 -0
  48. ksadk/context_engine/projection.py +31 -0
  49. ksadk/context_engine/shadow_plan.py +266 -0
  50. ksadk/context_engine/tokenizer.py +164 -0
  51. ksadk/conversations/context.py +126 -6
  52. ksadk/conversations/message_projection.py +292 -28
  53. ksadk/conversations/model_context.py +49 -3
  54. ksadk/conversations/runtime_compaction.py +207 -5
  55. ksadk/conversations/runtime_input.py +184 -2
  56. ksadk/conversations/runtime_invocation.py +81 -3
  57. ksadk/conversations/runtime_observability.py +141 -0
  58. ksadk/conversations/runtime_payloads.py +42 -0
  59. ksadk/conversations/runtime_preparation.py +272 -2
  60. ksadk/conversations/runtime_resume.py +20 -1
  61. ksadk/conversations/runtime_stream_events.py +94 -9
  62. ksadk/conversations/semantic_summary.py +311 -1
  63. ksadk/conversations/session_lock.py +51 -0
  64. ksadk/deployment/env_forward.py +72 -0
  65. ksadk/deployment/managed_runtime.py +5 -0
  66. ksadk/deployment/providers/serverless.py +143 -69
  67. ksadk/evaluation/__init__.py +32 -0
  68. ksadk/evaluation/a2a_adapter.py +9 -1
  69. ksadk/evaluation/adapters.py +18 -5
  70. ksadk/evaluation/agent_eval_client.py +395 -0
  71. ksadk/evaluation/cloud_binding.py +80 -0
  72. ksadk/evaluation/cloud_converter.py +194 -0
  73. ksadk/evaluation/cloud_service.py +199 -0
  74. ksadk/evaluation/contracts.py +73 -4
  75. ksadk/evaluation/evaluators.py +183 -17
  76. ksadk/evaluation/evidence.py +258 -0
  77. ksadk/evaluation/executor.py +31 -6
  78. ksadk/evaluation/local_adapter.py +550 -0
  79. ksadk/evaluation/service_env.py +27 -0
  80. ksadk/evaluation/studio_build_adapter.py +253 -0
  81. ksadk/evaluation/target.py +15 -6
  82. ksadk/events/__init__.py +13 -13
  83. ksadk/events/_v1_compat/__init__.py +1 -0
  84. ksadk/events/_v1_compat/models.py +299 -0
  85. ksadk/events/_v1_compat/parser.py +247 -0
  86. ksadk/events/_v1_compat/projection.py +753 -0
  87. ksadk/events/adapters/__init__.py +34 -0
  88. ksadk/events/adapters/_a2a_snapshot.py +622 -0
  89. ksadk/events/adapters/_a2a_support.py +275 -0
  90. ksadk/events/adapters/_codex_interactions.py +375 -0
  91. ksadk/events/adapters/_codex_items.py +801 -0
  92. ksadk/events/adapters/_codex_validators.py +311 -0
  93. ksadk/events/adapters/_langgraph_support.py +785 -0
  94. ksadk/events/adapters/a2a.py +841 -0
  95. ksadk/events/adapters/adk.py +503 -0
  96. ksadk/events/adapters/codex.py +717 -0
  97. ksadk/events/adapters/langgraph.py +745 -0
  98. ksadk/events/canonical.py +440 -0
  99. ksadk/events/canonical_replay.py +497 -0
  100. ksadk/events/canonical_store.py +468 -0
  101. ksadk/events/cold_recovery.py +257 -0
  102. ksadk/events/content.py +89 -0
  103. ksadk/events/identity.py +86 -0
  104. ksadk/events/pipeline.py +455 -0
  105. ksadk/events/projections.py +66 -0
  106. ksadk/events/reducer.py +661 -0
  107. ksadk/events/replay.py +3 -36
  108. ksadk/events/runtime_event.py +20 -265
  109. ksadk/events/session_event.py +324 -0
  110. ksadk/events/store.py +3 -316
  111. ksadk/events/v1_compat.py +41 -0
  112. ksadk/harness/runtime.py +160 -57
  113. ksadk/identity/resolver.py +4 -3
  114. ksadk/interaction/__init__.py +28 -0
  115. ksadk/interaction/contracts.py +136 -0
  116. ksadk/interaction/ledger.py +189 -0
  117. ksadk/interaction/provider.py +122 -0
  118. ksadk/interaction/providers/__init__.py +52 -0
  119. ksadk/interaction/providers/adk.py +40 -0
  120. ksadk/interaction/providers/codex.py +94 -0
  121. ksadk/interaction/providers/langgraph.py +58 -0
  122. ksadk/kernel/__init__.py +117 -0
  123. ksadk/kernel/authorization.py +258 -0
  124. ksadk/kernel/bootstrap.py +1074 -0
  125. ksadk/kernel/contract_fingerprints.py +61 -0
  126. ksadk/kernel/contracts.py +362 -0
  127. ksadk/kernel/control.py +219 -0
  128. ksadk/kernel/errors.py +79 -0
  129. ksadk/kernel/ingress.py +1102 -0
  130. ksadk/kernel/mapping.py +111 -0
  131. ksadk/kernel/memory_store.py +1133 -0
  132. ksadk/kernel/postgres_store.py +1756 -0
  133. ksadk/kernel/recovery.py +452 -0
  134. ksadk/kernel/runtime_identity.py +103 -0
  135. ksadk/kernel/sql/001_agent_kernel.sql +120 -0
  136. ksadk/kernel/sqlite_store.py +1410 -0
  137. ksadk/kernel/state.py +145 -0
  138. ksadk/kernel/store.py +232 -0
  139. ksadk/kernel/worker.py +953 -0
  140. ksadk/knowledge_base/client.py +7 -0
  141. ksadk/knowledge_base/service.py +32 -5
  142. ksadk/memory/__init__.py +61 -0
  143. ksadk/memory/adk/backends/base_ltm_backend.py +96 -1
  144. ksadk/memory/adk/backends/http_ltm_backend.py +14 -5
  145. ksadk/memory/adk/backends/inmemory_ltm_backend.py +136 -3
  146. ksadk/memory/adk/backends/sdk_ltm_backend.py +384 -2
  147. ksadk/memory/adk/backends/sqlite_ltm_backend.py +95 -0
  148. ksadk/memory/coordinator.py +461 -0
  149. ksadk/memory/events.py +202 -0
  150. ksadk/memory/extraction.py +228 -0
  151. ksadk/memory/ltm_backend_factory.py +16 -3
  152. ksadk/memory/models.py +316 -0
  153. ksadk/memory/policy.py +215 -0
  154. ksadk/memory/provider.py +47 -0
  155. ksadk/memory/provider_adapter.py +159 -0
  156. ksadk/memory/provider_resolver.py +71 -0
  157. ksadk/memory/providers/__init__.py +5 -0
  158. ksadk/memory/providers/local_sqlite.py +490 -0
  159. ksadk/memory/resolved_policy.py +145 -0
  160. ksadk/memory/service.py +188 -6
  161. ksadk/model_proxy/bootstrap.py +16 -9
  162. ksadk/model_proxy/detect.py +202 -9
  163. ksadk/model_proxy/namespace.py +39 -15
  164. ksadk/model_proxy/server.py +24 -5
  165. ksadk/model_proxy/transform.py +60 -2
  166. ksadk/observability/__init__.py +25 -0
  167. ksadk/observability/session_log.py +420 -0
  168. ksadk/observability/trajectory.py +105 -0
  169. ksadk/prompts/__init__.py +71 -0
  170. ksadk/prompts/compiler.py +215 -0
  171. ksadk/prompts/models.py +82 -0
  172. ksadk/prompts/projection.py +89 -0
  173. ksadk/prompts/resolved.py +137 -0
  174. ksadk/prompts/sources.py +233 -0
  175. ksadk/runners/_langgraph_runner_streams.py +822 -0
  176. ksadk/runners/adk_runner.py +5 -5
  177. ksadk/runners/base_runner.py +16 -0
  178. ksadk/runners/factory.py +1 -2
  179. ksadk/runners/langgraph_runner.py +198 -437
  180. ksadk/runtime/_runner_adapter/__init__.py +1 -0
  181. ksadk/runtime/_runner_adapter/stream_mapping.py +788 -0
  182. ksadk/runtime/adapter.py +118 -2
  183. ksadk/runtime/conversation_execution.py +316 -182
  184. ksadk/runtime/executor.py +144 -3
  185. ksadk/runtime/factory.py +193 -7
  186. ksadk/runtime/hosted_finalizer.py +268 -0
  187. ksadk/runtime/launch.py +14 -1
  188. ksadk/runtime/preprocessing.py +25 -3
  189. ksadk/runtime/runner_adapter.py +288 -508
  190. ksadk/runtime/usage.py +33 -0
  191. ksadk/sandbox/backends/e2b.py +35 -2
  192. ksadk/server/composition.py +8 -0
  193. ksadk/server/factory.py +69 -6
  194. ksadk/server/routes/kernel_ingress.py +258 -0
  195. ksadk/server/routes/openai_compat.py +88 -8
  196. ksadk/server/routes/projection.py +35 -20
  197. ksadk/server/routes/run.py +94 -0
  198. ksadk/server/routes/sessions.py +2 -0
  199. ksadk/server/routes/streaming.py +5 -2
  200. ksadk/server/static/assets/{ArtifactsPanel-CakCqxEN.js → ArtifactsPanel-DSU1sWD_.js} +1 -1
  201. ksadk/server/static/assets/{CodeBlock-CynLknyO.js → CodeBlock-DOH6MVcn.js} +4 -4
  202. ksadk/server/static/assets/{MathMessageMarkdown-3sQdOysS.js → MathMessageMarkdown-BL8my1R2.js} +4 -4
  203. ksadk/server/static/assets/MathMessageMarkdown-BorAY7qD.css +1 -0
  204. ksadk/server/static/assets/MermaidBlock-Dz4IP-Tx.js +312 -0
  205. ksadk/server/static/assets/NativeTerminalPanel-DUrK0JpZ.js +1 -0
  206. ksadk/server/static/assets/abnfDiagram-VCTEODGH-g20pFzNV.js +1 -0
  207. ksadk/server/static/assets/arc-C4FzinUA.js +1 -0
  208. ksadk/server/static/assets/architecture-7GRP2DOG-DjKratdf.js +1 -0
  209. ksadk/server/static/assets/architectureDiagram-5GKGNRK7-DpIqL5h4.js +36 -0
  210. ksadk/server/static/assets/blockDiagram-NRAW4CY4-BjkljTNz.js +129 -0
  211. ksadk/server/static/assets/c4Diagram-UCG6FXSJ-Dk_ieq2X.js +38 -0
  212. ksadk/server/static/assets/channel-4cQHKtx1.js +1 -0
  213. ksadk/server/static/assets/chunk-2Q5K7J3B-DmgWkESh.js +1 -0
  214. ksadk/server/static/assets/{chunk-FMBD7UC4-B_fH2YuH.js → chunk-5VM5RSS4-BVHAchFb.js} +1 -1
  215. ksadk/server/static/assets/{chunk-ND2GUHAM-D6rFLaVV.js → chunk-F27PBJKO-C-ipQzuS.js} +1 -1
  216. ksadk/server/static/assets/chunk-G27WJ6UU-C2EyJbIK.js +231 -0
  217. ksadk/server/static/assets/{chunk-4BX2VUAB-B9drTgOW.js → chunk-JWPE2WC7-vYvVJb_M.js} +1 -1
  218. ksadk/server/static/assets/chunk-LCL6LL3I-BcI6ysPT.js +206 -0
  219. ksadk/server/static/assets/chunk-POPQ4Y6H-C030x_Z1.js +1 -0
  220. ksadk/server/static/assets/chunk-RHFEMEQ7-yyGpsz4n.js +168 -0
  221. ksadk/server/static/assets/chunk-SVP7TREG-BLTlmMU7.js +88 -0
  222. ksadk/server/static/assets/chunk-XXDRQBXY-C_32ArgP.js +1 -0
  223. ksadk/server/static/assets/classDiagram-DTDB5LWJ-DLFd9Xi0.js +1 -0
  224. ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-DLFd9Xi0.js +1 -0
  225. ksadk/server/static/assets/{cose-bilkent-S5V4N54A-ChNs_Mdk.js → cose-bilkent-JH36ORCC-9YQYwdl0.js} +1 -1
  226. ksadk/server/static/assets/cynefin-OW5HDTMX-DjEM48Qp.js +1 -0
  227. ksadk/server/static/assets/cynefinDiagram-5FMLGOSQ-ErLF5N13.js +62 -0
  228. ksadk/server/static/assets/cytoscape.esm-CyCl8rPi.js +321 -0
  229. ksadk/server/static/assets/dagre-3AP2YEHR-DFiQF-6f.js +4 -0
  230. ksadk/server/static/assets/dagre-BuhGnRI1.js +1 -0
  231. ksadk/server/static/assets/diagram-S7CK7UJ4-Bt1v8-GC.js +30 -0
  232. ksadk/server/static/assets/diagram-UQ7AKVKN-DSCxJdBK.js +41 -0
  233. ksadk/server/static/assets/diagram-VSXAHHWV-DHfYwp_9.js +3 -0
  234. ksadk/server/static/assets/diagram-VX7I27RA-DdzD-4Le.js +24 -0
  235. ksadk/server/static/assets/diagram-Z3DM3KII-Du-nAJ9F.js +24 -0
  236. ksadk/server/static/assets/{dist-Dli7qW-B.js → dist-8hqcx0sU.js} +1 -1
  237. ksadk/server/static/assets/dist-B7seoj3d.js +1 -0
  238. ksadk/server/static/assets/{dist-C1sz5Eo3.js → dist-BE7bu-DL.js} +1 -1
  239. ksadk/server/static/assets/{dist-fhOuWds5.js → dist-BVDffZ0U.js} +1 -1
  240. ksadk/server/static/assets/{dist-BjOdlDjj.js → dist-BX8z72IC.js} +1 -1
  241. ksadk/server/static/assets/{dist-DsHPdk3W.js → dist-Bf2M8m3N.js} +1 -1
  242. ksadk/server/static/assets/{dist-o2OVmKvt.js → dist-BjU6y-A2.js} +1 -1
  243. ksadk/server/static/assets/{dist-DWKByxWV.js → dist-BytlxIDT.js} +1 -1
  244. ksadk/server/static/assets/{dist-DJN_6Y39.js → dist-CSh_DE14.js} +1 -1
  245. ksadk/server/static/assets/{dist-DQU9EWw-.js → dist-CXYYBw3_.js} +1 -1
  246. ksadk/server/static/assets/{dist-4N2NMVYg.js → dist-C_wsv-Qd.js} +1 -1
  247. ksadk/server/static/assets/{dist-gLnrGsoU.js → dist-ClxGviKw.js} +1 -1
  248. ksadk/server/static/assets/{dist-j6Dxmf_j.js → dist-CttYUqzS.js} +1 -1
  249. ksadk/server/static/assets/{dist-D6MpCZtz.js → dist-DNp6rLRA.js} +1 -1
  250. ksadk/server/static/assets/{dist-BB3-OwTQ.js → dist-DUX-id_F.js} +1 -1
  251. ksadk/server/static/assets/{dist-_pHPTyZ-.js → dist-DmldrHd_.js} +1 -1
  252. ksadk/server/static/assets/{dist-CWV4lDr1.js → dist-DnfXs8Vn.js} +2 -2
  253. ksadk/server/static/assets/{dist-B5oS1zcj.js → dist-Dq9gLD7L.js} +1 -1
  254. ksadk/server/static/assets/{dist-B-jNR6Vn.js → dist-W-TIVntx.js} +1 -1
  255. ksadk/server/static/assets/{dist-BIlKoW0k.js → dist-dDdADKFA.js} +1 -1
  256. ksadk/server/static/assets/{dist-X4TnqwOK.js → dist-dydu68Fl.js} +1 -1
  257. ksadk/server/static/assets/{dist-CmZHzlwZ.js → dist-ngtN0DJB.js} +1 -1
  258. ksadk/server/static/assets/ebnfDiagram-PWID7BFC-Da9C9NO1.js +1 -0
  259. ksadk/server/static/assets/erDiagram-SSCWMZ5O-EAGqqR79.js +99 -0
  260. ksadk/server/static/assets/eventmodeling-NTZA5JFV-CMgMaJrC.js +1 -0
  261. ksadk/server/static/assets/flowDiagram-A5DVABFB-BBKL0x3P.js +1 -0
  262. ksadk/server/static/assets/ganttDiagram-EL5Y4UJY-DqJsKb59.js +292 -0
  263. ksadk/server/static/assets/gitGraph-4MIJSDKK-Dw63mrhw.js +1 -0
  264. ksadk/server/static/assets/gitGraphDiagram-WWUBYQGX-B6g4dtDi.js +106 -0
  265. ksadk/server/static/assets/index-8ipRcQ-M.js +240 -0
  266. ksadk/server/static/assets/index-ByFCqjlh.css +2 -0
  267. ksadk/server/static/assets/info-A6RAGUB7-B6iAblII.js +1 -0
  268. ksadk/server/static/assets/infoDiagram-RXCK75RN-Djm_nbd5.js +2 -0
  269. ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-K0OOfVna.js → ishikawaDiagram-5VMMS53U-DWya9SG2.js} +6 -6
  270. ksadk/server/static/assets/{journeyDiagram-JHISSGLW-8LeOJaZp.js → journeyDiagram-EYS64GPL-DutxhSNI.js} +6 -6
  271. ksadk/server/static/assets/{kanban-definition-UN3LZRKU-C6uCgZrW.js → kanban-definition-3QL26DDD-DJdOF8Fm.js} +10 -10
  272. ksadk/server/static/assets/katex-vFWytM5c.js +257 -0
  273. ksadk/server/static/assets/{linear-hdBcYzM8.js → linear-BHjG8b-J.js} +1 -1
  274. ksadk/server/static/assets/mermaid-parser.core-KGSy4jWT.js +166 -0
  275. ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-Bjy_2v4_.js → mindmap-definition-FBJOCRG2-9-HC88D4.js} +29 -29
  276. ksadk/server/static/assets/packet-AYTQ26CC-BOYAipuq.js +1 -0
  277. ksadk/server/static/assets/pegDiagram-XKGWAZYB-DrEZd4fD.js +1 -0
  278. ksadk/server/static/assets/pie-WAS4IAKB-Ctq0Kbku.js +1 -0
  279. ksadk/server/static/assets/pieDiagram-E7YTZNPT-DpjqOFFp.js +39 -0
  280. ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-BmkRbwCg.js → quadrantDiagram-AXDQQJYC-BiNhgOXX.js} +6 -6
  281. ksadk/server/static/assets/radar-RG4KPBEZ-DN0-ul_0.js +1 -0
  282. ksadk/server/static/assets/railroad-74A4TZTK-C6cC_to0.js +1 -0
  283. ksadk/server/static/assets/railroad-abnf-HS5TGJTU-BimjIuRc.js +1 -0
  284. ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-CvY65ABq.js +1 -0
  285. ksadk/server/static/assets/railroad-peg-WCYAUIDC-D72Iq_Tv.js +1 -0
  286. ksadk/server/static/assets/railroadDiagram-O6MQD6OU-_Y3Ojg6G.js +1 -0
  287. ksadk/server/static/assets/requirementDiagram-EFPCY7ZU-DeCFdg9K.js +84 -0
  288. ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-l6iXmU65.js → sankeyDiagram-P5KCCOFB-CPH75rhw.js} +7 -7
  289. ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-Cq6UBeQB.js → sequenceDiagram-WJ2MYXX4-OEpQQdr4.js} +10 -10
  290. ksadk/server/static/assets/sizeCapture-X5ZJPWSS-heUErzhI.js +1 -0
  291. ksadk/server/static/assets/stateDiagram-HBIQ2CUA-DvA3jSMB.js +1 -0
  292. ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BgQY03nz.js +1 -0
  293. ksadk/server/static/assets/swimlanes-XN3QIQJK-CR-dfN_t.js +1 -0
  294. ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-DLVw3q5Q.js +8 -0
  295. ksadk/server/static/assets/{timeline-definition-PNZ67QCA-DEz24vV0.js → timeline-definition-24CTP7MA-DAVJf1TX.js} +7 -7
  296. ksadk/server/static/assets/treeView-Q6P3EWNA-BMpO0wsB.js +1 -0
  297. ksadk/server/static/assets/treemap-WGGIJYW6-Et0aqzU3.js +1 -0
  298. ksadk/server/static/assets/vennDiagram-4TSXK5OY-DwM8eDkh.js +34 -0
  299. ksadk/server/static/assets/wardley-WFR3VGLG-CbY0mhpl.js +1 -0
  300. ksadk/server/static/assets/wardleyDiagram-VM6X3IG4-DZL-Zz2t.js +78 -0
  301. ksadk/server/static/assets/xychartDiagram-S5SC5T6Z-Dy6Yh_hu.js +7 -0
  302. ksadk/server/static/index.html +2 -2
  303. ksadk/sessions/__init__.py +26 -9
  304. ksadk/sessions/_local_service_sync.py +688 -0
  305. ksadk/sessions/_local_tables.py +21 -0
  306. ksadk/sessions/_postgres_schema.py +305 -0
  307. ksadk/sessions/_postgres_tables.py +15 -0
  308. ksadk/sessions/base.py +80 -1
  309. ksadk/sessions/in_memory.py +53 -2
  310. ksadk/sessions/local_service.py +63 -604
  311. ksadk/sessions/postgres_service.py +58 -148
  312. ksadk/sessions/resilient.py +35 -1
  313. ksadk/studio/api.py +577 -25
  314. ksadk/studio/api_contracts.py +114 -5
  315. ksadk/studio/api_memory_routes.py +82 -0
  316. ksadk/studio/authoring.py +252 -27
  317. ksadk/studio/authoring_coordinator.py +799 -67
  318. ksadk/studio/builder.py +88 -11
  319. ksadk/studio/capabilities.py +17 -0
  320. ksadk/studio/cloud.py +1916 -95
  321. ksadk/studio/codex_agent_service.py +125 -14
  322. ksadk/studio/codex_authoring.py +469 -0
  323. ksadk/studio/codex_builder.py +138 -25
  324. ksadk/studio/codex_manifest.py +51 -22
  325. ksadk/studio/codex_run.py +46 -3
  326. ksadk/studio/compiler.py +1 -0
  327. ksadk/studio/contracts.py +114 -3
  328. ksadk/studio/event_store.py +25 -95
  329. ksadk/studio/framework_run.py +231 -3
  330. ksadk/studio/hosted_kernel.py +317 -0
  331. ksadk/studio/manifest_resolver.py +131 -0
  332. ksadk/studio/model_client.py +95 -9
  333. ksadk/studio/model_profile_service.py +1 -1
  334. ksadk/studio/operations.py +37 -5
  335. ksadk/studio/otel_trace.py +348 -42
  336. ksadk/studio/pcm_memory.py +85 -0
  337. ksadk/studio/repository.py +23 -2
  338. ksadk/studio/resource_catalog.py +43 -0
  339. ksadk/studio/run_service.py +563 -137
  340. ksadk/studio/runtime_source.py +42 -16
  341. ksadk/studio/service.py +1180 -72
  342. ksadk/studio/shared_web.py +32 -22
  343. ksadk/studio/static/assets/index-BLpcdrgW.css +1 -0
  344. ksadk/studio/static/assets/index-CYekR2Xv.js +260 -0
  345. ksadk/studio/static/index.html +2 -2
  346. ksadk/studio/templates.py +100 -8
  347. ksadk/studio/validator.py +19 -0
  348. ksadk/studio/workspace.py +30 -32
  349. ksadk/tracing/setup.py +304 -3
  350. ksadk/version.py +1 -1
  351. ksadk_runtime_common/schemas/runtime_event_v1.json +37 -0
  352. ksadk_runtime_common/schemas/runtime_event_v2.json +271 -0
  353. ksadk/events/parser.py +0 -191
  354. ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +0 -1
  355. ksadk/server/static/assets/MermaidBlock-NERD_ef7.js +0 -303
  356. ksadk/server/static/assets/NativeTerminalPanel-CiHWf82q.js +0 -1
  357. ksadk/server/static/assets/arc-DrgjcKhl.js +0 -1
  358. ksadk/server/static/assets/architecture-7EHR7CIX-CpZLRCcY.js +0 -1
  359. ksadk/server/static/assets/architectureDiagram-3BPJPVTR-BXoVRbQ6.js +0 -36
  360. ksadk/server/static/assets/blockDiagram-GPEHLZMM-BnaB3Ywv.js +0 -132
  361. ksadk/server/static/assets/c4Diagram-AAUBKEIU-Dw4weZcp.js +0 -10
  362. ksadk/server/static/assets/channel-Dt3rQTFI.js +0 -1
  363. ksadk/server/static/assets/chunk-2J33WTMH-ChjpLK7j.js +0 -1
  364. ksadk/server/static/assets/chunk-55IACEB6-CK4ROATy.js +0 -1
  365. ksadk/server/static/assets/chunk-727SXJPM-B9KtTTgw.js +0 -206
  366. ksadk/server/static/assets/chunk-AQP2D5EJ-1O1mki3m.js +0 -231
  367. ksadk/server/static/assets/chunk-QZHKN3VN-iXEZtKQc.js +0 -1
  368. ksadk/server/static/assets/classDiagram-4FO5ZUOK-DIveV17Q.js +0 -1
  369. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-DIveV17Q.js +0 -1
  370. ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +0 -321
  371. ksadk/server/static/assets/dagre-BM42HDAG-D0QXg_MQ.js +0 -4
  372. ksadk/server/static/assets/dagre-Bx709z4p.js +0 -1
  373. ksadk/server/static/assets/diagram-2AECGRRQ-DBDDKwBQ.js +0 -43
  374. ksadk/server/static/assets/diagram-5GNKFQAL-BkwDOxmO.js +0 -10
  375. ksadk/server/static/assets/diagram-KO2AKTUF-Bv8waYvY.js +0 -3
  376. ksadk/server/static/assets/diagram-LMA3HP47-e1VECDdu.js +0 -24
  377. ksadk/server/static/assets/diagram-OG6HWLK6-Ds9adfRl.js +0 -24
  378. ksadk/server/static/assets/dist-B5J2uhNk.js +0 -1
  379. ksadk/server/static/assets/erDiagram-TEJ5UH35-BPItFKH6.js +0 -85
  380. ksadk/server/static/assets/eventmodeling-FCH6USID-DYFSxbqr.js +0 -1
  381. ksadk/server/static/assets/flowDiagram-I6XJVG4X-BbYB9w5Y.js +0 -162
  382. ksadk/server/static/assets/ganttDiagram-6RSMTGT7-TQXa0KXv.js +0 -292
  383. ksadk/server/static/assets/gitGraph-WXDBUCRP-DyNYp6BN.js +0 -1
  384. ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-bKblCnpa.js +0 -106
  385. ksadk/server/static/assets/graphlib-B8gBHxth.js +0 -1
  386. ksadk/server/static/assets/index-R52pJJvg.js +0 -177
  387. ksadk/server/static/assets/index-jJr3_955.css +0 -2
  388. ksadk/server/static/assets/info-J43DQDTF-WUx7qTde.js +0 -1
  389. ksadk/server/static/assets/infoDiagram-5YYISTIA-BGVT7J_i.js +0 -2
  390. ksadk/server/static/assets/katex-CYWseY5E.js +0 -265
  391. ksadk/server/static/assets/mermaid-parser.core-BWrDjSZM.js +0 -161
  392. ksadk/server/static/assets/packet-YPE3B663-BN13y_xl.js +0 -1
  393. ksadk/server/static/assets/pie-LRSECV5Y-ftSLXhhc.js +0 -1
  394. ksadk/server/static/assets/pieDiagram-4H26LBE5-GmmqNiVH.js +0 -30
  395. ksadk/server/static/assets/radar-GUYGQ44K-jaICkIeO.js +0 -1
  396. ksadk/server/static/assets/requirementDiagram-4Y6WPE33-DSWFqL7c.js +0 -84
  397. ksadk/server/static/assets/stateDiagram-AJRCARHV-qzTQpg3Y.js +0 -1
  398. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-BYCoYGAN.js +0 -1
  399. ksadk/server/static/assets/treeView-BLDUP644-Cpib_vlz.js +0 -1
  400. ksadk/server/static/assets/treemap-LRROVOQU-DQxfmVHK.js +0 -1
  401. ksadk/server/static/assets/vennDiagram-CIIHVFJN-CMCixki2.js +0 -34
  402. ksadk/server/static/assets/wardley-L42UT6IY-BQYHnnkU.js +0 -1
  403. ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-CVzB8F9K.js +0 -78
  404. ksadk/server/static/assets/xychartDiagram-2RQKCTM6-BFBqBLc1.js +0 -7
  405. ksadk/studio/evaluation.py +0 -192
  406. ksadk/studio/static/assets/index-CaEN0eTR.css +0 -1
  407. ksadk/studio/static/assets/index-DI2eY_NH.js +0 -252
  408. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/WHEEL +0 -0
  409. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/entry_points.txt +0 -0
  410. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/licenses/LICENSE +0 -0
  411. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/top_level.txt +0 -0
ksadk/memory/service.py CHANGED
@@ -8,15 +8,25 @@ import os
8
8
  from typing import Any, cast
9
9
 
10
10
  from ksadk.common.aicp_env import resolve_aicp_connection
11
- from ksadk.memory.adk.backends.base_ltm_backend import BaseLongTermMemoryBackend
11
+ from ksadk.memory.adk.backends.base_ltm_backend import (
12
+ CAP_SESSION_STATUS,
13
+ CAP_STRUCTURED_SEARCH,
14
+ BaseLongTermMemoryBackend,
15
+ )
12
16
  from ksadk.memory.ltm_backend_factory import get_long_term_memory_backend_cls
17
+ from ksadk.memory.models import (
18
+ LongTermMemoryRecord,
19
+ MemoryExtractionStatus,
20
+ MemoryMutationResult,
21
+ UnsupportedMemoryOperation,
22
+ )
13
23
 
14
24
  logger = logging.getLogger(__name__)
15
25
 
16
26
 
17
27
  def format_memory_entries(entries: list[str]) -> str:
18
28
  if not entries:
19
- return "未找到相关长期记忆。"
29
+ return "" # empty → "" not "未找到"(§10.8)
20
30
 
21
31
  formatted_entries: list[str] = []
22
32
  for index, entry in enumerate(entries, 1):
@@ -35,6 +45,11 @@ def format_memory_entries(entries: list[str]) -> str:
35
45
  return "\n\n".join(formatted_entries)
36
46
 
37
47
 
48
+ def _normalize_content(text: str) -> str:
49
+ """归一化正文,用于写后确认的可见性匹配(去空白,小写)。"""
50
+ return "".join(str(text or "").split()).lower()
51
+
52
+
38
53
  class LongTermMemoryService:
39
54
  def __init__(
40
55
  self,
@@ -126,14 +141,137 @@ class LongTermMemoryService:
126
141
  top_k=top_k if top_k is not None else self.top_k,
127
142
  )
128
143
 
144
+ def search_records(
145
+ self, *, user_id: str, query: str, top_k: int | None = None
146
+ ) -> list[LongTermMemoryRecord]:
147
+ """结构化检索(§7.5):优先走 backend.search_records,返回带服务端 ID 的记录。
148
+
149
+ 旧 backend 不支持时抛出 UnsupportedMemoryOperation,
150
+ 由上层降级为只读 search/add,不得伪造 ID。
151
+ """
152
+ return self._backend.search_records(
153
+ user_id=user_id,
154
+ query=query,
155
+ top_k=top_k if top_k is not None else self.top_k,
156
+ )
157
+
158
+ def update_memory(
159
+ self,
160
+ *,
161
+ user_id: str,
162
+ memory_id: str,
163
+ content: str,
164
+ ) -> MemoryMutationResult:
165
+ """按 ID 原地更新记忆(§7.4);不支持的 backend 抛稳定异常。"""
166
+ return self._backend.update_memory(
167
+ user_id=user_id,
168
+ memory_id=memory_id,
169
+ content=content,
170
+ )
171
+
172
+ def delete_memory(self, *, user_id: str, memory_id: str) -> MemoryMutationResult:
173
+ """按 ID 软删除记忆(§7.4);重复删除归一为 already_absent。"""
174
+ return self._backend.delete_memory(user_id=user_id, memory_id=memory_id)
175
+
176
+ def list_memory_records(
177
+ self,
178
+ *,
179
+ user_id: str,
180
+ query: str = "",
181
+ page: int = 1,
182
+ page_size: int = 20,
183
+ ) -> list[LongTermMemoryRecord]:
184
+ """ListMemories 透传:写后确认可见性 / 取当前 MemoryId。"""
185
+ return self._backend.list_memory_records(
186
+ user_id=user_id, query=query, page=page, page_size=page_size
187
+ )
188
+
189
+ def get_extraction_status(
190
+ self,
191
+ *,
192
+ user_id: str,
193
+ session_id: str,
194
+ confirm_searchable: bool = False,
195
+ expected_content: str = "",
196
+ ) -> MemoryExtractionStatus:
197
+ """写后确认(§7.7):查询 Session 后台提取状态。
198
+
199
+ Args:
200
+ confirm_searchable: True 且 State=100 时,进一步通过
201
+ ListMemories 确认目标记录可见(目标达成后 searchable=True)。
202
+ expected_content: 用于在 ListMemories 中定位目标记录的归一化正文。
203
+ """
204
+ if CAP_SESSION_STATUS not in self._backend.capabilities():
205
+ raise UnsupportedMemoryOperation(
206
+ f"{type(self._backend).__name__} does not support session status"
207
+ )
208
+ status = self._backend.get_extraction_status(user_id=user_id, session_id=session_id)
209
+ if (
210
+ confirm_searchable
211
+ and status.status == "extracted"
212
+ and CAP_STRUCTURED_SEARCH in self._backend.capabilities()
213
+ and expected_content.strip()
214
+ ):
215
+ # 用 expected_content 作为 Query 语义过滤,避免大记忆库时目标不在首页。
216
+ records = self.list_memory_records(
217
+ user_id=user_id, query=expected_content, page_size=50
218
+ )
219
+ if self._find_matching_record(records, expected_content) is not None:
220
+ status = MemoryExtractionStatus(
221
+ session_id=status.session_id,
222
+ state=status.state,
223
+ status=status.status,
224
+ searchable=True,
225
+ message=status.message,
226
+ )
227
+ return status
228
+
229
+ @staticmethod
230
+ def _find_matching_record(
231
+ records: list[LongTermMemoryRecord], expected_content: str
232
+ ) -> LongTermMemoryRecord | None:
233
+ """按归一化正文等值/包含匹配目标记录(方案 N3:可见性判定规则)。
234
+
235
+ expected_content 为空时不作匹配(返回 None),避免误判任意记录为可见。
236
+ """
237
+ normalized = _normalize_content(expected_content)
238
+ if not normalized:
239
+ return None
240
+ for record in records:
241
+ if normalized == _normalize_content(record.content):
242
+ return record
243
+ for record in records:
244
+ if normalized in _normalize_content(record.content):
245
+ return record
246
+ return None
247
+
248
+ def capabilities(self) -> set[str]:
249
+ """透传 backend 能力声明(§7.3)。"""
250
+ return set(self._backend.capabilities())
251
+
252
+ @property
253
+ def last_error(self) -> str:
254
+ """最近一次后端失败原因(成功调用前置空,失败时填充)。
255
+
256
+ 后端(SDK/HTTP)失败时可能吞掉异常返空列表而非抛错,这里把该信号暴露给
257
+ ``build_context``,以区分"后端吞错返空"与"真无记忆"。
258
+ """
259
+ return str(getattr(self._backend, "last_error", "") or "")
260
+
129
261
  def search_text(self, *, user_id: str, query: str, top_k: int | None = None) -> str:
262
+ """检索长期记忆并格式化为文本(方案 §10.8:错误不得混入正文)。
263
+
264
+ Provider 异常时返回空字符串而非错误文本——错误文本会被当作记忆正文注入模型上下文,
265
+ 污染回答。需要区分"真无记忆"与"后端失败"的调用方应改用 ``build_context()``
266
+ 或检查 ``self.last_error``。
267
+ """
130
268
  try:
131
269
  return format_memory_entries(
132
270
  self.search_entries(user_id=user_id, query=query, top_k=top_k)
133
271
  )
134
272
  except Exception as exc:
135
273
  logger.error("load_memory failed: %s", exc)
136
- return f"长期记忆检索失败: {exc}"
274
+ return ""
137
275
 
138
276
  def save_event_strings(
139
277
  self,
@@ -141,18 +279,52 @@ class LongTermMemoryService:
141
279
  user_id: str,
142
280
  event_strings: list[str],
143
281
  metadata: dict[str, Any] | None = None,
282
+ session_id: str | None = None,
283
+ flush: bool | None = None,
144
284
  ) -> bool:
285
+ """保存事件字符串。
286
+
287
+ 显式参数优先于 metadata(方案 §7.6):
288
+ flush: None 表示未指定,回退到 metadata["flush"];
289
+ True/False 显式覆盖 metadata。
290
+ session_id: None 回退到 metadata["session_id"]。
291
+ 兼容期旧调用(仅 metadata)行为不变。
292
+ """
293
+ base_metadata = dict(metadata or {})
294
+ effective_flush = flush if flush is not None else base_metadata.get("flush")
295
+ if session_id is not None:
296
+ base_metadata["session_id"] = session_id
297
+ effective_session_id = base_metadata.get("session_id")
298
+ # 只有显式为 True 时才携带 flush;False/未携带时不传,
299
+ # 让服务端走默认累积策略(§5.2)。
300
+ if effective_flush is True:
301
+ base_metadata["flush"] = True
302
+ else:
303
+ base_metadata.pop("flush", None)
145
304
  return bool(
146
305
  self._backend.save_memory(
147
306
  user_id=user_id,
148
307
  event_strings=event_strings,
149
- metadata=metadata or {},
308
+ metadata=base_metadata,
309
+ session_id=effective_session_id,
310
+ flush=effective_flush,
150
311
  )
151
312
  )
152
313
 
153
314
  def save_text(
154
- self, *, user_id: str, content: str, metadata: dict[str, Any] | None = None
315
+ self,
316
+ *,
317
+ user_id: str,
318
+ content: str,
319
+ metadata: dict[str, Any] | None = None,
320
+ session_id: str | None = None,
321
+ flush: bool | None = None,
155
322
  ) -> bool:
323
+ """保存自包含事实文本。
324
+
325
+ 显式 flush=True 用于显式、内容自包含的持久事实保存
326
+ (如 ksadk_memory_add);普通 sync_turn 不传 flush(§3.1/§8.7)。
327
+ """
156
328
  payload = {
157
329
  "role": "user",
158
330
  "parts": [{"text": content}],
@@ -162,6 +334,8 @@ class LongTermMemoryService:
162
334
  user_id=user_id,
163
335
  event_strings=[json.dumps(payload, ensure_ascii=False)],
164
336
  metadata=metadata,
337
+ session_id=session_id,
338
+ flush=flush,
165
339
  )
166
340
 
167
341
  def build_context(
@@ -176,7 +350,15 @@ class LongTermMemoryService:
176
350
  return None
177
351
  if not self.is_configured():
178
352
  return None
353
+ try:
354
+ entries = self.search_entries(user_id=user_id, query=normalized, top_k=top_k)
355
+ except Exception as exc:
356
+ logger.error("load_memory failed: %s", exc)
357
+ return {"query": normalized, "formatted_text": "", "error": str(exc)}
358
+ backend_error = self.last_error
359
+ if not entries and backend_error:
360
+ return {"query": normalized, "formatted_text": "", "error": backend_error}
179
361
  return {
180
362
  "query": normalized,
181
- "formatted_text": self.search_text(user_id=user_id, query=normalized, top_k=top_k),
363
+ "formatted_text": format_memory_entries(entries),
182
364
  }
@@ -21,6 +21,7 @@ from .server import ProxyServer
21
21
  # 进程级单例:setup_environment 多次调用只起一个 proxy
22
22
  _proxy: Optional[ProxyServer] = None
23
23
  _original_base: Optional[str] = None
24
+ _original_base_env: Optional[dict[str, str | None]] = None
24
25
 
25
26
 
26
27
  def setup_proxy_redirect_if_enabled(
@@ -35,7 +36,7 @@ def setup_proxy_redirect_if_enabled(
35
36
  返回 proxy base_url(已重定向)或 None(未启用)。多次调用幂等(单例)。
36
37
  上游凭证来自 ProxyConfig(从 env 读),不下发给子进程(凭证闭合)。
37
38
  """
38
- global _proxy, _original_base
39
+ global _proxy, _original_base, _original_base_env
39
40
  if _proxy is not None:
40
41
  return _proxy.base_url # 幂等:已起
41
42
  gate = gate or ProxyGate.from_env()
@@ -48,9 +49,7 @@ def setup_proxy_redirect_if_enabled(
48
49
  or os.environ.get("OPENAI_API_BASE")
49
50
  or ""
50
51
  )
51
- key = (
52
- api_key or os.environ.get("OPENAI_API_KEY") or os.environ.get("LLM_API_KEY") or ""
53
- )
52
+ key = api_key or os.environ.get("OPENAI_API_KEY") or os.environ.get("LLM_API_KEY") or ""
54
53
  token = local_token or os.environ.get("KSADK_PROXY_TOKEN") or ""
55
54
  if not upstream or not key:
56
55
  return None # 缺凭证/上游,不启用(保持原 env)
@@ -59,7 +58,11 @@ def setup_proxy_redirect_if_enabled(
59
58
  srv.start()
60
59
  _proxy = srv
61
60
  # 记录原 base 并重定向(双别名都指向 proxy)
62
- _original_base = os.environ.get("OPENAI_BASE_URL") or os.environ.get("OPENAI_API_BASE")
61
+ _original_base_env = {
62
+ "OPENAI_BASE_URL": os.environ.get("OPENAI_BASE_URL"),
63
+ "OPENAI_API_BASE": os.environ.get("OPENAI_API_BASE"),
64
+ }
65
+ _original_base = _original_base_env["OPENAI_BASE_URL"] or _original_base_env["OPENAI_API_BASE"]
63
66
  os.environ["OPENAI_BASE_URL"] = srv.base_url
64
67
  os.environ["OPENAI_API_BASE"] = srv.base_url
65
68
  if not token:
@@ -70,11 +73,15 @@ def setup_proxy_redirect_if_enabled(
70
73
 
71
74
  def teardown_proxy_redirect() -> None:
72
75
  """回收 proxy 并恢复原 OPENAI_BASE_URL(进程退出/卸载时调)。"""
73
- global _proxy, _original_base
76
+ global _proxy, _original_base, _original_base_env
74
77
  if _proxy is not None:
75
78
  _proxy.stop()
76
79
  _proxy = None
77
- if _original_base is not None:
78
- os.environ["OPENAI_BASE_URL"] = _original_base
79
- os.environ["OPENAI_API_BASE"] = _original_base
80
+ if _original_base_env is not None:
81
+ for key, value in _original_base_env.items():
82
+ if value is None:
83
+ os.environ.pop(key, None)
84
+ else:
85
+ os.environ[key] = value
86
+ _original_base_env = None
80
87
  _original_base = None
@@ -12,6 +12,9 @@
12
12
  超时/5xx/429/401/403 一律 "unknown",不改变判定(故障 ≠ 能力缺失)。
13
13
  - ``stream_delta_ok`` 需真发流式请求才能判定,成本高,本模块默认 None(不探),
14
14
  由调用方按需触发或默认走转换层(转换层自己生成完整 delta)。
15
+ - Codex 直连还需功能性 tool probes:真实发送当前 Codex 会声明的
16
+ ``additional_tools`` 工具面。任一必需类型被拒时保留
17
+ ``responses_supported=True``,同时推荐经 Chat 转换层执行。
15
18
  """
16
19
 
17
20
  from __future__ import annotations
@@ -24,6 +27,12 @@ import httpx
24
27
 
25
28
  Verdict = Literal["supported", "unsupported", "unknown"]
26
29
 
30
+ # These are Codex wire-contract capabilities, not provider/model allowlists.
31
+ # namespace/custom are required for a native Responses turn; web_search is an
32
+ # optional built-in that may be disabled without downgrading the whole protocol.
33
+ CODEX_DIRECT_REQUIRED_TOOL_TYPES = frozenset({"namespace", "custom"})
34
+ CODEX_OPTIONAL_TOOL_TYPES = frozenset({"web_search"})
35
+
27
36
 
28
37
  @dataclass
29
38
  class ModelCapabilities:
@@ -31,7 +40,7 @@ class ModelCapabilities:
31
40
 
32
41
  responses_supported: bool | None = None # None = 未知(未探/不确定)
33
42
  stream_delta_ok: bool | None = None # 流式增量 delta;None = 未探
34
- tool_types: set[str] = field(default_factory=set) # 支持的工具 namespace
43
+ tool_types: set[str] = field(default_factory=set) # 已实测支持的 Codex 工具类型
35
44
  preferred_protocol: str = "chat" # "responses" | "chat":路由该走哪条
36
45
  checked_at: float = 0.0
37
46
  verdict: Verdict = "unknown" # 最近一次探测结论
@@ -65,7 +74,10 @@ def probe_responses_capability(
65
74
  ):
66
75
  """功能性 probe /v1/responses,返回能力矩阵(sync) 或 coroutine(async)。
67
76
 
68
- - 200 且结构合法(output+status)→ supported,preferred_protocol=responses
77
+ - 纯文本 200 且结构合法后,再逐个探测真实 Codex 工具类型
78
+ - namespace/custom 成功→ preferred_protocol=responses
79
+ - 仅 web_search 被拒→仍为 responses,但不把 web_search 记入 tool_types
80
+ - namespace/custom 被拒→ supported,preferred_protocol=chat
69
81
  - 200 但非 responses 结构(网关伪 200)→ unsupported,preferred_protocol=chat
70
82
  - 404/405/400 unknown → unsupported,preferred_protocol=chat
71
83
  - 超时/5xx/429/401/403 → unknown,preferred_protocol 保持默认 chat(保守走转换层)
@@ -86,16 +98,47 @@ def _probe_sync(
86
98
  caps = ModelCapabilities(checked_at=time.time(), verdict="unknown")
87
99
  url = f"{base.rstrip('/')}/responses"
88
100
  headers = {"Authorization": f"Bearer {key}", "Content-Type": "application/json"}
89
- payload = {"model": model, "input": "hi", "max_output_tokens": 1, "stream": False}
90
101
  try:
91
- r = client.post(url, json=payload, headers=headers, timeout=timeout)
102
+ r = client.post(
103
+ url,
104
+ json=_base_probe_payload(model),
105
+ headers=headers,
106
+ timeout=timeout,
107
+ )
92
108
  except (httpx.TimeoutException, httpx.ConnectError, httpx.HTTPError):
93
109
  return _finalize(caps)
94
110
  try:
95
111
  data = r.json()
96
112
  except ValueError:
97
113
  data = None
98
- return _apply_response(caps, r.status_code, r.text, data)
114
+ caps = _apply_response(caps, r.status_code, r.text, data)
115
+ if not caps.responses_supported:
116
+ return caps
117
+ for tool_type, payload in _codex_tool_probe_payloads(model):
118
+ try:
119
+ tool_response = client.post(
120
+ url,
121
+ json=payload,
122
+ headers=headers,
123
+ timeout=timeout,
124
+ )
125
+ except (httpx.TimeoutException, httpx.ConnectError, httpx.HTTPError):
126
+ caps.verdict = "unknown"
127
+ return _finalize(caps)
128
+ try:
129
+ tool_data = tool_response.json()
130
+ except ValueError:
131
+ tool_data = None
132
+ caps = _apply_tool_response(
133
+ caps,
134
+ tool_type,
135
+ tool_response.status_code,
136
+ tool_response.text,
137
+ tool_data,
138
+ )
139
+ if tool_type not in caps.tool_types:
140
+ return caps
141
+ return _finalize(caps)
99
142
 
100
143
 
101
144
  async def _probe_async(
@@ -104,16 +147,162 @@ async def _probe_async(
104
147
  caps = ModelCapabilities(checked_at=time.time(), verdict="unknown")
105
148
  url = f"{base.rstrip('/')}/responses"
106
149
  headers = {"Authorization": f"Bearer {key}", "Content-Type": "application/json"}
107
- payload = {"model": model, "input": "hi", "max_output_tokens": 1, "stream": False}
108
150
  try:
109
- r = await client.post(url, json=payload, headers=headers, timeout=timeout)
151
+ r = await client.post(
152
+ url,
153
+ json=_base_probe_payload(model),
154
+ headers=headers,
155
+ timeout=timeout,
156
+ )
110
157
  except (httpx.TimeoutException, httpx.ConnectError, httpx.HTTPError):
111
158
  return _finalize(caps)
112
159
  try:
113
160
  data = r.json()
114
161
  except ValueError:
115
162
  data = None
116
- return _apply_response(caps, r.status_code, r.text, data)
163
+ caps = _apply_response(caps, r.status_code, r.text, data)
164
+ if not caps.responses_supported:
165
+ return caps
166
+ for tool_type, payload in _codex_tool_probe_payloads(model):
167
+ try:
168
+ tool_response = await client.post(
169
+ url,
170
+ json=payload,
171
+ headers=headers,
172
+ timeout=timeout,
173
+ )
174
+ except (httpx.TimeoutException, httpx.ConnectError, httpx.HTTPError):
175
+ caps.verdict = "unknown"
176
+ return _finalize(caps)
177
+ try:
178
+ tool_data = tool_response.json()
179
+ except ValueError:
180
+ tool_data = None
181
+ caps = _apply_tool_response(
182
+ caps,
183
+ tool_type,
184
+ tool_response.status_code,
185
+ tool_response.text,
186
+ tool_data,
187
+ )
188
+ if tool_type not in caps.tool_types:
189
+ return caps
190
+ return _finalize(caps)
191
+
192
+
193
+ def _base_probe_payload(model: str) -> dict[str, Any]:
194
+ return {"model": model, "input": "hi", "max_output_tokens": 1, "stream": False}
195
+
196
+
197
+ def _namespace_probe_payload(model: str) -> dict[str, Any]:
198
+ """Return the smallest real Codex 0.147 dynamic-tool declaration."""
199
+
200
+ return {
201
+ "model": model,
202
+ "input": [
203
+ {
204
+ "type": "additional_tools",
205
+ "role": "developer",
206
+ "tools": [
207
+ {
208
+ "type": "namespace",
209
+ "name": "functions",
210
+ "description": "KsADK Codex capability probe",
211
+ "tools": [
212
+ {
213
+ "type": "function",
214
+ "name": "probe",
215
+ "description": "Probe Codex namespace tool support",
216
+ "parameters": {
217
+ "type": "object",
218
+ "properties": {},
219
+ "additionalProperties": False,
220
+ },
221
+ }
222
+ ],
223
+ }
224
+ ],
225
+ },
226
+ {
227
+ "type": "message",
228
+ "role": "user",
229
+ "content": [{"type": "input_text", "text": "Reply with OK."}],
230
+ },
231
+ ],
232
+ "max_output_tokens": 1,
233
+ "stream": False,
234
+ }
235
+
236
+
237
+ def _custom_probe_payload(model: str) -> dict[str, Any]:
238
+ """Return the smallest Codex namespaced freeform-tool declaration."""
239
+
240
+ payload = _namespace_probe_payload(model)
241
+ payload["input"][0]["tools"][0]["tools"] = [
242
+ {
243
+ "type": "custom",
244
+ "name": "probe",
245
+ "description": "Probe Codex custom tool support",
246
+ "format": {
247
+ "type": "grammar",
248
+ "syntax": "lark",
249
+ "definition": 'start: "OK"',
250
+ },
251
+ }
252
+ ]
253
+ return payload
254
+
255
+
256
+ def _web_search_probe_payload(model: str) -> dict[str, Any]:
257
+ """Return Codex's ``additional_tools`` built-in web-search declaration."""
258
+
259
+ payload = _namespace_probe_payload(model)
260
+ payload["input"][0]["tools"] = [{"type": "web_search"}]
261
+ return payload
262
+
263
+
264
+ def _codex_tool_probe_payloads(model: str) -> tuple[tuple[str, dict[str, Any]], ...]:
265
+ """Return the model-agnostic direct tool surface required by Codex."""
266
+
267
+ return (
268
+ ("namespace", _namespace_probe_payload(model)),
269
+ ("custom", _custom_probe_payload(model)),
270
+ ("web_search", _web_search_probe_payload(model)),
271
+ )
272
+
273
+
274
+ def _apply_tool_response(
275
+ caps: ModelCapabilities, tool_type: str, status: int, text: str, data: Any
276
+ ) -> ModelCapabilities:
277
+ valid_envelope = isinstance(data, dict) and "output" in data and "status" in data
278
+ envelope_failed = valid_envelope and (
279
+ str(data.get("status") or "").lower() == "failed" or bool(data.get("error"))
280
+ )
281
+ if status == 200 and valid_envelope and not envelope_failed:
282
+ caps.tool_types.add(tool_type)
283
+ caps.verdict = "supported"
284
+ return _finalize(caps)
285
+
286
+ low = (text or "").lower()
287
+ dialect_marker = any(
288
+ marker in low
289
+ for marker in (
290
+ tool_type.lower(),
291
+ "additional_tools",
292
+ "invalid value",
293
+ "supported values",
294
+ "not supported",
295
+ "unrecognized",
296
+ )
297
+ )
298
+ dialect_rejected = (
299
+ status in (400, 404, 405, 422) and (status in (404, 405) or dialect_marker)
300
+ ) or (status == 200 and envelope_failed and dialect_marker)
301
+ if not dialect_rejected:
302
+ # Transient failures do not establish direct compatibility and should
303
+ # not be cached as a native Codex tool-capable endpoint.
304
+ caps.verdict = "unknown"
305
+ return _finalize(caps)
117
306
 
118
307
 
119
308
  def _apply_response(
@@ -136,7 +325,11 @@ def _apply_response(
136
325
 
137
326
  def _finalize(caps: ModelCapabilities) -> ModelCapabilities:
138
327
  """根据 verdict 定 preferred_protocol(保守:不确定也走转换层 chat)。"""
139
- if caps.verdict == "supported" and caps.responses_supported:
328
+ if (
329
+ caps.verdict == "supported"
330
+ and caps.responses_supported
331
+ and CODEX_DIRECT_REQUIRED_TOOL_TYPES.issubset(caps.tool_types)
332
+ ):
140
333
  caps.preferred_protocol = "responses"
141
334
  else:
142
335
  # unsupported 或 unknown 都默认 chat(走转换层):unknown 时走转换层更安全,
@@ -83,13 +83,19 @@ def build_restore_map(tools: list[Any] | None) -> dict[str, dict[str, str]]:
83
83
  if not namespace:
84
84
  continue
85
85
  for child in _namespace_children(tool):
86
- if not isinstance(child, dict) or child.get("type") != "function":
86
+ if not isinstance(child, dict) or child.get("type") not in {
87
+ "function",
88
+ "custom",
89
+ }:
87
90
  continue
88
91
  name = (child.get("name") or "").strip()
89
92
  if not name:
90
93
  continue
91
94
  flat = flatten_namespace_tool_name(namespace, name)
92
- restore.setdefault(flat, {"namespace": namespace, "name": name})
95
+ entry = {"namespace": namespace, "name": name}
96
+ if child.get("type") == "custom":
97
+ entry["custom"] = "true"
98
+ restore.setdefault(flat, entry)
93
99
  return restore
94
100
 
95
101
 
@@ -104,7 +110,7 @@ def _rewrite_qualified_calls(value: Any, owners: dict[str, dict[str, str]]) -> b
104
110
  for item in value:
105
111
  changed |= _rewrite_qualified_calls(item, owners)
106
112
  elif isinstance(value, dict):
107
- if value.get("type") == "function_call":
113
+ if value.get("type") in {"function_call", "custom_tool_call"}:
108
114
  namespace = (value.get("namespace") or "").strip()
109
115
  name = (value.get("name") or "").strip()
110
116
  if namespace and name:
@@ -142,24 +148,39 @@ def flatten_request_namespaces(body: dict) -> dict[str, dict[str, str]]:
142
148
  if tool.get("type") == "namespace":
143
149
  namespace = (tool.get("name") or "").strip()
144
150
  for child in _namespace_children(tool):
145
- if not isinstance(child, dict) or child.get("type") != "function":
151
+ if not isinstance(child, dict) or child.get("type") not in {
152
+ "function",
153
+ "custom",
154
+ }:
146
155
  continue
147
156
  name = (child.get("name") or "").strip()
148
157
  if not name or not namespace:
149
158
  continue
150
159
  flat = flatten_namespace_tool_name(namespace, name)
151
160
  if flat in seen_flat:
152
- raise ValueError(
153
- f"namespace 拍平撞名:{flat} 来自不同 child,上游无法消歧"
154
- )
161
+ raise ValueError(f"namespace 拍平撞名:{flat} 来自不同 child,上游无法消歧")
155
162
  seen_flat.add(flat)
156
- flat_tools.append({
157
- "type": "function",
158
- "name": flat,
159
- "description": child.get("description", ""),
160
- "parameters": child.get("parameters", {"type": "object", "properties": {}}),
161
- **({"strict": child["strict"]} if "strict" in child else {}),
162
- })
163
+ if child.get("type") == "custom":
164
+ flat_tools.append(
165
+ {
166
+ "type": "custom",
167
+ "name": flat,
168
+ "description": child.get("description", ""),
169
+ **({"format": child["format"]} if "format" in child else {}),
170
+ }
171
+ )
172
+ else:
173
+ flat_tools.append(
174
+ {
175
+ "type": "function",
176
+ "name": flat,
177
+ "description": child.get("description", ""),
178
+ "parameters": child.get(
179
+ "parameters", {"type": "object", "properties": {}}
180
+ ),
181
+ **({"strict": child["strict"]} if "strict" in child else {}),
182
+ }
183
+ )
163
184
  else:
164
185
  flat_tools.append(tool)
165
186
  body["tools"] = flat_tools
@@ -194,7 +215,10 @@ def restore_function_call(item: dict, restore_map: dict[str, dict[str, str]]) ->
194
215
  item["type"] = "custom_tool_call"
195
216
  item["name"] = entry["name"]
196
217
  item["input"] = text_input
197
- item.pop("namespace", None)
218
+ if entry.get("namespace"):
219
+ item["namespace"] = entry["namespace"]
220
+ else:
221
+ item.pop("namespace", None)
198
222
  return item
199
223
  item["name"] = entry["name"]
200
224
  item["namespace"] = entry["namespace"]