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
aethergraph/__init__.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
__version__ = "0.1.
|
|
1
|
+
__version__ = "0.1.0a2"
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
|
|
4
|
-
# Server
|
|
5
|
-
# Channel buttons
|
|
6
5
|
from .contracts.services.channel import Button
|
|
7
6
|
|
|
8
7
|
# Graphs
|
|
@@ -24,6 +23,8 @@ from .server.start import (
|
|
|
24
23
|
stop_server, # stop the sidecar server
|
|
25
24
|
)
|
|
26
25
|
|
|
26
|
+
logging.getLogger("aethergraph").addHandler(logging.NullHandler())
|
|
27
|
+
|
|
27
28
|
__all__ = [
|
|
28
29
|
# Server
|
|
29
30
|
"start_server",
|
|
@@ -34,16 +35,9 @@ __all__ = [
|
|
|
34
35
|
"graph_fn",
|
|
35
36
|
"graphify",
|
|
36
37
|
"TaskGraph",
|
|
37
|
-
"RuntimeEnv",
|
|
38
38
|
"NodeContext",
|
|
39
39
|
# Services
|
|
40
40
|
"Service",
|
|
41
41
|
# Channel buttons
|
|
42
42
|
"Button",
|
|
43
43
|
]
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
# Setup a default null logger to avoid "No handler found" warnings
|
|
47
|
-
import logging
|
|
48
|
-
|
|
49
|
-
logging.getLogger("aethergraph").addHandler(logging.NullHandler())
|
aethergraph/__main__.py
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# aethergraph/__main__.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
|
|
10
|
+
import uvicorn
|
|
11
|
+
|
|
12
|
+
from aethergraph.config.context import set_current_settings
|
|
13
|
+
from aethergraph.config.loader import load_settings
|
|
14
|
+
from aethergraph.server.app_factory import create_app
|
|
15
|
+
from aethergraph.server.loading import GraphLoader, LoadSpec
|
|
16
|
+
from aethergraph.server.server_state import (
|
|
17
|
+
get_running_url_if_any,
|
|
18
|
+
pick_free_port,
|
|
19
|
+
workspace_lock,
|
|
20
|
+
write_server_state,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
AetherGraph CLI (Phase 1)
|
|
25
|
+
|
|
26
|
+
Goal: run the sidecar persistently as a long-lived process.
|
|
27
|
+
|
|
28
|
+
Why:
|
|
29
|
+
- Your workspace stores persistent data (runs/artifacts/memory/sessions).
|
|
30
|
+
- The server process must stay alive for the frontend/Electron to call the API repeatedly.
|
|
31
|
+
- When port=0 (auto free port), the actual URL changes per start.
|
|
32
|
+
We write workspace/.aethergraph/server.json so the UI can discover the URL without
|
|
33
|
+
hardcoding ports or parsing stdout.
|
|
34
|
+
|
|
35
|
+
Commands:
|
|
36
|
+
|
|
37
|
+
1) Start the sidecar (blocking, recommended for "always-on" local server)
|
|
38
|
+
python -m aethergraph serve --workspace ./aethergraph_data --port 0 \
|
|
39
|
+
--project-root . \
|
|
40
|
+
--load-path ./graphs.py
|
|
41
|
+
|
|
42
|
+
Notes:
|
|
43
|
+
- --port 0 auto-picks a free port and prints the resulting URL.
|
|
44
|
+
- --load-path / --load-module imports user code BEFORE the server starts,
|
|
45
|
+
so decorated graphs/apps/agents appear immediately in the UI.
|
|
46
|
+
- --project-root is temporarily added to sys.path during loading (for local imports).
|
|
47
|
+
- server.json is written under the workspace for discovery.
|
|
48
|
+
|
|
49
|
+
2) Reuse detection (avoid starting multiple servers for the same workspace)
|
|
50
|
+
python -m aethergraph serve --workspace ./aethergraph_data --reuse
|
|
51
|
+
|
|
52
|
+
Behavior:
|
|
53
|
+
- If a server for this workspace is already running, print its URL and exit 0.
|
|
54
|
+
- If not running, starts a new server.
|
|
55
|
+
|
|
56
|
+
Recommended desktop/Electron workflow:
|
|
57
|
+
- Electron chooses a workspace folder.
|
|
58
|
+
- Electron checks workspace/.aethergraph/server.json and tries to connect.
|
|
59
|
+
- If missing/dead, Electron spawns:
|
|
60
|
+
python -m aethergraph serve --workspace <workspace> --port 0 --load-path <graphs.py> ...
|
|
61
|
+
- Electron reads server.json to get the URL and connects.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def main(argv: list[str] | None = None) -> int:
|
|
66
|
+
"""
|
|
67
|
+
Start the AetherGraph server via CLI.
|
|
68
|
+
|
|
69
|
+
This entrypoint launches the persistent sidecar server for your workspace,
|
|
70
|
+
enabling API access for frontend/UI clients. It supports automatic
|
|
71
|
+
port selection, workspace isolation, and dynamic loading of user graphs/apps.
|
|
72
|
+
|
|
73
|
+
Examples:
|
|
74
|
+
Basic usage with default workspace and port:
|
|
75
|
+
```bash
|
|
76
|
+
python -m aethergraph serve --workspace # only default agents/apps show up
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
load user graphs from a file and autoreload on changes:
|
|
80
|
+
```bash
|
|
81
|
+
python -m aethergraph serve --load-path ./graphs.py --reload
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Load multiple modules and set a custom project root:
|
|
85
|
+
```bash
|
|
86
|
+
python -m aethergraph serve --load-module mygraphs --project-root .
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Reuse detection (print URL if already running):
|
|
90
|
+
```bash
|
|
91
|
+
python -m aethergraph serve --reuse
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Customize workspace and port:
|
|
95
|
+
```bash
|
|
96
|
+
python -m aethergraph serve --workspace ./my_workspace --port 8000 # this will not show previous runs/artifacts unless reused
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
argv: Optional list of CLI arguments. If None, uses sys.argv[1:].
|
|
101
|
+
|
|
102
|
+
Required keywords:
|
|
103
|
+
- `serve`: Command to start the AetherGraph server. If no other command is given, the server will only load default built-in agents/apps.
|
|
104
|
+
|
|
105
|
+
Optional keywords:
|
|
106
|
+
- `workspace`: Path to the workspace folder (default: ./aethergraph_data).
|
|
107
|
+
- `host`: Host address to bind (default: 127.0.0.1).
|
|
108
|
+
- `port`: Port to bind (default: 8745; use 0 for auto-pick).
|
|
109
|
+
- `log-level`: App log level (default: warning).
|
|
110
|
+
- `uvicorn-log-level`: Uvicorn log level (default: warning).
|
|
111
|
+
- `project-root`: Temporarily added to sys.path for local imports.
|
|
112
|
+
- `load-module`: Python module(s) to import before server starts (repeatable).
|
|
113
|
+
- `load-path`: Python file(s) to load before server starts (repeatable).
|
|
114
|
+
- `strict-load`: Raise error if graph loading fails.
|
|
115
|
+
- `reuse`: If server already running for workspace, print URL and exit.
|
|
116
|
+
- `reload`: Enable auto-reload (dev mode).
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
int: Exit code (0 for success, 2 for unknown command).
|
|
120
|
+
|
|
121
|
+
Notes:
|
|
122
|
+
- Launching the server via CLI keeps it running persistently for API clients to connect like AetherGraph UI.
|
|
123
|
+
- In local mode, the server port will automatically be consistent with UI connections.
|
|
124
|
+
- use `--reload` for development to auto-restart on code changes. This will use uvicorn's reload feature.
|
|
125
|
+
- When switching ports, the UI will not show previous runs/artifacts unless the server is reused. This is
|
|
126
|
+
because the server URL is tied to the frontend hash. Keep the server in a same port (default 8745) for local dev.
|
|
127
|
+
Later the UI can support dynamic port discovery via server.json.
|
|
128
|
+
"""
|
|
129
|
+
argv = argv if argv is not None else sys.argv[1:]
|
|
130
|
+
|
|
131
|
+
parser = argparse.ArgumentParser(prog="aethergraph")
|
|
132
|
+
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
133
|
+
|
|
134
|
+
serve = sub.add_parser("serve", help="Run the AetherGraph sidecar (blocking).")
|
|
135
|
+
serve.add_argument("--workspace", default="./aethergraph_data")
|
|
136
|
+
serve.add_argument("--host", default="127.0.0.1")
|
|
137
|
+
serve.add_argument("--port", type=int, default=8745, help="0 = auto free port")
|
|
138
|
+
serve.add_argument("--log-level", default="warning")
|
|
139
|
+
serve.add_argument("--uvicorn-log-level", default="info")
|
|
140
|
+
|
|
141
|
+
serve.add_argument(
|
|
142
|
+
"--project-root",
|
|
143
|
+
default=".",
|
|
144
|
+
help="Root directory for the project. Added to sys.path while loading user graphs.",
|
|
145
|
+
)
|
|
146
|
+
serve.add_argument(
|
|
147
|
+
"--load-module", action="append", default=[], help="Module to import (repeatable)."
|
|
148
|
+
)
|
|
149
|
+
serve.add_argument(
|
|
150
|
+
"--load-path", action="append", default=[], help="Python file path to load (repeatable)."
|
|
151
|
+
)
|
|
152
|
+
serve.add_argument("--strict-load", action="store_true", help="Raise if graph loading fails.")
|
|
153
|
+
|
|
154
|
+
serve.add_argument(
|
|
155
|
+
"--reuse",
|
|
156
|
+
action="store_true",
|
|
157
|
+
help="If server already running for workspace, print URL and exit 0.",
|
|
158
|
+
)
|
|
159
|
+
serve.add_argument(
|
|
160
|
+
"--reload",
|
|
161
|
+
action="store_true",
|
|
162
|
+
help="Enable auto-reload on code changes (dev only).",
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
args = parser.parse_args(argv)
|
|
166
|
+
print(args)
|
|
167
|
+
|
|
168
|
+
if args.cmd == "serve":
|
|
169
|
+
loader = GraphLoader()
|
|
170
|
+
|
|
171
|
+
# Ensure one workspace => one server process
|
|
172
|
+
with workspace_lock(args.workspace):
|
|
173
|
+
running = get_running_url_if_any(args.workspace)
|
|
174
|
+
if running:
|
|
175
|
+
if args.reuse:
|
|
176
|
+
print(running)
|
|
177
|
+
return 0
|
|
178
|
+
print(f"Already running for workspace: {running}")
|
|
179
|
+
return 0
|
|
180
|
+
|
|
181
|
+
# Load graphs BEFORE app starts
|
|
182
|
+
project_root = args.project_root
|
|
183
|
+
modules = list(args.load_module or [])
|
|
184
|
+
paths = list(args.load_path or [])
|
|
185
|
+
spec = LoadSpec(
|
|
186
|
+
modules=list(args.load_module or []),
|
|
187
|
+
paths=list(args.load_path or []),
|
|
188
|
+
project_root=args.project_root,
|
|
189
|
+
strict=bool(args.strict_load),
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# Export them to environment so the worker factory can read them
|
|
193
|
+
os.environ["AETHERGRAPH_WORKSPACE"] = args.workspace
|
|
194
|
+
os.environ.setdefault(
|
|
195
|
+
"AETHERGRAPH_ROOT", args.workspace
|
|
196
|
+
) # AETHERGRAPH_ROOT is the workspace root in env
|
|
197
|
+
os.environ["AETHERGRAPH_PROJECT_ROOT"] = str(project_root)
|
|
198
|
+
os.environ["AETHERGRAPH_LOAD_MODULES"] = ",".join(modules)
|
|
199
|
+
os.environ["AETHERGRAPH_LOAD_PATHS"] = os.pathsep.join(paths)
|
|
200
|
+
os.environ["AETHERGRAPH_STRICT_LOAD"] = "1" if args.strict_load else "0"
|
|
201
|
+
os.environ["AETHERGRAPH_LOG_LEVEL"] = args.log_level
|
|
202
|
+
|
|
203
|
+
print("=" * 50)
|
|
204
|
+
print("🔄 Loading graphs and agents...")
|
|
205
|
+
if spec.modules or spec.paths:
|
|
206
|
+
print(
|
|
207
|
+
"➕ Importing modules:",
|
|
208
|
+
spec.modules,
|
|
209
|
+
"and paths:",
|
|
210
|
+
spec.paths,
|
|
211
|
+
"at project root:",
|
|
212
|
+
spec.project_root,
|
|
213
|
+
)
|
|
214
|
+
report = loader.load(spec)
|
|
215
|
+
# Optional: print load errors but still continue if not strict
|
|
216
|
+
if report.errors and not args.strict_load:
|
|
217
|
+
for e in report.errors:
|
|
218
|
+
print(f"⚠️ [load error] {e.source}: {e.error}")
|
|
219
|
+
print(" (continuing despite load error; use --strict-load to fail)")
|
|
220
|
+
if e.traceback:
|
|
221
|
+
print(e.traceback)
|
|
222
|
+
print("✅ Graph/agents loading complete.")
|
|
223
|
+
print("=" * 50)
|
|
224
|
+
|
|
225
|
+
cfg = load_settings()
|
|
226
|
+
set_current_settings(cfg)
|
|
227
|
+
|
|
228
|
+
app = create_app(workspace=args.workspace, cfg=cfg, log_level=args.log_level)
|
|
229
|
+
app.state.last_load_report = getattr(loader, "last_report", None)
|
|
230
|
+
|
|
231
|
+
port = pick_free_port(int(args.port))
|
|
232
|
+
url = f"http://{args.host}:{port}"
|
|
233
|
+
|
|
234
|
+
# Write discovery file while we still hold the lock
|
|
235
|
+
write_server_state(
|
|
236
|
+
args.workspace,
|
|
237
|
+
{
|
|
238
|
+
"pid": os.getpid(),
|
|
239
|
+
"host": args.host,
|
|
240
|
+
"port": port,
|
|
241
|
+
"url": url,
|
|
242
|
+
"workspace": str(Path(args.workspace).resolve()),
|
|
243
|
+
"started_at": time.time(),
|
|
244
|
+
},
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
if not args.reload:
|
|
248
|
+
# Run blocking server (lock released so others can read server.json)
|
|
249
|
+
print("\n" + "=" * 50)
|
|
250
|
+
# We align the labels to 18 characters (the length of the longest label)
|
|
251
|
+
print(f"[AetherGraph] 🚀 {'Server started at:':<18} {url}")
|
|
252
|
+
print(
|
|
253
|
+
f"[AetherGraph] 🖥️ {'UI:':<18} {url}/ui (if built)"
|
|
254
|
+
) # strangly, this needs two spaces unlike the rest
|
|
255
|
+
print(f"[AetherGraph] 📡 {'API:':<18} {url}/api/v1/")
|
|
256
|
+
print(f"[AetherGraph] 📂 {'Workspace:':<18} {args.workspace}")
|
|
257
|
+
print("=" * 50 + "\n")
|
|
258
|
+
uvicorn.run(
|
|
259
|
+
app,
|
|
260
|
+
host=args.host,
|
|
261
|
+
port=port,
|
|
262
|
+
log_level=args.uvicorn_log_level,
|
|
263
|
+
)
|
|
264
|
+
return 0
|
|
265
|
+
|
|
266
|
+
# When --reload is on:
|
|
267
|
+
if args.reload:
|
|
268
|
+
print("\n" + "=" * 50)
|
|
269
|
+
print(f"[AetherGraph] 🚀 {'Server started at:':<18} {url}")
|
|
270
|
+
print(f"[AetherGraph] 🖥️ {'UI:':<18} {url}/ui (if built)")
|
|
271
|
+
print(f"[AetherGraph] 📡 {'API:':<18} {url}/api/v1/")
|
|
272
|
+
print(f"[AetherGraph] 📂 {'Workspace:':<18} {args.workspace}")
|
|
273
|
+
print(f"[AetherGraph] ♻️ {'Auto-reload:':<18} enabled (uvicorn)")
|
|
274
|
+
print("=" * 50 + "\n")
|
|
275
|
+
|
|
276
|
+
reload_dirs: list[str] = [str(project_root)]
|
|
277
|
+
for p in paths:
|
|
278
|
+
reload_dirs.append(str(Path(p).parent))
|
|
279
|
+
|
|
280
|
+
# Use import string + factory=True here
|
|
281
|
+
uvicorn.run(
|
|
282
|
+
"aethergraph.server.app_factory:create_app_from_env",
|
|
283
|
+
host=args.host,
|
|
284
|
+
port=port,
|
|
285
|
+
log_level=args.uvicorn_log_level,
|
|
286
|
+
reload=True,
|
|
287
|
+
reload_dirs=reload_dirs,
|
|
288
|
+
factory=True,
|
|
289
|
+
)
|
|
290
|
+
return 0
|
|
291
|
+
|
|
292
|
+
return 2
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
if __name__ == "__main__":
|
|
296
|
+
raise SystemExit(main())
|
|
File without changes
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# aethergraph/api/v1/agents.py
|
|
2
|
+
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
|
+
from fastapi import APIRouter, Depends, HTTPException
|
|
6
|
+
|
|
7
|
+
from aethergraph.api.v1.deps import RequestIdentity, get_identity
|
|
8
|
+
from aethergraph.api.v1.schemas import AgentDescriptor
|
|
9
|
+
from aethergraph.core.runtime.runtime_registry import current_registry
|
|
10
|
+
|
|
11
|
+
router = APIRouter(tags=["agents"])
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@router.get("/agents", response_model=list[AgentDescriptor])
|
|
15
|
+
async def list_agents(
|
|
16
|
+
identity: Annotated[RequestIdentity, Depends(get_identity)] = None,
|
|
17
|
+
) -> list[AgentDescriptor]:
|
|
18
|
+
"""
|
|
19
|
+
List all registered agents.
|
|
20
|
+
|
|
21
|
+
These come from `as_agent={...}` (or legacy `agent="..."`) in your decorators.
|
|
22
|
+
"""
|
|
23
|
+
reg = current_registry()
|
|
24
|
+
if reg is None:
|
|
25
|
+
raise HTTPException(status_code=500, detail="Registry not available")
|
|
26
|
+
|
|
27
|
+
entries = reg.list_agents() # {'agent:designer': '0.1.0', ...}
|
|
28
|
+
out: list[AgentDescriptor] = []
|
|
29
|
+
|
|
30
|
+
for ref, _version in entries.items():
|
|
31
|
+
try:
|
|
32
|
+
_, name = ref.split(":", 1)
|
|
33
|
+
except ValueError:
|
|
34
|
+
continue
|
|
35
|
+
|
|
36
|
+
meta = reg.get_meta(nspace="agent", name=name) or {}
|
|
37
|
+
agent_id = meta.get("id", name)
|
|
38
|
+
|
|
39
|
+
out.append(
|
|
40
|
+
AgentDescriptor(
|
|
41
|
+
id=agent_id,
|
|
42
|
+
meta=meta,
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
return out
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# aethergraph/api/v1/apps.py
|
|
2
|
+
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
|
+
from fastapi import APIRouter, Depends, HTTPException
|
|
6
|
+
|
|
7
|
+
from aethergraph.api.v1.deps import RequestIdentity, get_identity
|
|
8
|
+
from aethergraph.api.v1.schemas import AppDescriptor
|
|
9
|
+
from aethergraph.core.runtime.runtime_registry import current_registry
|
|
10
|
+
|
|
11
|
+
router = APIRouter(tags=["apps"])
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@router.get("/apps", response_model=list[AppDescriptor])
|
|
15
|
+
async def list_apps(
|
|
16
|
+
identity: Annotated[RequestIdentity, Depends(get_identity)] = None,
|
|
17
|
+
) -> list[AppDescriptor]:
|
|
18
|
+
"""
|
|
19
|
+
List all registered apps.
|
|
20
|
+
|
|
21
|
+
Each app is a graph (or graphfn) that has been decorated with `as_app={...}`.
|
|
22
|
+
"""
|
|
23
|
+
reg = current_registry()
|
|
24
|
+
if reg is None:
|
|
25
|
+
raise HTTPException(status_code=500, detail="Registry not available")
|
|
26
|
+
|
|
27
|
+
# {'app:metalens': '0.1.0', ...}
|
|
28
|
+
entries = reg.list_apps()
|
|
29
|
+
out: list[AppDescriptor] = []
|
|
30
|
+
|
|
31
|
+
for ref, _version in entries.items():
|
|
32
|
+
# ref is "app:<name>"
|
|
33
|
+
try:
|
|
34
|
+
_, name = ref.split(":", 1)
|
|
35
|
+
except ValueError:
|
|
36
|
+
# Defensive: ignore malformed keys
|
|
37
|
+
continue
|
|
38
|
+
|
|
39
|
+
meta = reg.get_meta(nspace="app", name=name) or {}
|
|
40
|
+
app_id = meta.get("id", name)
|
|
41
|
+
graph_id = meta.get("graph_id", name)
|
|
42
|
+
|
|
43
|
+
out.append(
|
|
44
|
+
AppDescriptor(
|
|
45
|
+
id=app_id,
|
|
46
|
+
graph_id=graph_id,
|
|
47
|
+
meta=meta,
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return out
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@router.get("/apps/{app_id}", response_model=AppDescriptor)
|
|
55
|
+
async def get_app(
|
|
56
|
+
app_id: str,
|
|
57
|
+
identity: Annotated[RequestIdentity, Depends(get_identity)] = None,
|
|
58
|
+
) -> AppDescriptor:
|
|
59
|
+
reg = current_registry()
|
|
60
|
+
if reg is None:
|
|
61
|
+
raise HTTPException(status_code=500, detail="Registry not available")
|
|
62
|
+
|
|
63
|
+
# Resolve by app id (we store app_id as the registry `name`)
|
|
64
|
+
meta = reg.get_meta(nspace="app", name=app_id)
|
|
65
|
+
if not meta:
|
|
66
|
+
raise HTTPException(status_code=404, detail=f"App not found: {app_id}")
|
|
67
|
+
|
|
68
|
+
graph_id = meta.get("graph_id", meta.get("backing", {}).get("name", app_id))
|
|
69
|
+
|
|
70
|
+
return AppDescriptor(id=meta.get("id", app_id), graph_id=graph_id, meta=meta)
|