aethergraph 0.1.0a1__py3-none-any.whl → 0.1.0a3__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 (267) hide show
  1. aethergraph/__init__.py +4 -10
  2. aethergraph/__main__.py +296 -0
  3. aethergraph/api/v1/__init__.py +0 -0
  4. aethergraph/api/v1/agents.py +46 -0
  5. aethergraph/api/v1/apps.py +70 -0
  6. aethergraph/api/v1/artifacts.py +415 -0
  7. aethergraph/api/v1/channels.py +89 -0
  8. aethergraph/api/v1/deps.py +168 -0
  9. aethergraph/api/v1/graphs.py +259 -0
  10. aethergraph/api/v1/identity.py +25 -0
  11. aethergraph/api/v1/memory.py +353 -0
  12. aethergraph/api/v1/misc.py +47 -0
  13. aethergraph/api/v1/pagination.py +29 -0
  14. aethergraph/api/v1/runs.py +568 -0
  15. aethergraph/api/v1/schemas.py +535 -0
  16. aethergraph/api/v1/session.py +323 -0
  17. aethergraph/api/v1/stats.py +201 -0
  18. aethergraph/api/v1/viz.py +152 -0
  19. aethergraph/config/config.py +22 -0
  20. aethergraph/config/loader.py +3 -2
  21. aethergraph/config/storage.py +209 -0
  22. aethergraph/contracts/__init__.py +0 -0
  23. aethergraph/contracts/services/__init__.py +0 -0
  24. aethergraph/contracts/services/artifacts.py +27 -14
  25. aethergraph/contracts/services/memory.py +45 -17
  26. aethergraph/contracts/services/metering.py +129 -0
  27. aethergraph/contracts/services/runs.py +50 -0
  28. aethergraph/contracts/services/sessions.py +87 -0
  29. aethergraph/contracts/services/state_stores.py +3 -0
  30. aethergraph/contracts/services/viz.py +44 -0
  31. aethergraph/contracts/storage/artifact_index.py +88 -0
  32. aethergraph/contracts/storage/artifact_store.py +99 -0
  33. aethergraph/contracts/storage/async_kv.py +34 -0
  34. aethergraph/contracts/storage/blob_store.py +50 -0
  35. aethergraph/contracts/storage/doc_store.py +35 -0
  36. aethergraph/contracts/storage/event_log.py +31 -0
  37. aethergraph/contracts/storage/vector_index.py +48 -0
  38. aethergraph/core/__init__.py +0 -0
  39. aethergraph/core/execution/forward_scheduler.py +13 -2
  40. aethergraph/core/execution/global_scheduler.py +21 -15
  41. aethergraph/core/execution/step_forward.py +10 -1
  42. aethergraph/core/graph/__init__.py +0 -0
  43. aethergraph/core/graph/graph_builder.py +8 -4
  44. aethergraph/core/graph/graph_fn.py +156 -15
  45. aethergraph/core/graph/graph_spec.py +8 -0
  46. aethergraph/core/graph/graphify.py +146 -27
  47. aethergraph/core/graph/node_spec.py +0 -2
  48. aethergraph/core/graph/node_state.py +3 -0
  49. aethergraph/core/graph/task_graph.py +39 -1
  50. aethergraph/core/runtime/__init__.py +0 -0
  51. aethergraph/core/runtime/ad_hoc_context.py +64 -4
  52. aethergraph/core/runtime/base_service.py +28 -4
  53. aethergraph/core/runtime/execution_context.py +13 -15
  54. aethergraph/core/runtime/graph_runner.py +222 -37
  55. aethergraph/core/runtime/node_context.py +510 -6
  56. aethergraph/core/runtime/node_services.py +12 -5
  57. aethergraph/core/runtime/recovery.py +15 -1
  58. aethergraph/core/runtime/run_manager.py +783 -0
  59. aethergraph/core/runtime/run_manager_local.py +204 -0
  60. aethergraph/core/runtime/run_registration.py +2 -2
  61. aethergraph/core/runtime/run_types.py +89 -0
  62. aethergraph/core/runtime/runtime_env.py +136 -7
  63. aethergraph/core/runtime/runtime_metering.py +71 -0
  64. aethergraph/core/runtime/runtime_registry.py +36 -13
  65. aethergraph/core/runtime/runtime_services.py +194 -6
  66. aethergraph/core/tools/builtins/toolset.py +1 -1
  67. aethergraph/core/tools/toolkit.py +5 -0
  68. aethergraph/plugins/agents/default_chat_agent copy.py +90 -0
  69. aethergraph/plugins/agents/default_chat_agent.py +171 -0
  70. aethergraph/plugins/agents/shared.py +81 -0
  71. aethergraph/plugins/channel/adapters/webui.py +112 -112
  72. aethergraph/plugins/channel/routes/webui_routes.py +367 -102
  73. aethergraph/plugins/channel/utils/slack_utils.py +115 -59
  74. aethergraph/plugins/channel/utils/telegram_utils.py +88 -47
  75. aethergraph/plugins/channel/websockets/weibui_ws.py +172 -0
  76. aethergraph/runtime/__init__.py +15 -0
  77. aethergraph/server/app_factory.py +196 -34
  78. aethergraph/server/clients/channel_client.py +202 -0
  79. aethergraph/server/http/channel_http_routes.py +116 -0
  80. aethergraph/server/http/channel_ws_routers.py +45 -0
  81. aethergraph/server/loading.py +117 -0
  82. aethergraph/server/server.py +131 -0
  83. aethergraph/server/server_state.py +240 -0
  84. aethergraph/server/start.py +227 -66
  85. aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  86. aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  87. aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  88. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  89. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  90. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  91. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  92. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  93. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  94. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  95. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  96. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  97. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  98. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  99. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  100. aethergraph/server/ui_static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  101. aethergraph/server/ui_static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  102. aethergraph/server/ui_static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  103. aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  104. aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  105. aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  106. aethergraph/server/ui_static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  107. aethergraph/server/ui_static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  108. aethergraph/server/ui_static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  109. aethergraph/server/ui_static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  110. aethergraph/server/ui_static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  111. aethergraph/server/ui_static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  112. aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  113. aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  114. aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  115. aethergraph/server/ui_static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  116. aethergraph/server/ui_static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  117. aethergraph/server/ui_static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  118. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  119. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  120. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  121. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  122. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  123. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  124. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  125. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  126. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  127. aethergraph/server/ui_static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  128. aethergraph/server/ui_static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  129. aethergraph/server/ui_static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  130. aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  131. aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  132. aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  133. aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  134. aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  135. aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  136. aethergraph/server/ui_static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  137. aethergraph/server/ui_static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  138. aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  139. aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  140. aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  141. aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  142. aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  143. aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  144. aethergraph/server/ui_static/assets/index-BR5GtXcZ.css +1 -0
  145. aethergraph/server/ui_static/assets/index-CQ0HZZ83.js +400 -0
  146. aethergraph/server/ui_static/index.html +15 -0
  147. aethergraph/server/ui_static/logo.png +0 -0
  148. aethergraph/services/artifacts/__init__.py +0 -0
  149. aethergraph/services/artifacts/facade.py +1239 -132
  150. aethergraph/services/auth/{dev.py → authn.py} +0 -8
  151. aethergraph/services/auth/authz.py +100 -0
  152. aethergraph/services/channel/__init__.py +0 -0
  153. aethergraph/services/channel/channel_bus.py +19 -1
  154. aethergraph/services/channel/factory.py +13 -1
  155. aethergraph/services/channel/ingress.py +311 -0
  156. aethergraph/services/channel/queue_adapter.py +75 -0
  157. aethergraph/services/channel/session.py +502 -19
  158. aethergraph/services/container/default_container.py +122 -43
  159. aethergraph/services/continuations/continuation.py +6 -0
  160. aethergraph/services/continuations/stores/fs_store.py +19 -0
  161. aethergraph/services/eventhub/event_hub.py +76 -0
  162. aethergraph/services/kv/__init__.py +0 -0
  163. aethergraph/services/kv/ephemeral.py +244 -0
  164. aethergraph/services/llm/__init__.py +0 -0
  165. aethergraph/services/llm/generic_client copy.py +691 -0
  166. aethergraph/services/llm/generic_client.py +1288 -187
  167. aethergraph/services/llm/providers.py +3 -1
  168. aethergraph/services/llm/types.py +47 -0
  169. aethergraph/services/llm/utils.py +284 -0
  170. aethergraph/services/logger/std.py +3 -0
  171. aethergraph/services/mcp/__init__.py +9 -0
  172. aethergraph/services/mcp/http_client.py +38 -0
  173. aethergraph/services/mcp/service.py +225 -1
  174. aethergraph/services/mcp/stdio_client.py +41 -6
  175. aethergraph/services/mcp/ws_client.py +44 -2
  176. aethergraph/services/memory/__init__.py +0 -0
  177. aethergraph/services/memory/distillers/llm_long_term.py +234 -0
  178. aethergraph/services/memory/distillers/llm_meta_summary.py +398 -0
  179. aethergraph/services/memory/distillers/long_term.py +225 -0
  180. aethergraph/services/memory/facade/__init__.py +3 -0
  181. aethergraph/services/memory/facade/chat.py +440 -0
  182. aethergraph/services/memory/facade/core.py +447 -0
  183. aethergraph/services/memory/facade/distillation.py +424 -0
  184. aethergraph/services/memory/facade/rag.py +410 -0
  185. aethergraph/services/memory/facade/results.py +315 -0
  186. aethergraph/services/memory/facade/retrieval.py +139 -0
  187. aethergraph/services/memory/facade/types.py +77 -0
  188. aethergraph/services/memory/facade/utils.py +43 -0
  189. aethergraph/services/memory/facade_dep.py +1539 -0
  190. aethergraph/services/memory/factory.py +9 -3
  191. aethergraph/services/memory/utils.py +10 -0
  192. aethergraph/services/metering/eventlog_metering.py +470 -0
  193. aethergraph/services/metering/noop.py +25 -4
  194. aethergraph/services/rag/__init__.py +0 -0
  195. aethergraph/services/rag/facade.py +279 -23
  196. aethergraph/services/rag/index_factory.py +2 -2
  197. aethergraph/services/rag/node_rag.py +317 -0
  198. aethergraph/services/rate_limit/inmem_rate_limit.py +24 -0
  199. aethergraph/services/registry/__init__.py +0 -0
  200. aethergraph/services/registry/agent_app_meta.py +419 -0
  201. aethergraph/services/registry/registry_key.py +1 -1
  202. aethergraph/services/registry/unified_registry.py +74 -6
  203. aethergraph/services/scope/scope.py +159 -0
  204. aethergraph/services/scope/scope_factory.py +164 -0
  205. aethergraph/services/state_stores/serialize.py +5 -0
  206. aethergraph/services/state_stores/utils.py +2 -1
  207. aethergraph/services/viz/__init__.py +0 -0
  208. aethergraph/services/viz/facade.py +413 -0
  209. aethergraph/services/viz/viz_service.py +69 -0
  210. aethergraph/storage/artifacts/artifact_index_jsonl.py +180 -0
  211. aethergraph/storage/artifacts/artifact_index_sqlite.py +426 -0
  212. aethergraph/storage/artifacts/cas_store.py +422 -0
  213. aethergraph/storage/artifacts/fs_cas.py +18 -0
  214. aethergraph/storage/artifacts/s3_cas.py +14 -0
  215. aethergraph/storage/artifacts/utils.py +124 -0
  216. aethergraph/storage/blob/fs_blob.py +86 -0
  217. aethergraph/storage/blob/s3_blob.py +115 -0
  218. aethergraph/storage/continuation_store/fs_cont.py +283 -0
  219. aethergraph/storage/continuation_store/inmem_cont.py +146 -0
  220. aethergraph/storage/continuation_store/kvdoc_cont.py +261 -0
  221. aethergraph/storage/docstore/fs_doc.py +63 -0
  222. aethergraph/storage/docstore/sqlite_doc.py +31 -0
  223. aethergraph/storage/docstore/sqlite_doc_sync.py +90 -0
  224. aethergraph/storage/eventlog/fs_event.py +136 -0
  225. aethergraph/storage/eventlog/sqlite_event.py +47 -0
  226. aethergraph/storage/eventlog/sqlite_event_sync.py +178 -0
  227. aethergraph/storage/factory.py +432 -0
  228. aethergraph/storage/fs_utils.py +28 -0
  229. aethergraph/storage/graph_state_store/state_store.py +64 -0
  230. aethergraph/storage/kv/inmem_kv.py +103 -0
  231. aethergraph/storage/kv/layered_kv.py +52 -0
  232. aethergraph/storage/kv/sqlite_kv.py +39 -0
  233. aethergraph/storage/kv/sqlite_kv_sync.py +98 -0
  234. aethergraph/storage/memory/event_persist.py +68 -0
  235. aethergraph/storage/memory/fs_persist.py +118 -0
  236. aethergraph/{services/memory/hotlog_kv.py → storage/memory/hotlog.py} +8 -2
  237. aethergraph/{services → storage}/memory/indices.py +31 -7
  238. aethergraph/storage/metering/meter_event.py +55 -0
  239. aethergraph/storage/runs/doc_store.py +280 -0
  240. aethergraph/storage/runs/inmen_store.py +82 -0
  241. aethergraph/storage/runs/sqlite_run_store.py +403 -0
  242. aethergraph/storage/sessions/doc_store.py +183 -0
  243. aethergraph/storage/sessions/inmem_store.py +110 -0
  244. aethergraph/storage/sessions/sqlite_session_store.py +399 -0
  245. aethergraph/storage/vector_index/chroma_index.py +138 -0
  246. aethergraph/storage/vector_index/faiss_index.py +179 -0
  247. aethergraph/storage/vector_index/sqlite_index.py +187 -0
  248. {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/METADATA +138 -31
  249. aethergraph-0.1.0a3.dist-info/RECORD +356 -0
  250. aethergraph-0.1.0a3.dist-info/entry_points.txt +3 -0
  251. aethergraph/services/artifacts/factory.py +0 -35
  252. aethergraph/services/artifacts/fs_store.py +0 -656
  253. aethergraph/services/artifacts/jsonl_index.py +0 -123
  254. aethergraph/services/artifacts/sqlite_index.py +0 -209
  255. aethergraph/services/memory/distillers/episode.py +0 -116
  256. aethergraph/services/memory/distillers/rolling.py +0 -74
  257. aethergraph/services/memory/facade.py +0 -633
  258. aethergraph/services/memory/persist_fs.py +0 -40
  259. aethergraph/services/rag/index/base.py +0 -27
  260. aethergraph/services/rag/index/faiss_index.py +0 -121
  261. aethergraph/services/rag/index/sqlite_index.py +0 -134
  262. aethergraph-0.1.0a1.dist-info/RECORD +0 -182
  263. aethergraph-0.1.0a1.dist-info/entry_points.txt +0 -2
  264. {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/WHEEL +0 -0
  265. {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/licenses/LICENSE +0 -0
  266. {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/licenses/NOTICE +0 -0
  267. {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,280 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from dataclasses import asdict
5
+ from datetime import datetime
6
+ from typing import Any
7
+
8
+ from aethergraph.contracts.services.runs import RunStore
9
+ from aethergraph.contracts.storage.doc_store import DocStore
10
+ from aethergraph.core.runtime.run_types import (
11
+ RunImportance,
12
+ RunOrigin,
13
+ RunRecord,
14
+ RunStatus,
15
+ RunVisibility,
16
+ )
17
+
18
+ # Generic DocStore-backed RunStore implementation
19
+
20
+
21
+ def _encode_dt(dt: datetime | None) -> str | None:
22
+ if dt is None:
23
+ return None
24
+ # ISO-8601 string; JSON friendly
25
+ return dt.isoformat()
26
+
27
+
28
+ def _decode_dt(raw: Any) -> datetime | None:
29
+ if raw is None:
30
+ return None
31
+ if isinstance(raw, datetime):
32
+ return raw
33
+ try:
34
+ return datetime.fromisoformat(str(raw))
35
+ except Exception:
36
+ return None
37
+
38
+
39
+ def _encode_status(status: RunStatus) -> str:
40
+ """
41
+ Store RunStatus as its plain value ("running").
42
+ """
43
+ if isinstance(status, RunStatus):
44
+ return status.value
45
+ # tolerate weird callers, but normalize via str
46
+ s = str(status)
47
+ if s.startswith("RunStatus."):
48
+ return s.split(".", 1)[1]
49
+ return s
50
+
51
+
52
+ def _decode_status(raw: Any) -> RunStatus:
53
+ """
54
+ Decode status from:
55
+ - RunStatus enum
56
+ - "running"
57
+ - "RunStatus.running"
58
+ """
59
+ if raw is None:
60
+ # If we really want to be strict, we can raise instead. For now default:
61
+ return RunStatus.pending
62
+
63
+ if isinstance(raw, RunStatus):
64
+ return raw
65
+
66
+ s = str(raw)
67
+ if s.startswith("RunStatus."):
68
+ s = s.split(".", 1)[1]
69
+
70
+ # This will raise ValueError if s is invalid, which is fine:
71
+ return RunStatus(s)
72
+
73
+
74
+ def _encode_origin(origin: RunOrigin | None) -> str | None:
75
+ if origin is None:
76
+ return None
77
+ if isinstance(origin, RunOrigin):
78
+ return origin.value
79
+ s = str(origin)
80
+ if s.startswith("RunOrigin."):
81
+ return s.split(".", 1)[1]
82
+ return s
83
+
84
+
85
+ def _decode_origin(raw: Any) -> RunOrigin:
86
+ if raw is None:
87
+ # sensible default
88
+ return RunOrigin.app
89
+ if isinstance(raw, RunOrigin):
90
+ return raw
91
+ s = str(raw)
92
+ if s.startswith("RunOrigin."):
93
+ s = s.split(".", 1)[1]
94
+ return RunOrigin(s)
95
+
96
+
97
+ def _encode_visibility(visibility: RunVisibility | None) -> str | None:
98
+ if visibility is None:
99
+ return None
100
+ if isinstance(visibility, RunVisibility):
101
+ return visibility.value
102
+ s = str(visibility)
103
+ if s.startswith("RunVisibility."):
104
+ return s.split(".", 1)[1]
105
+ return s
106
+
107
+
108
+ def _decode_visibility(raw: Any) -> RunVisibility:
109
+ if raw is None:
110
+ return RunVisibility.normal
111
+ if isinstance(raw, RunVisibility):
112
+ return raw
113
+ s = str(raw)
114
+ if s.startswith("RunVisibility."):
115
+ s = s.split(".", 1)[1]
116
+ return RunVisibility(s)
117
+
118
+
119
+ def _encode_importance(importance: RunImportance | None) -> str | None:
120
+ if importance is None:
121
+ return None
122
+ if isinstance(importance, RunImportance):
123
+ return importance.value
124
+ s = str(importance)
125
+ if s.startswith("RunImportance."):
126
+ return s.split(".", 1)[1]
127
+ return s
128
+
129
+
130
+ def _decode_importance(raw: Any) -> RunImportance:
131
+ if raw is None:
132
+ return RunImportance.normal
133
+ if isinstance(raw, RunImportance):
134
+ return raw
135
+ s = str(raw)
136
+ if s.startswith("RunImportance."):
137
+ s = s.split(".", 1)[1]
138
+ return RunImportance(s)
139
+
140
+
141
+ def _runrecord_to_doc(record: RunRecord) -> dict[str, Any]:
142
+ d = asdict(record)
143
+ d["status"] = _encode_status(record.status)
144
+ d["started_at"] = _encode_dt(record.started_at)
145
+ d["finished_at"] = _encode_dt(record.finished_at)
146
+ d["origin"] = _encode_origin(record.origin)
147
+ d["visibility"] = _encode_visibility(record.visibility)
148
+ d["importance"] = _encode_importance(record.importance)
149
+ return d
150
+
151
+
152
+ def _doc_to_runrecord(doc: dict[str, Any]) -> RunRecord:
153
+ return RunRecord(
154
+ run_id=doc["run_id"],
155
+ graph_id=doc["graph_id"],
156
+ kind=doc.get("kind", "other"),
157
+ status=_decode_status(doc.get("status")),
158
+ started_at=_decode_dt(doc.get("started_at")) or datetime.utcnow(),
159
+ finished_at=_decode_dt(doc.get("finished_at")),
160
+ tags=list(doc.get("tags") or []),
161
+ user_id=doc.get("user_id"),
162
+ org_id=doc.get("org_id"),
163
+ error=doc.get("error"),
164
+ meta=dict(doc.get("meta") or {}),
165
+ session_id=doc.get("session_id"),
166
+ origin=_decode_origin(doc.get("origin")),
167
+ visibility=_decode_visibility(doc.get("visibility")),
168
+ importance=_decode_importance(doc.get("importance")),
169
+ app_id=doc.get("app_id"),
170
+ agent_id=doc.get("agent_id"),
171
+ )
172
+
173
+
174
+ class DocRunStore(RunStore):
175
+ """
176
+ RunStore backed by an arbitrary DocStore.
177
+
178
+ - Uses doc IDs like "<prefix><run_id>" (prefix defaults to "run:").
179
+ - Persists RunRecord as JSON-friendly dicts (ISO datetimes, status as string).
180
+ - Supports FS-backed or SQLite-backed DocStore transparently.
181
+
182
+ The only requirement is that the underlying DocStore implements `list()`
183
+ if you want `RunStore.list()` to work.
184
+ """
185
+
186
+ def __init__(self, doc_store: DocStore, *, prefix: str = "run:") -> None:
187
+ self._ds = doc_store
188
+ self._prefix = prefix
189
+ self._lock = asyncio.Lock()
190
+
191
+ def _doc_id(self, run_id: str) -> str:
192
+ return f"{self._prefix}{run_id}"
193
+
194
+ async def create(self, record: RunRecord) -> None:
195
+ doc_id = self._doc_id(record.run_id)
196
+ doc = _runrecord_to_doc(record)
197
+ async with self._lock:
198
+ await self._ds.put(doc_id, doc)
199
+
200
+ async def update_status(
201
+ self,
202
+ run_id: str,
203
+ status: RunStatus,
204
+ *,
205
+ finished_at: datetime | None = None,
206
+ error: str | None = None,
207
+ ) -> None:
208
+ doc_id = self._doc_id(run_id)
209
+ async with self._lock:
210
+ doc = await self._ds.get(doc_id)
211
+ if doc is None:
212
+ # You could choose to create a minimal record here instead.
213
+ return
214
+
215
+ doc["status"] = _encode_status(status)
216
+ if finished_at is not None:
217
+ doc["finished_at"] = _encode_dt(finished_at)
218
+ if error is not None:
219
+ doc["error"] = error
220
+
221
+ await self._ds.put(doc_id, doc)
222
+
223
+ async def get(self, run_id: str) -> RunRecord | None:
224
+ doc_id = self._doc_id(run_id)
225
+ async with self._lock:
226
+ doc = await self._ds.get(doc_id)
227
+ if doc is None:
228
+ return None
229
+ return _doc_to_runrecord(doc)
230
+
231
+ async def list(
232
+ self,
233
+ *,
234
+ graph_id: str | None = None,
235
+ status: RunStatus | None = None,
236
+ session_id: str | None = None,
237
+ limit: int = 100,
238
+ offset: int = 0,
239
+ ) -> list[RunRecord]:
240
+ # NOTE: This implementation is fine for small/medium numbers of runs, but it:
241
+ # - Calls DocStore.list() to load ALL doc_ids
242
+ # - Loads each run doc and filters in Python
243
+ # - Sorts in memory by started_at
244
+ # For large volumes / multi-tenant cloud use, replace this with a real DB-backed
245
+ # RunStore that pushes filtering + sorting + LIMIT/OFFSET (or keyset) into SQL.
246
+ if not hasattr(self._ds, "list"):
247
+ raise RuntimeError(
248
+ "Underlying DocStore does not implement list(); " "cannot support RunStore.list()."
249
+ )
250
+
251
+ async with self._lock:
252
+ doc_ids: list[str] = await self._ds.list() # type: ignore[attr-defined]
253
+ # Only consider docs under our prefix
254
+ doc_ids = [d for d in doc_ids if d.startswith(self._prefix)]
255
+
256
+ records: list[RunRecord] = []
257
+ for doc_id in doc_ids:
258
+ doc = await self._ds.get(doc_id)
259
+ if not doc:
260
+ continue
261
+ rec = _doc_to_runrecord(doc)
262
+
263
+ if graph_id is not None and rec.graph_id != graph_id:
264
+ continue
265
+ if status is not None and rec.status != status:
266
+ continue
267
+ if session_id is not None and rec.session_id != session_id:
268
+ continue
269
+
270
+ records.append(rec)
271
+
272
+ # Sort newest first, then truncate
273
+ records.sort(key=lambda r: r.started_at, reverse=True)
274
+
275
+ # apply offset
276
+ if offset > 0:
277
+ records = records[offset:]
278
+ if limit is not None:
279
+ records = records[:limit]
280
+ return records
@@ -0,0 +1,82 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from dataclasses import asdict
5
+ from datetime import datetime
6
+
7
+ from aethergraph.contracts.services.runs import RunStore
8
+ from aethergraph.core.runtime.run_types import RunRecord, RunStatus
9
+
10
+
11
+ class InMemoryRunStore(RunStore):
12
+ """
13
+ Simple in-memory RunStore useful for sidecar/server default.
14
+
15
+ Not persisted across process restarts.
16
+ """
17
+
18
+ def __init__(self) -> None:
19
+ self._records: dict[str, RunRecord] = {}
20
+ self._lock = asyncio.Lock()
21
+
22
+ async def create(self, record: RunRecord) -> None:
23
+ async with self._lock:
24
+ self._records[record.run_id] = record
25
+
26
+ async def update_status(
27
+ self,
28
+ run_id: str,
29
+ status: RunStatus,
30
+ *,
31
+ finished_at: datetime | None = None,
32
+ error: str | None = None,
33
+ ) -> None:
34
+ async with self._lock:
35
+ rec = self._records.get(run_id)
36
+ if rec is None:
37
+ # Optionally: create a minimal record; for now, just ignore.
38
+ return
39
+ rec.status = status
40
+ if finished_at is not None:
41
+ rec.finished_at = finished_at
42
+ if error is not None:
43
+ rec.error = error
44
+
45
+ async def get(self, run_id: str) -> RunRecord | None:
46
+ async with self._lock:
47
+ rec = self._records.get(run_id)
48
+ if rec is None:
49
+ return None
50
+ # return a deep copy to avoid external mutation of internal state
51
+ return RunRecord(**asdict(rec))
52
+
53
+ async def list(
54
+ self,
55
+ *,
56
+ graph_id: str | None = None,
57
+ status: RunStatus | None = None,
58
+ session_id: str | None = None,
59
+ limit: int = 100,
60
+ offset: int = 0,
61
+ ) -> list[RunRecord]:
62
+ # NOTE: InMemoryRunStore is for dev/sidecar/demo only.
63
+ # It scans all in-memory records and sorts in Python.
64
+ # Do NOT use this in any environment where run counts can grow large;
65
+ # prefer DocRunStore + SQLite/FS or a proper DB-backed RunStore.
66
+ async with self._lock:
67
+ records: list[RunRecord] = list(self._records.values())
68
+ if graph_id is not None:
69
+ records = [r for r in records if r.graph_id == graph_id]
70
+ if status is not None:
71
+ records = [r for r in records if r.status == status]
72
+ if session_id is not None:
73
+ records = [r for r in records if r.session_id == session_id]
74
+
75
+ records = sorted(records, key=lambda r: r.started_at, reverse=True)
76
+
77
+ if offset > 0:
78
+ records = records[offset:]
79
+ if limit is not None:
80
+ records = records[:limit]
81
+ # return copies
82
+ return [RunRecord(**asdict(r)) for r in records]