aethergraph 0.1.0a1__py3-none-any.whl → 0.1.0a2__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 +293 -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 +190 -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.0a2.dist-info}/METADATA +138 -31
- aethergraph-0.1.0a2.dist-info/RECORD +356 -0
- aethergraph-0.1.0a2.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.0a2.dist-info}/WHEEL +0 -0
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a2.dist-info}/licenses/LICENSE +0 -0
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a2.dist-info}/licenses/NOTICE +0 -0
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import sqlite3
|
|
7
|
+
from typing import Any, Literal
|
|
8
|
+
|
|
9
|
+
from aethergraph.contracts.services.artifacts import Artifact
|
|
10
|
+
from aethergraph.contracts.storage.artifact_index import AsyncArtifactIndex
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SqliteArtifactIndexSync:
|
|
14
|
+
"""
|
|
15
|
+
SQLite-based artifact index.
|
|
16
|
+
|
|
17
|
+
- Good for tens/hundreds of thousands of artifacts.
|
|
18
|
+
- Stores labels/metrics as JSON.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, path: str):
|
|
22
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
23
|
+
self._conn = sqlite3.connect(path, check_same_thread=False)
|
|
24
|
+
self._conn.row_factory = sqlite3.Row
|
|
25
|
+
self._init_schema()
|
|
26
|
+
|
|
27
|
+
def _init_schema(self) -> None:
|
|
28
|
+
cur = self._conn.cursor()
|
|
29
|
+
cur.execute(
|
|
30
|
+
"""
|
|
31
|
+
CREATE TABLE IF NOT EXISTS artifacts (
|
|
32
|
+
artifact_id TEXT PRIMARY KEY,
|
|
33
|
+
run_id TEXT,
|
|
34
|
+
graph_id TEXT,
|
|
35
|
+
node_id TEXT,
|
|
36
|
+
tool_name TEXT,
|
|
37
|
+
tool_version TEXT,
|
|
38
|
+
kind TEXT,
|
|
39
|
+
sha256 TEXT,
|
|
40
|
+
bytes INTEGER,
|
|
41
|
+
mime TEXT,
|
|
42
|
+
created_at TEXT,
|
|
43
|
+
labels_json TEXT,
|
|
44
|
+
metrics_json TEXT,
|
|
45
|
+
pinned INTEGER DEFAULT 0,
|
|
46
|
+
-- tenant / scope columns
|
|
47
|
+
org_id TEXT,
|
|
48
|
+
user_id TEXT,
|
|
49
|
+
client_id TEXT,
|
|
50
|
+
app_id TEXT,
|
|
51
|
+
session_id TEXT,
|
|
52
|
+
-- uri columns (may be missing in older DBs)
|
|
53
|
+
uri TEXT,
|
|
54
|
+
preview_uri TEXT
|
|
55
|
+
)
|
|
56
|
+
"""
|
|
57
|
+
)
|
|
58
|
+
cur.execute(
|
|
59
|
+
"""
|
|
60
|
+
CREATE TABLE IF NOT EXISTS artifact_occurrences (
|
|
61
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
62
|
+
artifact_id TEXT,
|
|
63
|
+
run_id TEXT,
|
|
64
|
+
graph_id TEXT,
|
|
65
|
+
node_id TEXT,
|
|
66
|
+
tool_name TEXT,
|
|
67
|
+
tool_version TEXT,
|
|
68
|
+
created_at TEXT,
|
|
69
|
+
labels_json TEXT
|
|
70
|
+
)
|
|
71
|
+
"""
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Migration: add uri / preview_uri / tenant columns if missing
|
|
75
|
+
cur.execute("PRAGMA table_info(artifacts)")
|
|
76
|
+
cols = {row["name"] for row in cur.fetchall()}
|
|
77
|
+
|
|
78
|
+
if "uri" not in cols:
|
|
79
|
+
cur.execute("ALTER TABLE artifacts ADD COLUMN uri TEXT")
|
|
80
|
+
if "preview_uri" not in cols:
|
|
81
|
+
cur.execute("ALTER TABLE artifacts ADD COLUMN preview_uri TEXT")
|
|
82
|
+
|
|
83
|
+
# 🔹 NEW tenant columns (for existing DBs)
|
|
84
|
+
if "org_id" not in cols:
|
|
85
|
+
cur.execute("ALTER TABLE artifacts ADD COLUMN org_id TEXT")
|
|
86
|
+
if "user_id" not in cols:
|
|
87
|
+
cur.execute("ALTER TABLE artifacts ADD COLUMN user_id TEXT")
|
|
88
|
+
if "client_id" not in cols:
|
|
89
|
+
cur.execute("ALTER TABLE artifacts ADD COLUMN client_id TEXT")
|
|
90
|
+
if "app_id" not in cols:
|
|
91
|
+
cur.execute("ALTER TABLE artifacts ADD COLUMN app_id TEXT")
|
|
92
|
+
if "session_id" not in cols:
|
|
93
|
+
cur.execute("ALTER TABLE artifacts ADD COLUMN session_id TEXT")
|
|
94
|
+
|
|
95
|
+
# Existing indexes
|
|
96
|
+
cur.execute("CREATE INDEX IF NOT EXISTS idx_artifacts_run ON artifacts(run_id)")
|
|
97
|
+
cur.execute("CREATE INDEX IF NOT EXISTS idx_artifacts_kind ON artifacts(kind)")
|
|
98
|
+
cur.execute("CREATE INDEX IF NOT EXISTS idx_artifacts_sha ON artifacts(sha256)")
|
|
99
|
+
cur.execute(
|
|
100
|
+
"CREATE INDEX IF NOT EXISTS idx_occ_artifact ON artifact_occurrences(artifact_id)"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# 🔹 NEW tenant-oriented indexes (tune as needed)
|
|
104
|
+
cur.execute(
|
|
105
|
+
"CREATE INDEX IF NOT EXISTS idx_artifacts_user ON artifacts(user_id, created_at)"
|
|
106
|
+
)
|
|
107
|
+
cur.execute("CREATE INDEX IF NOT EXISTS idx_artifacts_org ON artifacts(org_id, created_at)")
|
|
108
|
+
cur.execute(
|
|
109
|
+
"CREATE INDEX IF NOT EXISTS idx_artifacts_session ON artifacts(session_id, created_at)"
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
self._conn.commit()
|
|
113
|
+
|
|
114
|
+
def upsert(self, a: Artifact) -> None:
|
|
115
|
+
rec = a.to_dict()
|
|
116
|
+
labels_json = json.dumps(rec.get("labels") or {}, ensure_ascii=False)
|
|
117
|
+
metrics_json = json.dumps(rec.get("metrics") or {}, ensure_ascii=False)
|
|
118
|
+
|
|
119
|
+
self._conn.execute(
|
|
120
|
+
"""
|
|
121
|
+
INSERT INTO artifacts (
|
|
122
|
+
artifact_id,
|
|
123
|
+
run_id,
|
|
124
|
+
graph_id,
|
|
125
|
+
node_id,
|
|
126
|
+
tool_name,
|
|
127
|
+
tool_version,
|
|
128
|
+
kind,
|
|
129
|
+
sha256,
|
|
130
|
+
bytes,
|
|
131
|
+
mime,
|
|
132
|
+
created_at,
|
|
133
|
+
labels_json,
|
|
134
|
+
metrics_json,
|
|
135
|
+
pinned,
|
|
136
|
+
uri,
|
|
137
|
+
preview_uri,
|
|
138
|
+
org_id,
|
|
139
|
+
user_id,
|
|
140
|
+
client_id,
|
|
141
|
+
app_id,
|
|
142
|
+
session_id
|
|
143
|
+
) VALUES (
|
|
144
|
+
:artifact_id,
|
|
145
|
+
:run_id,
|
|
146
|
+
:graph_id,
|
|
147
|
+
:node_id,
|
|
148
|
+
:tool_name,
|
|
149
|
+
:tool_version,
|
|
150
|
+
:kind,
|
|
151
|
+
:sha256,
|
|
152
|
+
:bytes,
|
|
153
|
+
:mime,
|
|
154
|
+
:created_at,
|
|
155
|
+
:labels_json,
|
|
156
|
+
:metrics_json,
|
|
157
|
+
:pinned,
|
|
158
|
+
:uri,
|
|
159
|
+
:preview_uri,
|
|
160
|
+
:org_id,
|
|
161
|
+
:user_id,
|
|
162
|
+
:client_id,
|
|
163
|
+
:app_id,
|
|
164
|
+
:session_id
|
|
165
|
+
)
|
|
166
|
+
ON CONFLICT(artifact_id) DO UPDATE SET
|
|
167
|
+
run_id = excluded.run_id,
|
|
168
|
+
graph_id = excluded.graph_id,
|
|
169
|
+
node_id = excluded.node_id,
|
|
170
|
+
tool_name = excluded.tool_name,
|
|
171
|
+
tool_version = excluded.tool_version,
|
|
172
|
+
kind = excluded.kind,
|
|
173
|
+
sha256 = excluded.sha256,
|
|
174
|
+
bytes = excluded.bytes,
|
|
175
|
+
mime = excluded.mime,
|
|
176
|
+
created_at = excluded.created_at,
|
|
177
|
+
labels_json = excluded.labels_json,
|
|
178
|
+
metrics_json = excluded.metrics_json,
|
|
179
|
+
pinned = excluded.pinned,
|
|
180
|
+
uri = excluded.uri,
|
|
181
|
+
preview_uri = excluded.preview_uri,
|
|
182
|
+
org_id = excluded.org_id,
|
|
183
|
+
user_id = excluded.user_id,
|
|
184
|
+
client_id = excluded.client_id,
|
|
185
|
+
app_id = excluded.app_id,
|
|
186
|
+
session_id = excluded.session_id
|
|
187
|
+
""",
|
|
188
|
+
{
|
|
189
|
+
"artifact_id": rec["artifact_id"],
|
|
190
|
+
"run_id": rec.get("run_id"),
|
|
191
|
+
"graph_id": rec.get("graph_id"),
|
|
192
|
+
"node_id": rec.get("node_id"),
|
|
193
|
+
"tool_name": rec.get("tool_name"),
|
|
194
|
+
"tool_version": rec.get("tool_version"),
|
|
195
|
+
"kind": rec.get("kind"),
|
|
196
|
+
"sha256": rec.get("sha256"),
|
|
197
|
+
"bytes": rec.get("bytes"),
|
|
198
|
+
"mime": rec.get("mime"),
|
|
199
|
+
"created_at": rec.get("created_at"),
|
|
200
|
+
"labels_json": labels_json,
|
|
201
|
+
"metrics_json": metrics_json,
|
|
202
|
+
"pinned": int(rec.get("pinned") or 0),
|
|
203
|
+
"uri": rec.get("uri"),
|
|
204
|
+
"preview_uri": rec.get("preview_uri"),
|
|
205
|
+
"org_id": rec.get("org_id"),
|
|
206
|
+
"user_id": rec.get("user_id"),
|
|
207
|
+
"client_id": rec.get("client_id"),
|
|
208
|
+
"app_id": rec.get("app_id"),
|
|
209
|
+
"session_id": rec.get("session_id"),
|
|
210
|
+
},
|
|
211
|
+
)
|
|
212
|
+
self._conn.commit()
|
|
213
|
+
|
|
214
|
+
def list_for_run(self, run_id: str) -> list[Artifact]:
|
|
215
|
+
cur = self._conn.execute(
|
|
216
|
+
"SELECT * FROM artifacts WHERE run_id = ? ORDER BY created_at ASC",
|
|
217
|
+
(run_id,),
|
|
218
|
+
)
|
|
219
|
+
rows = cur.fetchall()
|
|
220
|
+
return [self._row_to_artifact(r) for r in rows]
|
|
221
|
+
|
|
222
|
+
def search(
|
|
223
|
+
self,
|
|
224
|
+
*,
|
|
225
|
+
kind: str | None = None,
|
|
226
|
+
labels: dict[str, Any] | None = None,
|
|
227
|
+
metric: str | None = None,
|
|
228
|
+
mode: Literal["max", "min"] | None = None,
|
|
229
|
+
limit: int | None = None,
|
|
230
|
+
offset: int = 0,
|
|
231
|
+
) -> list[Artifact]:
|
|
232
|
+
"""Search artifacts with optional filtering and ranking.
|
|
233
|
+
Args:
|
|
234
|
+
kind: Optional kind to filter by.
|
|
235
|
+
labels: Optional dict of labels to filter by (exact match).
|
|
236
|
+
metric: Optional metric name to rank by.
|
|
237
|
+
mode: "max" or "min" for metric ranking.
|
|
238
|
+
limit: Optional max number of results to return.
|
|
239
|
+
offset: Number of results to skip from start.
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
List of matching Artifact objects.
|
|
243
|
+
|
|
244
|
+
NOTE: If metric and mode are provided, ranking is done in Python
|
|
245
|
+
after fetching all candidates, which may be slower for large datasets.
|
|
246
|
+
"""
|
|
247
|
+
where = []
|
|
248
|
+
params: list[Any] = []
|
|
249
|
+
|
|
250
|
+
if kind:
|
|
251
|
+
where.append("kind = ?")
|
|
252
|
+
params.append(kind)
|
|
253
|
+
|
|
254
|
+
TENANT_KEYS = {
|
|
255
|
+
"org_id": "org_id",
|
|
256
|
+
"user_id": "user_id",
|
|
257
|
+
"client_id": "client_id",
|
|
258
|
+
"app_id": "app_id",
|
|
259
|
+
"session_id": "session_id",
|
|
260
|
+
"run_id": "run_id",
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if labels:
|
|
264
|
+
for k, v in labels.items():
|
|
265
|
+
if k == "tags":
|
|
266
|
+
tag_list = v if isinstance(v, list) else [v]
|
|
267
|
+
tag_list = [t for t in (t.strip() for t in tag_list) if t]
|
|
268
|
+
if tag_list:
|
|
269
|
+
ors = []
|
|
270
|
+
for t in tag_list:
|
|
271
|
+
ors.append("labels_json LIKE ?")
|
|
272
|
+
params.append(f'%"{k}":%"{t}"%')
|
|
273
|
+
where.append("(" + " OR ".join(ors) + ")")
|
|
274
|
+
continue
|
|
275
|
+
|
|
276
|
+
if k in TENANT_KEYS:
|
|
277
|
+
where.append(f"{TENANT_KEYS[k]} = ?")
|
|
278
|
+
params.append(v)
|
|
279
|
+
continue
|
|
280
|
+
|
|
281
|
+
where.append("labels_json LIKE ?")
|
|
282
|
+
params.append(f'%"{k}": "{v}"%')
|
|
283
|
+
|
|
284
|
+
base_sql = "SELECT * FROM artifacts"
|
|
285
|
+
if where:
|
|
286
|
+
base_sql += " WHERE " + " AND ".join(where)
|
|
287
|
+
|
|
288
|
+
# Fast path: no metric-based ranking → push ORDER + LIMIT/OFFSET to SQL
|
|
289
|
+
if not metric or not mode:
|
|
290
|
+
sql = base_sql + " ORDER BY created_at DESC"
|
|
291
|
+
if limit is not None:
|
|
292
|
+
sql += " LIMIT ? OFFSET ?"
|
|
293
|
+
params.extend([limit, offset])
|
|
294
|
+
elif offset:
|
|
295
|
+
# offset without limit is weird; just add a huge limit for safety
|
|
296
|
+
sql += " LIMIT -1 OFFSET ?"
|
|
297
|
+
params.append(offset)
|
|
298
|
+
|
|
299
|
+
cur = self._conn.execute(sql, params)
|
|
300
|
+
rows = [self._row_to_artifact(r) for r in cur.fetchall()]
|
|
301
|
+
return rows
|
|
302
|
+
|
|
303
|
+
# Slow path: metric sorting in Python (same as before)
|
|
304
|
+
sql = base_sql + " ORDER BY created_at DESC"
|
|
305
|
+
cur = self._conn.execute(sql, params)
|
|
306
|
+
rows = [self._row_to_artifact(r) for r in cur.fetchall()]
|
|
307
|
+
|
|
308
|
+
rows = [a for a in rows if metric in (a.metrics or {})]
|
|
309
|
+
rows.sort(
|
|
310
|
+
key=lambda a: a.metrics[metric],
|
|
311
|
+
reverse=(mode == "max"),
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
if offset:
|
|
315
|
+
rows = rows[offset:]
|
|
316
|
+
if limit is not None:
|
|
317
|
+
rows = rows[:limit]
|
|
318
|
+
|
|
319
|
+
return rows
|
|
320
|
+
|
|
321
|
+
def best(
|
|
322
|
+
self,
|
|
323
|
+
*,
|
|
324
|
+
kind: str,
|
|
325
|
+
metric: str,
|
|
326
|
+
mode: Literal["max", "min"],
|
|
327
|
+
filters: dict[str, Any] | None = None,
|
|
328
|
+
) -> Artifact | None:
|
|
329
|
+
rows = self.search(
|
|
330
|
+
kind=kind,
|
|
331
|
+
labels=filters,
|
|
332
|
+
metric=metric,
|
|
333
|
+
mode=mode,
|
|
334
|
+
limit=1,
|
|
335
|
+
)
|
|
336
|
+
return rows[0] if rows else None
|
|
337
|
+
|
|
338
|
+
def pin(self, artifact_id: str, pinned: bool = True) -> None:
|
|
339
|
+
self._conn.execute(
|
|
340
|
+
"UPDATE artifacts SET pinned = ? WHERE artifact_id = ?",
|
|
341
|
+
(int(bool(pinned)), artifact_id),
|
|
342
|
+
)
|
|
343
|
+
self._conn.commit()
|
|
344
|
+
|
|
345
|
+
def record_occurrence(self, a: Artifact, extra_labels: dict | None = None) -> None:
|
|
346
|
+
labels = {**(a.labels or {}), **(extra_labels or {})}
|
|
347
|
+
labels_json = json.dumps(labels, ensure_ascii=False)
|
|
348
|
+
self._conn.execute(
|
|
349
|
+
"""
|
|
350
|
+
INSERT INTO artifact_occurrences (
|
|
351
|
+
artifact_id, run_id, graph_id, node_id,
|
|
352
|
+
tool_name, tool_version, created_at, labels_json
|
|
353
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
354
|
+
""",
|
|
355
|
+
(
|
|
356
|
+
a.artifact_id,
|
|
357
|
+
a.run_id,
|
|
358
|
+
a.graph_id,
|
|
359
|
+
a.node_id,
|
|
360
|
+
a.tool_name,
|
|
361
|
+
a.tool_version,
|
|
362
|
+
a.created_at,
|
|
363
|
+
labels_json,
|
|
364
|
+
),
|
|
365
|
+
)
|
|
366
|
+
self._conn.commit()
|
|
367
|
+
|
|
368
|
+
# -------- helpers --------
|
|
369
|
+
|
|
370
|
+
def _row_to_artifact(self, row: sqlite3.Row) -> Artifact:
|
|
371
|
+
labels = json.loads(row["labels_json"] or "{}")
|
|
372
|
+
metrics = json.loads(row["metrics_json"] or "{}")
|
|
373
|
+
return Artifact(
|
|
374
|
+
artifact_id=row["artifact_id"],
|
|
375
|
+
run_id=row["run_id"],
|
|
376
|
+
graph_id=row["graph_id"],
|
|
377
|
+
node_id=row["node_id"],
|
|
378
|
+
tool_name=row["tool_name"],
|
|
379
|
+
tool_version=row["tool_version"],
|
|
380
|
+
kind=row["kind"],
|
|
381
|
+
sha256=row["sha256"],
|
|
382
|
+
bytes=row["bytes"],
|
|
383
|
+
mime=row["mime"],
|
|
384
|
+
created_at=row["created_at"],
|
|
385
|
+
labels=labels,
|
|
386
|
+
metrics=metrics,
|
|
387
|
+
pinned=bool(row["pinned"]),
|
|
388
|
+
uri=row["uri"], # real URI
|
|
389
|
+
preview_uri=row["preview_uri"], # real preview URI (may be None)
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
def get(self, artifact_id: str) -> Artifact | None:
|
|
393
|
+
cur = self._conn.execute(
|
|
394
|
+
"SELECT * FROM artifacts WHERE artifact_id = ?",
|
|
395
|
+
(artifact_id,),
|
|
396
|
+
)
|
|
397
|
+
row = cur.fetchone()
|
|
398
|
+
if row:
|
|
399
|
+
return self._row_to_artifact(row)
|
|
400
|
+
return None
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
class SqliteArtifactIndex(AsyncArtifactIndex):
|
|
404
|
+
def __init__(self, path: str):
|
|
405
|
+
self._sync = SqliteArtifactIndexSync(path)
|
|
406
|
+
|
|
407
|
+
async def upsert(self, a: Artifact) -> None:
|
|
408
|
+
await asyncio.to_thread(self._sync.upsert, a)
|
|
409
|
+
|
|
410
|
+
async def list_for_run(self, run_id: str) -> list[Artifact]:
|
|
411
|
+
return await asyncio.to_thread(self._sync.list_for_run, run_id)
|
|
412
|
+
|
|
413
|
+
async def search(self, **kwargs) -> list[Artifact]:
|
|
414
|
+
return await asyncio.to_thread(self._sync.search, **kwargs)
|
|
415
|
+
|
|
416
|
+
async def best(self, **kwargs) -> Artifact | None:
|
|
417
|
+
return await asyncio.to_thread(self._sync.best, **kwargs)
|
|
418
|
+
|
|
419
|
+
async def pin(self, artifact_id: str, pinned: bool = True) -> None:
|
|
420
|
+
await asyncio.to_thread(self._sync.pin, artifact_id, pinned)
|
|
421
|
+
|
|
422
|
+
async def record_occurrence(self, a: Artifact, extra_labels: dict | None = None) -> None:
|
|
423
|
+
await asyncio.to_thread(self._sync.record_occurrence, a, extra_labels)
|
|
424
|
+
|
|
425
|
+
async def get(self, artifact_id: str) -> Artifact | None:
|
|
426
|
+
return await asyncio.to_thread(self._sync.get, artifact_id)
|