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.
- aethergraph/__init__.py +4 -10
- aethergraph/__main__.py +296 -0
- aethergraph/api/v1/__init__.py +0 -0
- aethergraph/api/v1/agents.py +46 -0
- aethergraph/api/v1/apps.py +70 -0
- aethergraph/api/v1/artifacts.py +415 -0
- aethergraph/api/v1/channels.py +89 -0
- aethergraph/api/v1/deps.py +168 -0
- aethergraph/api/v1/graphs.py +259 -0
- aethergraph/api/v1/identity.py +25 -0
- aethergraph/api/v1/memory.py +353 -0
- aethergraph/api/v1/misc.py +47 -0
- aethergraph/api/v1/pagination.py +29 -0
- aethergraph/api/v1/runs.py +568 -0
- aethergraph/api/v1/schemas.py +535 -0
- aethergraph/api/v1/session.py +323 -0
- aethergraph/api/v1/stats.py +201 -0
- aethergraph/api/v1/viz.py +152 -0
- aethergraph/config/config.py +22 -0
- aethergraph/config/loader.py +3 -2
- aethergraph/config/storage.py +209 -0
- aethergraph/contracts/__init__.py +0 -0
- aethergraph/contracts/services/__init__.py +0 -0
- aethergraph/contracts/services/artifacts.py +27 -14
- aethergraph/contracts/services/memory.py +45 -17
- aethergraph/contracts/services/metering.py +129 -0
- aethergraph/contracts/services/runs.py +50 -0
- aethergraph/contracts/services/sessions.py +87 -0
- aethergraph/contracts/services/state_stores.py +3 -0
- aethergraph/contracts/services/viz.py +44 -0
- aethergraph/contracts/storage/artifact_index.py +88 -0
- aethergraph/contracts/storage/artifact_store.py +99 -0
- aethergraph/contracts/storage/async_kv.py +34 -0
- aethergraph/contracts/storage/blob_store.py +50 -0
- aethergraph/contracts/storage/doc_store.py +35 -0
- aethergraph/contracts/storage/event_log.py +31 -0
- aethergraph/contracts/storage/vector_index.py +48 -0
- aethergraph/core/__init__.py +0 -0
- aethergraph/core/execution/forward_scheduler.py +13 -2
- aethergraph/core/execution/global_scheduler.py +21 -15
- aethergraph/core/execution/step_forward.py +10 -1
- aethergraph/core/graph/__init__.py +0 -0
- aethergraph/core/graph/graph_builder.py +8 -4
- aethergraph/core/graph/graph_fn.py +156 -15
- aethergraph/core/graph/graph_spec.py +8 -0
- aethergraph/core/graph/graphify.py +146 -27
- aethergraph/core/graph/node_spec.py +0 -2
- aethergraph/core/graph/node_state.py +3 -0
- aethergraph/core/graph/task_graph.py +39 -1
- aethergraph/core/runtime/__init__.py +0 -0
- aethergraph/core/runtime/ad_hoc_context.py +64 -4
- aethergraph/core/runtime/base_service.py +28 -4
- aethergraph/core/runtime/execution_context.py +13 -15
- aethergraph/core/runtime/graph_runner.py +222 -37
- aethergraph/core/runtime/node_context.py +510 -6
- aethergraph/core/runtime/node_services.py +12 -5
- aethergraph/core/runtime/recovery.py +15 -1
- aethergraph/core/runtime/run_manager.py +783 -0
- aethergraph/core/runtime/run_manager_local.py +204 -0
- aethergraph/core/runtime/run_registration.py +2 -2
- aethergraph/core/runtime/run_types.py +89 -0
- aethergraph/core/runtime/runtime_env.py +136 -7
- aethergraph/core/runtime/runtime_metering.py +71 -0
- aethergraph/core/runtime/runtime_registry.py +36 -13
- aethergraph/core/runtime/runtime_services.py +194 -6
- aethergraph/core/tools/builtins/toolset.py +1 -1
- aethergraph/core/tools/toolkit.py +5 -0
- aethergraph/plugins/agents/default_chat_agent copy.py +90 -0
- aethergraph/plugins/agents/default_chat_agent.py +171 -0
- aethergraph/plugins/agents/shared.py +81 -0
- aethergraph/plugins/channel/adapters/webui.py +112 -112
- aethergraph/plugins/channel/routes/webui_routes.py +367 -102
- aethergraph/plugins/channel/utils/slack_utils.py +115 -59
- aethergraph/plugins/channel/utils/telegram_utils.py +88 -47
- aethergraph/plugins/channel/websockets/weibui_ws.py +172 -0
- aethergraph/runtime/__init__.py +15 -0
- aethergraph/server/app_factory.py +196 -34
- aethergraph/server/clients/channel_client.py +202 -0
- aethergraph/server/http/channel_http_routes.py +116 -0
- aethergraph/server/http/channel_ws_routers.py +45 -0
- aethergraph/server/loading.py +117 -0
- aethergraph/server/server.py +131 -0
- aethergraph/server/server_state.py +240 -0
- aethergraph/server/start.py +227 -66
- aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- aethergraph/server/ui_static/assets/index-BR5GtXcZ.css +1 -0
- aethergraph/server/ui_static/assets/index-CQ0HZZ83.js +400 -0
- aethergraph/server/ui_static/index.html +15 -0
- aethergraph/server/ui_static/logo.png +0 -0
- aethergraph/services/artifacts/__init__.py +0 -0
- aethergraph/services/artifacts/facade.py +1239 -132
- aethergraph/services/auth/{dev.py → authn.py} +0 -8
- aethergraph/services/auth/authz.py +100 -0
- aethergraph/services/channel/__init__.py +0 -0
- aethergraph/services/channel/channel_bus.py +19 -1
- aethergraph/services/channel/factory.py +13 -1
- aethergraph/services/channel/ingress.py +311 -0
- aethergraph/services/channel/queue_adapter.py +75 -0
- aethergraph/services/channel/session.py +502 -19
- aethergraph/services/container/default_container.py +122 -43
- aethergraph/services/continuations/continuation.py +6 -0
- aethergraph/services/continuations/stores/fs_store.py +19 -0
- aethergraph/services/eventhub/event_hub.py +76 -0
- aethergraph/services/kv/__init__.py +0 -0
- aethergraph/services/kv/ephemeral.py +244 -0
- aethergraph/services/llm/__init__.py +0 -0
- aethergraph/services/llm/generic_client copy.py +691 -0
- aethergraph/services/llm/generic_client.py +1288 -187
- aethergraph/services/llm/providers.py +3 -1
- aethergraph/services/llm/types.py +47 -0
- aethergraph/services/llm/utils.py +284 -0
- aethergraph/services/logger/std.py +3 -0
- aethergraph/services/mcp/__init__.py +9 -0
- aethergraph/services/mcp/http_client.py +38 -0
- aethergraph/services/mcp/service.py +225 -1
- aethergraph/services/mcp/stdio_client.py +41 -6
- aethergraph/services/mcp/ws_client.py +44 -2
- aethergraph/services/memory/__init__.py +0 -0
- aethergraph/services/memory/distillers/llm_long_term.py +234 -0
- aethergraph/services/memory/distillers/llm_meta_summary.py +398 -0
- aethergraph/services/memory/distillers/long_term.py +225 -0
- aethergraph/services/memory/facade/__init__.py +3 -0
- aethergraph/services/memory/facade/chat.py +440 -0
- aethergraph/services/memory/facade/core.py +447 -0
- aethergraph/services/memory/facade/distillation.py +424 -0
- aethergraph/services/memory/facade/rag.py +410 -0
- aethergraph/services/memory/facade/results.py +315 -0
- aethergraph/services/memory/facade/retrieval.py +139 -0
- aethergraph/services/memory/facade/types.py +77 -0
- aethergraph/services/memory/facade/utils.py +43 -0
- aethergraph/services/memory/facade_dep.py +1539 -0
- aethergraph/services/memory/factory.py +9 -3
- aethergraph/services/memory/utils.py +10 -0
- aethergraph/services/metering/eventlog_metering.py +470 -0
- aethergraph/services/metering/noop.py +25 -4
- aethergraph/services/rag/__init__.py +0 -0
- aethergraph/services/rag/facade.py +279 -23
- aethergraph/services/rag/index_factory.py +2 -2
- aethergraph/services/rag/node_rag.py +317 -0
- aethergraph/services/rate_limit/inmem_rate_limit.py +24 -0
- aethergraph/services/registry/__init__.py +0 -0
- aethergraph/services/registry/agent_app_meta.py +419 -0
- aethergraph/services/registry/registry_key.py +1 -1
- aethergraph/services/registry/unified_registry.py +74 -6
- aethergraph/services/scope/scope.py +159 -0
- aethergraph/services/scope/scope_factory.py +164 -0
- aethergraph/services/state_stores/serialize.py +5 -0
- aethergraph/services/state_stores/utils.py +2 -1
- aethergraph/services/viz/__init__.py +0 -0
- aethergraph/services/viz/facade.py +413 -0
- aethergraph/services/viz/viz_service.py +69 -0
- aethergraph/storage/artifacts/artifact_index_jsonl.py +180 -0
- aethergraph/storage/artifacts/artifact_index_sqlite.py +426 -0
- aethergraph/storage/artifacts/cas_store.py +422 -0
- aethergraph/storage/artifacts/fs_cas.py +18 -0
- aethergraph/storage/artifacts/s3_cas.py +14 -0
- aethergraph/storage/artifacts/utils.py +124 -0
- aethergraph/storage/blob/fs_blob.py +86 -0
- aethergraph/storage/blob/s3_blob.py +115 -0
- aethergraph/storage/continuation_store/fs_cont.py +283 -0
- aethergraph/storage/continuation_store/inmem_cont.py +146 -0
- aethergraph/storage/continuation_store/kvdoc_cont.py +261 -0
- aethergraph/storage/docstore/fs_doc.py +63 -0
- aethergraph/storage/docstore/sqlite_doc.py +31 -0
- aethergraph/storage/docstore/sqlite_doc_sync.py +90 -0
- aethergraph/storage/eventlog/fs_event.py +136 -0
- aethergraph/storage/eventlog/sqlite_event.py +47 -0
- aethergraph/storage/eventlog/sqlite_event_sync.py +178 -0
- aethergraph/storage/factory.py +432 -0
- aethergraph/storage/fs_utils.py +28 -0
- aethergraph/storage/graph_state_store/state_store.py +64 -0
- aethergraph/storage/kv/inmem_kv.py +103 -0
- aethergraph/storage/kv/layered_kv.py +52 -0
- aethergraph/storage/kv/sqlite_kv.py +39 -0
- aethergraph/storage/kv/sqlite_kv_sync.py +98 -0
- aethergraph/storage/memory/event_persist.py +68 -0
- aethergraph/storage/memory/fs_persist.py +118 -0
- aethergraph/{services/memory/hotlog_kv.py → storage/memory/hotlog.py} +8 -2
- aethergraph/{services → storage}/memory/indices.py +31 -7
- aethergraph/storage/metering/meter_event.py +55 -0
- aethergraph/storage/runs/doc_store.py +280 -0
- aethergraph/storage/runs/inmen_store.py +82 -0
- aethergraph/storage/runs/sqlite_run_store.py +403 -0
- aethergraph/storage/sessions/doc_store.py +183 -0
- aethergraph/storage/sessions/inmem_store.py +110 -0
- aethergraph/storage/sessions/sqlite_session_store.py +399 -0
- aethergraph/storage/vector_index/chroma_index.py +138 -0
- aethergraph/storage/vector_index/faiss_index.py +179 -0
- aethergraph/storage/vector_index/sqlite_index.py +187 -0
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/METADATA +138 -31
- aethergraph-0.1.0a3.dist-info/RECORD +356 -0
- aethergraph-0.1.0a3.dist-info/entry_points.txt +3 -0
- aethergraph/services/artifacts/factory.py +0 -35
- aethergraph/services/artifacts/fs_store.py +0 -656
- aethergraph/services/artifacts/jsonl_index.py +0 -123
- aethergraph/services/artifacts/sqlite_index.py +0 -209
- aethergraph/services/memory/distillers/episode.py +0 -116
- aethergraph/services/memory/distillers/rolling.py +0 -74
- aethergraph/services/memory/facade.py +0 -633
- aethergraph/services/memory/persist_fs.py +0 -40
- aethergraph/services/rag/index/base.py +0 -27
- aethergraph/services/rag/index/faiss_index.py +0 -121
- aethergraph/services/rag/index/sqlite_index.py +0 -134
- aethergraph-0.1.0a1.dist-info/RECORD +0 -182
- aethergraph-0.1.0a1.dist-info/entry_points.txt +0 -2
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/WHEEL +0 -0
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/licenses/LICENSE +0 -0
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/licenses/NOTICE +0 -0
- {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]
|