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
ksadk/runtime/usage.py ADDED
@@ -0,0 +1,33 @@
1
+ """Canonical runtime usage projection helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Mapping
6
+
7
+
8
+ def canonical_usage_payload(
9
+ usage: Mapping[str, Any], *, runtime_type: str
10
+ ) -> dict[str, Any]:
11
+ """Normalize provider usage, including nested cache/reasoning details."""
12
+ input_details = usage.get("input_token_details")
13
+ output_details = usage.get("output_token_details")
14
+ cached = (
15
+ input_details.get("cached")
16
+ if isinstance(input_details, Mapping)
17
+ else usage.get("cached_tokens")
18
+ )
19
+ reasoning = (
20
+ output_details.get("reasoning")
21
+ if isinstance(output_details, Mapping)
22
+ else usage.get("reasoning_tokens")
23
+ )
24
+ input_tokens = int(usage.get("input_tokens") or 0)
25
+ output_tokens = int(usage.get("output_tokens") or 0)
26
+ return {
27
+ "input_tokens": input_tokens,
28
+ "output_tokens": output_tokens,
29
+ "total_tokens": int(usage.get("total_tokens") or input_tokens + output_tokens),
30
+ "cached_tokens": int(cached or 0),
31
+ "reasoning_tokens": int(reasoning or 0),
32
+ "source": str(usage.get("source") or runtime_type),
33
+ }
ksadk/sandbox/__init__.py CHANGED
@@ -12,7 +12,7 @@ from ksadk.sandbox.base import (
12
12
  SandboxSpec,
13
13
  SandboxType,
14
14
  )
15
- from ksadk.sandbox.factory import create_sandbox_backend, sandbox_spec_from_env
15
+ from ksadk.sandbox.factory import create_sandbox_backend, sandbox_spec_from_env, setup_sandbox_api_url_if_needed
16
16
 
17
17
  __all__ = [
18
18
  "E2BSandboxBackend",
@@ -28,4 +28,5 @@ __all__ = [
28
28
  "SandboxType",
29
29
  "create_sandbox_backend",
30
30
  "sandbox_spec_from_env",
31
+ "setup_sandbox_api_url_if_needed",
31
32
  ]
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import logging
3
4
  import os
4
5
  import time
5
6
  from typing import Any
@@ -12,10 +13,15 @@ from ksadk.sandbox.base import (
12
13
  SandboxSpec,
13
14
  )
14
15
 
16
+ logger = logging.getLogger(__name__)
17
+
15
18
  _TRANSIENT_STARTUP_ERROR_NAMES = {
16
19
  "NotFoundException",
17
20
  "FileNotFoundException",
18
21
  "SandboxNotFoundException",
22
+ # create 快速返回后 envd RPC 通道可能尚未就绪 (connection refused / unavailable),
23
+ # SDK 将其包装为 TimeoutException 抛出, 需按瞬时启动错误重试。
24
+ "TimeoutException",
19
25
  }
20
26
 
21
27
 
@@ -148,7 +154,7 @@ class E2BSandboxBackend:
148
154
  allow_internet_access=self.spec.allow_internet_access,
149
155
  )
150
156
  session = self._wrap_sandbox(sandbox)
151
- self._wait_until_ready(session)
157
+ self._wait_until_ready(session, runtime_env)
152
158
  session.write_files(
153
159
  [(item.target_path, item.source.read_bytes()) for item in input_files or []]
154
160
  )
@@ -157,6 +163,33 @@ class E2BSandboxBackend:
157
163
  def _wrap_sandbox(self, sandbox: Any) -> E2BSandboxSession:
158
164
  return E2BSandboxSession(sandbox)
159
165
 
160
- def _wait_until_ready(self, session: E2BSandboxSession) -> None:
166
+ def _wait_until_ready(
167
+ self, session: E2BSandboxSession, env: dict[str, str] | None = None
168
+ ) -> None:
161
169
  _with_startup_retry(lambda: session.run_command("true"))
162
170
  _with_startup_retry(lambda: session.write_file("/tmp/.ksadk-sandbox-ready", ""))
171
+ self._wait_env_applied(session, env or {})
172
+
173
+ def _wait_env_applied(self, session: E2BSandboxSession, env: dict[str, str]) -> None:
174
+ # create 返回后 envd 异步应用 envVars, 实测存在秒级延迟;
175
+ # 轮询一个非空变量直到值就位, 避免紧随其后的 agent 进程读到缺失的环境。
176
+ probe = next(((k, v) for k, v in env.items() if v), None)
177
+ if probe is None:
178
+ return
179
+ key, expected = probe
180
+ attempts = _startup_retry_attempts()
181
+ delay = _startup_retry_delay()
182
+ for attempt in range(attempts):
183
+ result = session.run_command(f"printenv {key} || true")
184
+ if result.stdout.strip() == expected:
185
+ return
186
+ time.sleep(min(delay * (2**attempt), 1.0))
187
+ # 不 raise: 模板可能预置同名空值覆盖 create 注入 (envd 合并优先级问题),
188
+ # 此时调用方仍可通过 run_command(env=...) 的 per-command 注入拿到正确值。
189
+ logger.warning(
190
+ "E2B sandbox env %s not applied after %d attempts (expected %r); "
191
+ "template may predefine an empty override",
192
+ key,
193
+ attempts,
194
+ expected,
195
+ )
ksadk/sandbox/factory.py CHANGED
@@ -16,11 +16,83 @@ def bool_env(name: str, default: bool = True) -> bool:
16
16
  return raw.strip().lower() in {"1", "true", "yes", "on"}
17
17
 
18
18
 
19
+ def _resolve_sandbox_api_url() -> str:
20
+ """Probe sandbox control-plane URL at runtime (in-Pod).
21
+
22
+ agentengine-server injects public E2B_API_URL on the management cluster.
23
+ public_only compute pods can reach the public address directly; private_only
24
+ pods have no public egress. Strategy: try the original (public) URL first;
25
+ if reachable keep it, otherwise fall back to the 198 public-service-net
26
+ internal address. Mirrors KSPMAS get_kspmas_api_base(): probe runs in-Pod.
27
+ """
28
+ raw = (os.environ.get("E2B_API_URL") or "").strip()
29
+ if not raw:
30
+ return raw
31
+
32
+ from urllib.parse import urlsplit, urlunsplit
33
+
34
+ parsed = urlsplit(raw)
35
+ host = (parsed.hostname or "").lower()
36
+ if host.endswith(".sdns.ksyun.com"):
37
+ return raw
38
+ if not host.endswith(".sandbox.ksyun.com"):
39
+ return raw
40
+
41
+ parts = host.split(".")
42
+ region = ""
43
+ if len(parts) >= 4 and parts[0] == "mgr":
44
+ region = parts[1]
45
+ elif len(parts) >= 4:
46
+ region = parts[1] if parts[1] != "sandbox" else ""
47
+ if not region:
48
+ return raw
49
+
50
+ import socket
51
+
52
+ port = parsed.port or (443 if parsed.scheme == "https" else 80)
53
+
54
+ # 1) Try the original (public) URL first; reachable => keep it.
55
+ try:
56
+ s = socket.socket()
57
+ s.settimeout(1.5)
58
+ s.connect((host, port))
59
+ s.close()
60
+ return raw
61
+ except OSError:
62
+ pass
63
+
64
+ # 2) Public unreachable (e.g. private_only node) => fall back to internal.
65
+ internal_host = f"sandbox.{region}.sandbox.sdns.ksyun.com"
66
+ try:
67
+ s = socket.socket()
68
+ s.settimeout(1.5)
69
+ s.connect((internal_host, port))
70
+ s.close()
71
+ except OSError:
72
+ return raw
73
+
74
+ internal_url = urlunsplit((parsed.scheme or "https", internal_host, parsed.path, parsed.query, ""))
75
+ return internal_url
76
+
77
+
78
+ def setup_sandbox_api_url_if_needed() -> None:
79
+ """Override E2B_API_URL to internal address before creating E2B backend.
80
+
81
+ Only runs in managed runtime (AGENT_RUNTIME_ID / K8S); local dev unaffected.
82
+ """
83
+ if not os.environ.get("AGENT_RUNTIME_ID") and not os.environ.get("KUBERNETES_SERVICE_HOST"):
84
+ return
85
+ resolved = _resolve_sandbox_api_url()
86
+ if resolved != (os.environ.get("E2B_API_URL") or ""):
87
+ os.environ["E2B_API_URL"] = resolved
88
+
89
+
19
90
  def create_sandbox_backend(
20
91
  backend: str | None = None,
21
92
  *,
22
93
  sandbox_cls: Any | None = None,
23
94
  ) -> SandboxBackend:
95
+ setup_sandbox_api_url_if_needed()
24
96
  resolved = (backend or os.environ.get("KSADK_SANDBOX_BACKEND") or "e2b").strip().lower()
25
97
  if resolved in {"local", "local_process"}:
26
98
  return LocalProcessSandboxBackend(
@@ -0,0 +1,18 @@
1
+ """Local Scheduler Lite contracts and deterministic calendar calculations."""
2
+
3
+ from ksadk.scheduler.calendar import next_schedule_time
4
+ from ksadk.scheduler.contracts import ScheduledTask, ScheduleOccurrence, ScheduleSpec
5
+ from ksadk.scheduler.dispatcher import AgentControlSchedulerDispatcher
6
+ from ksadk.scheduler.engine import SchedulerDispatchError, SchedulerEngine
7
+ from ksadk.scheduler.sqlite_store import SchedulerSQLiteStore
8
+
9
+ __all__ = [
10
+ "ScheduleOccurrence",
11
+ "ScheduledTask",
12
+ "ScheduleSpec",
13
+ "AgentControlSchedulerDispatcher",
14
+ "SchedulerDispatchError",
15
+ "SchedulerEngine",
16
+ "SchedulerSQLiteStore",
17
+ "next_schedule_time",
18
+ ]
@@ -0,0 +1,102 @@
1
+ """Deterministic, dependency-free calendar calculations for Scheduler Lite.
2
+
3
+ The supported cron dialect intentionally stays small and explicit: five fields,
4
+ numeric values, comma lists, ranges and ``*/step``. It is sufficient for the
5
+ Studio scheduler and avoids inheriting an accidental transitive dependency.
6
+ All matching happens after UTC -> IANA timezone conversion. That means a
7
+ nonexistent DST local minute is skipped and the repeated fall-back minute runs
8
+ once (the first ``fold`` only).
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from datetime import datetime, timedelta, timezone
14
+ from zoneinfo import ZoneInfo
15
+
16
+ from ksadk.scheduler.contracts import ScheduleSpec, _parse_cron
17
+
18
+
19
+ def next_schedule_time(
20
+ schedule: ScheduleSpec,
21
+ *,
22
+ after: datetime,
23
+ anchor_at: datetime | None = None,
24
+ ) -> datetime | None:
25
+ """Return the first scheduled UTC timestamp strictly after ``after``."""
26
+
27
+ if after.tzinfo is None:
28
+ raise ValueError("after must include a timezone")
29
+ after = after.astimezone(timezone.utc)
30
+ if schedule.kind == "once":
31
+ assert schedule.at is not None
32
+ return schedule.at if schedule.at > after else None
33
+ if schedule.kind == "interval":
34
+ assert schedule.every_seconds is not None
35
+ anchor = (schedule.anchor_at or anchor_at or after).astimezone(timezone.utc)
36
+ if anchor > after:
37
+ return anchor
38
+ elapsed = (after - anchor).total_seconds()
39
+ step = schedule.every_seconds
40
+ return anchor + timedelta(seconds=(int(elapsed // step) + 1) * step)
41
+ assert schedule.expression is not None
42
+ return _next_cron(schedule.expression, schedule.timezone, after)
43
+
44
+
45
+ def _next_cron(expression: str, timezone_name: str, after: datetime) -> datetime:
46
+ minute, hour, day, month, weekday = _parse_cron(expression)
47
+ fields = (
48
+ _expand_field(minute, 0, 59),
49
+ _expand_field(hour, 0, 23),
50
+ _expand_field(day, 1, 31),
51
+ _expand_field(month, 1, 12),
52
+ _expand_field(weekday, 0, 6),
53
+ )
54
+ zone = ZoneInfo(timezone_name)
55
+ candidate = after.replace(second=0, microsecond=0) + timedelta(minutes=1)
56
+ # Five years is a deliberate guard against impossible expressions such as
57
+ # 31 February. The caller receives a typed validation error rather than a
58
+ # scheduler loop that runs forever.
59
+ deadline = candidate + timedelta(days=366 * 5)
60
+ while candidate <= deadline:
61
+ local = candidate.astimezone(zone)
62
+ # datetime.weekday uses Monday=0. Cron uses Sunday=0.
63
+ cron_weekday = (local.weekday() + 1) % 7
64
+ if (
65
+ local.fold == 0
66
+ and local.minute in fields[0]
67
+ and local.hour in fields[1]
68
+ and local.day in fields[2]
69
+ and local.month in fields[3]
70
+ and cron_weekday in fields[4]
71
+ ):
72
+ return candidate
73
+ candidate += timedelta(minutes=1)
74
+ raise ValueError("cron expression has no matching time within five years")
75
+
76
+
77
+ def _expand_field(value: str, minimum: int, maximum: int) -> set[int]:
78
+ result: set[int] = set()
79
+ for item in value.split(","):
80
+ if "/" in item:
81
+ base, step_text = item.split("/", 1)
82
+ else:
83
+ base, step_text = item, None
84
+ step = int(step_text) if step_text is not None else 1
85
+ if step <= 0:
86
+ raise ValueError("cron step must be positive")
87
+ if base == "*":
88
+ start, end = minimum, maximum
89
+ elif "-" in base:
90
+ start_text, end_text = base.split("-", 1)
91
+ start, end = int(start_text), int(end_text)
92
+ else:
93
+ start = end = int(base)
94
+ if start < minimum or end > maximum or start > end:
95
+ raise ValueError("cron field is outside its valid range")
96
+ result.update(range(start, end + 1, step))
97
+ if not result:
98
+ raise ValueError("cron field must not be empty")
99
+ return result
100
+
101
+
102
+ __all__ = ["next_schedule_time"]
@@ -0,0 +1,254 @@
1
+ """Versioned Scheduler Lite source contracts (P2-06 foundation)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from datetime import datetime, timezone
7
+ from typing import Any, Literal
8
+ from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
9
+
10
+ from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
11
+
12
+
13
+ def _to_camel(value: str) -> str:
14
+ head, *tail = value.split("_")
15
+ return head + "".join(part.capitalize() for part in tail)
16
+
17
+
18
+ class ScheduleModel(BaseModel):
19
+ model_config = ConfigDict(
20
+ alias_generator=_to_camel,
21
+ populate_by_name=True,
22
+ extra="forbid",
23
+ frozen=True,
24
+ )
25
+
26
+
27
+ _TASK_ID = re.compile(r"^[a-z][a-z0-9-]{2,62}$")
28
+ ScheduleOccurrenceState = Literal[
29
+ "claimed",
30
+ "accepted",
31
+ "running",
32
+ "succeeded",
33
+ "failed",
34
+ "skipped",
35
+ "cancelled",
36
+ ]
37
+
38
+
39
+ class ScheduleSpec(ScheduleModel):
40
+ kind: Literal["once", "interval", "cron"]
41
+ timezone: str = "UTC"
42
+ at: datetime | None = None
43
+ every_seconds: int | None = Field(default=None, ge=60, le=31_536_000)
44
+ anchor_at: datetime | None = None
45
+ expression: str | None = Field(default=None, min_length=9, max_length=128)
46
+ misfire_policy: Literal["skip", "run_once"] = "skip"
47
+
48
+ @field_validator("timezone")
49
+ @classmethod
50
+ def validate_timezone(cls, value: str) -> str:
51
+ try:
52
+ ZoneInfo(value)
53
+ except ZoneInfoNotFoundError as error:
54
+ raise ValueError("timezone must be an IANA timezone") from error
55
+ return value
56
+
57
+ @field_validator("at", "anchor_at")
58
+ @classmethod
59
+ def normalize_timestamp(cls, value: datetime | None) -> datetime | None:
60
+ if value is None:
61
+ return None
62
+ if value.tzinfo is None:
63
+ raise ValueError("schedule timestamps must include a timezone")
64
+ return value.astimezone(timezone.utc)
65
+
66
+ @model_validator(mode="after")
67
+ def validate_kind_shape(self) -> "ScheduleSpec":
68
+ if self.kind == "once":
69
+ if self.at is None or self.every_seconds is not None or self.expression is not None:
70
+ raise ValueError("once schedule requires only at")
71
+ elif self.kind == "interval":
72
+ if self.every_seconds is None or self.at is not None or self.expression is not None:
73
+ raise ValueError("interval schedule requires everySeconds and no expression")
74
+ elif self.kind == "cron":
75
+ if self.expression is None or self.at is not None or self.every_seconds is not None:
76
+ raise ValueError("cron schedule requires only expression")
77
+ _parse_cron(self.expression)
78
+ return self
79
+
80
+
81
+ class ScheduledTaskTarget(ScheduleModel):
82
+ # ``agent_instance_id`` identifies the concrete Kernel owner which will
83
+ # consume an Inbox command. ``agent_id`` is the Studio-facing stable
84
+ # logical Agent identity used to retain a task on its detail page even when
85
+ # the task is pinned to an older immutable Build. It is optional only so
86
+ # existing v1 SQLite rows remain readable; all Studio-authored tasks set it.
87
+ agent_id: str | None = Field(default=None, min_length=1, max_length=256)
88
+ tenant_id: str = Field(min_length=1, max_length=256)
89
+ agent_instance_id: str = Field(min_length=1, max_length=256)
90
+ agent_version_ref: str | None = Field(default=None, min_length=1, max_length=256)
91
+ session_id: str | None = Field(default=None, min_length=1, max_length=256)
92
+ authorization_ref: str = Field(min_length=1, max_length=512)
93
+
94
+
95
+ class ScheduleCommandTemplate(ScheduleModel):
96
+ """The sole supported Scheduler Lite action: durable enqueue via AgentControl."""
97
+
98
+ command_type: Literal["enqueue"] = "enqueue"
99
+ payload: dict[str, Any]
100
+
101
+ @model_validator(mode="after")
102
+ def validate_payload(self) -> "ScheduleCommandTemplate":
103
+ if "content" not in self.payload:
104
+ raise ValueError("scheduled enqueue payload requires content")
105
+ _reject_clear_secrets(self.payload)
106
+ return self
107
+
108
+
109
+ class ScheduledTask(ScheduleModel):
110
+ api_version: Literal["schedule.ksadk.io/v1"] = "schedule.ksadk.io/v1"
111
+ kind: Literal["ScheduledTask"] = "ScheduledTask"
112
+ schema_version: Literal[1] = 1
113
+ task_id: str = Field(min_length=3, max_length=63)
114
+ # A human-facing task label belongs to the durable schedule intent, not a
115
+ # transient Studio card. Optional keeps the previously frozen v1 rows
116
+ # and third-party producers readable during this additive transition.
117
+ display_name: str | None = Field(default=None, min_length=1, max_length=128)
118
+ target: ScheduledTaskTarget
119
+ schedule: ScheduleSpec
120
+ command: ScheduleCommandTemplate
121
+ enabled: bool = True
122
+ continuity: Literal["new_session", "continue_session"] = "new_session"
123
+ concurrency_policy: Literal["forbid"] = "forbid"
124
+ next_run_at: datetime | None = None
125
+ created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
126
+ updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
127
+
128
+ @field_validator("task_id")
129
+ @classmethod
130
+ def validate_task_id(cls, value: str) -> str:
131
+ if not _TASK_ID.fullmatch(value):
132
+ raise ValueError("taskId must be a lowercase stable identifier")
133
+ return value
134
+
135
+ @model_validator(mode="after")
136
+ def validate_continuity_target(self) -> "ScheduledTask":
137
+ if self.continuity == "continue_session" and not self.target.session_id:
138
+ raise ValueError("continue_session schedule requires target.sessionId")
139
+ return self
140
+
141
+ @field_validator("next_run_at", "created_at", "updated_at")
142
+ @classmethod
143
+ def normalize_timestamp(cls, value: datetime | None) -> datetime | None:
144
+ if value is None:
145
+ return None
146
+ if value.tzinfo is None:
147
+ raise ValueError("scheduler timestamps must include a timezone")
148
+ return value.astimezone(timezone.utc)
149
+
150
+
151
+ class ScheduleOccurrenceTransition(ScheduleModel):
152
+ """One durable state change displayed by Studio's execution history."""
153
+
154
+ state: ScheduleOccurrenceState
155
+ at: datetime
156
+ detail: str | None = Field(default=None, max_length=1024)
157
+ error_code: str | None = Field(default=None, max_length=128)
158
+
159
+ @field_validator("at")
160
+ @classmethod
161
+ def normalize_timestamp(cls, value: datetime) -> datetime:
162
+ if value.tzinfo is None:
163
+ raise ValueError("occurrence transition timestamps must include a timezone")
164
+ return value.astimezone(timezone.utc)
165
+
166
+
167
+ class ScheduleOccurrence(ScheduleModel):
168
+ api_version: Literal["schedule.ksadk.io/v1"] = "schedule.ksadk.io/v1"
169
+ kind: Literal["ScheduleOccurrence"] = "ScheduleOccurrence"
170
+ schema_version: Literal[1] = 1
171
+ occurrence_id: str = Field(min_length=8, max_length=256)
172
+ task_id: str = Field(min_length=3, max_length=63)
173
+ # Keep the execution target as an immutable occurrence snapshot. Tasks can
174
+ # later be edited or deleted, but history/reconciliation must still know
175
+ # exactly which Agent revision accepted this occurrence.
176
+ target: ScheduledTaskTarget | None = None
177
+ scheduled_for: datetime
178
+ session_id: str = Field(min_length=1, max_length=256)
179
+ trigger: Literal["schedule", "manual"] = "schedule"
180
+ state: ScheduleOccurrenceState
181
+ attempt: int = Field(default=1, ge=1, le=100)
182
+ command_id: str | None = None
183
+ accepted_seq: int | None = Field(default=None, ge=0)
184
+ last_event_seq: int | None = Field(default=None, ge=0)
185
+ run_id: str | None = Field(default=None, min_length=1, max_length=256)
186
+ claimed_at: datetime | None = None
187
+ accepted_at: datetime | None = None
188
+ started_at: datetime | None = None
189
+ error_code: str | None = Field(default=None, max_length=128)
190
+ detail: str | None = Field(default=None, max_length=1024)
191
+ completed_at: datetime | None = None
192
+ transitions: tuple[ScheduleOccurrenceTransition, ...] = ()
193
+
194
+ @field_validator("scheduled_for", "claimed_at", "accepted_at", "started_at", "completed_at")
195
+ @classmethod
196
+ def normalize_timestamp(cls, value: datetime | None) -> datetime | None:
197
+ if value is None:
198
+ return None
199
+ if value.tzinfo is None:
200
+ raise ValueError("occurrence timestamps must include a timezone")
201
+ return value.astimezone(timezone.utc)
202
+
203
+ @model_validator(mode="after")
204
+ def validate_terminal_fields(self) -> "ScheduleOccurrence":
205
+ terminal = {"succeeded", "failed", "skipped", "cancelled"}
206
+ if self.state in terminal and self.completed_at is None:
207
+ raise ValueError("terminal occurrence requires completedAt")
208
+ if self.state == "failed" and not self.error_code:
209
+ raise ValueError("failed occurrence requires errorCode")
210
+ if self.state == "running" and (not self.run_id or self.started_at is None):
211
+ raise ValueError("running occurrence requires runId and startedAt")
212
+ if self.transitions:
213
+ timestamps = [transition.at for transition in self.transitions]
214
+ if timestamps != sorted(timestamps):
215
+ raise ValueError("occurrence transitions must be chronological")
216
+ if self.transitions[-1].state != self.state:
217
+ raise ValueError("last occurrence transition must match state")
218
+ return self
219
+
220
+
221
+ def _reject_clear_secrets(value: Any, *, key: str = "payload") -> None:
222
+ if isinstance(value, dict):
223
+ for name, item in value.items():
224
+ lowered = str(name).lower()
225
+ if any(term in lowered for term in ("secret", "password", "token", "api_key")):
226
+ if not isinstance(item, str) or not item.startswith(
227
+ ("env://", "secret://", "credential://", "vault://")
228
+ ):
229
+ raise ValueError(f"{key}.{name} must be a secret reference, not a value")
230
+ _reject_clear_secrets(item, key=f"{key}.{name}")
231
+ elif isinstance(value, list):
232
+ for index, item in enumerate(value):
233
+ _reject_clear_secrets(item, key=f"{key}[{index}]")
234
+
235
+
236
+ def _parse_cron(expression: str) -> tuple[str, str, str, str, str]:
237
+ fields = tuple(part for part in expression.split() if part)
238
+ if len(fields) != 5:
239
+ raise ValueError("cron expression must use exactly five fields")
240
+ for value in fields:
241
+ if not re.fullmatch(r"[0-9*/,-]+", value):
242
+ raise ValueError("cron fields only support numbers, ranges, lists and steps")
243
+ return fields # detailed field grammar is shared with calendar.py
244
+
245
+
246
+ __all__ = [
247
+ "ScheduleCommandTemplate",
248
+ "ScheduleOccurrence",
249
+ "ScheduleOccurrenceState",
250
+ "ScheduleOccurrenceTransition",
251
+ "ScheduleSpec",
252
+ "ScheduledTask",
253
+ "ScheduledTaskTarget",
254
+ ]