agentgraph-server 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. agentgraph/__init__.py +1 -0
  2. agentgraph/auth/__init__.py +0 -0
  3. agentgraph/auth/credentials.py +224 -0
  4. agentgraph/backends/__init__.py +50 -0
  5. agentgraph/backends/sqlite/__init__.py +1 -0
  6. agentgraph/backends/sqlite/backend.py +1471 -0
  7. agentgraph/backends/sqlite/vector.py +142 -0
  8. agentgraph/cli.py +721 -0
  9. agentgraph/cli_query.py +519 -0
  10. agentgraph/config.py +90 -0
  11. agentgraph/connectors/__init__.py +0 -0
  12. agentgraph/connectors/base.py +455 -0
  13. agentgraph/connectors/registry.py +78 -0
  14. agentgraph/connectors/status.py +244 -0
  15. agentgraph/core/__init__.py +0 -0
  16. agentgraph/core/context.py +26 -0
  17. agentgraph/core/runtime.py +36 -0
  18. agentgraph/core/storage.py +240 -0
  19. agentgraph/graph/__init__.py +1 -0
  20. agentgraph/graph/bookmark.py +87 -0
  21. agentgraph/graph/delete.py +17 -0
  22. agentgraph/graph/download.py +35 -0
  23. agentgraph/graph/embeddings.py +58 -0
  24. agentgraph/graph/fetch.py +53 -0
  25. agentgraph/graph/gc.py +26 -0
  26. agentgraph/graph/link.py +63 -0
  27. agentgraph/graph/person.py +40 -0
  28. agentgraph/graph/query.py +244 -0
  29. agentgraph/graph/upsert.py +49 -0
  30. agentgraph/logging.py +78 -0
  31. agentgraph/mcp/__init__.py +0 -0
  32. agentgraph/mcp/server.py +811 -0
  33. agentgraph/perf.py +43 -0
  34. agentgraph/server/__init__.py +0 -0
  35. agentgraph/server/app.py +133 -0
  36. agentgraph/server/cli_api.py +708 -0
  37. agentgraph/server/dwell.py +79 -0
  38. agentgraph/server/graph_api.py +46 -0
  39. agentgraph/server/router.py +47 -0
  40. agentgraph/server/sync.py +247 -0
  41. agentgraph/skills.py +93 -0
  42. agentgraph_server-0.5.0.data/data/.agents/skills/graph/SKILL.md +159 -0
  43. agentgraph_server-0.5.0.data/data/.agents/skills/slack-auth/SKILL.md +92 -0
  44. agentgraph_server-0.5.0.dist-info/METADATA +286 -0
  45. agentgraph_server-0.5.0.dist-info/RECORD +49 -0
  46. agentgraph_server-0.5.0.dist-info/WHEEL +5 -0
  47. agentgraph_server-0.5.0.dist-info/entry_points.txt +2 -0
  48. agentgraph_server-0.5.0.dist-info/licenses/LICENSE +21 -0
  49. agentgraph_server-0.5.0.dist-info/top_level.txt +1 -0
agentgraph/perf.py ADDED
@@ -0,0 +1,43 @@
1
+ """Small performance instrumentation helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from collections.abc import Iterator
7
+ from contextlib import contextmanager
8
+ from contextvars import ContextVar, Token
9
+ from time import perf_counter
10
+ from typing import Any
11
+
12
+ logger = logging.getLogger("agentgraph.perf")
13
+
14
+ _timing_sink: ContextVar[list[tuple[str, float]] | None] = ContextVar("timing_sink", default=None)
15
+
16
+
17
+ @contextmanager
18
+ def capture_timings() -> Iterator[list[tuple[str, float]]]:
19
+ """Collect nested ``timed`` spans for the current async task context."""
20
+ timings: list[tuple[str, float]] = []
21
+ token: Token[list[tuple[str, float]] | None] = _timing_sink.set(timings)
22
+ try:
23
+ yield timings
24
+ finally:
25
+ _timing_sink.reset(token)
26
+
27
+
28
+ @contextmanager
29
+ def timed(operation: str, **fields: Any) -> Iterator[None]:
30
+ """Log elapsed time for an operation at debug level."""
31
+ start = perf_counter()
32
+ try:
33
+ yield
34
+ finally:
35
+ elapsed_ms = (perf_counter() - start) * 1000
36
+ sink = _timing_sink.get()
37
+ if sink is not None:
38
+ sink.append((operation, elapsed_ms))
39
+ suffix = " ".join(f"{key}={value}" for key, value in fields.items())
40
+ if suffix:
41
+ logger.debug("%s completed in %.1fms %s", operation, elapsed_ms, suffix)
42
+ else:
43
+ logger.debug("%s completed in %.1fms", operation, elapsed_ms)
File without changes
@@ -0,0 +1,133 @@
1
+ """FastAPI application: browser extension intake and server lifecycle."""
2
+
3
+ # pyright: reportUnknownMemberType=false, reportUnknownVariableType=false
4
+
5
+ from __future__ import annotations
6
+
7
+ import logging
8
+ from collections.abc import AsyncGenerator
9
+ from contextlib import asynccontextmanager
10
+ from pathlib import Path
11
+ from time import perf_counter
12
+ from typing import Any
13
+
14
+ from apscheduler.schedulers.asyncio import AsyncIOScheduler # type: ignore[import-untyped]
15
+ from fastapi import FastAPI, Request
16
+ from fastapi.responses import FileResponse
17
+ from fastapi.staticfiles import StaticFiles
18
+ from pydantic import BaseModel
19
+
20
+ from agentgraph.core.context import clear_backend, set_backend
21
+ from agentgraph.graph.gc import run_gc
22
+ from agentgraph.server.cli_api import router as cli_router
23
+ from agentgraph.server.graph_api import router as graph_router
24
+ from agentgraph.server.sync import setup_sync, shutdown_poll_tasks
25
+
26
+ _STATIC_DIR = Path(__file__).parent / "static"
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ _scheduler: AsyncIOScheduler | None = None
31
+
32
+
33
+ def viewer_url(host: str, port: int) -> str:
34
+ browser_host = "127.0.0.1" if host in {"", "0.0.0.0", "::"} else host
35
+ if ":" in browser_host and not browser_host.startswith("["):
36
+ browser_host = f"[{browser_host}]"
37
+ return f"http://{browser_host}:{port}/viewer"
38
+
39
+
40
+ @asynccontextmanager
41
+ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
42
+ global _scheduler
43
+ from agentgraph.backends import get_backend_class
44
+ from agentgraph.config import get_settings
45
+ from agentgraph.connectors.registry import bootstrap
46
+ from agentgraph.logging import configure_logging
47
+
48
+ settings = get_settings()
49
+ configure_logging(settings.log_level, settings.log_file)
50
+
51
+ backend_class: Any = get_backend_class(settings.backend)
52
+ if settings.backend == "sqlite":
53
+ backend = backend_class(settings.backend_sqlite_path, settings.backend_sqlite_vector_mode)
54
+ else:
55
+ backend = backend_class(settings)
56
+
57
+ await backend.initialize()
58
+ set_backend(backend)
59
+
60
+ bootstrap()
61
+
62
+ _scheduler = AsyncIOScheduler()
63
+ _scheduler.add_job(run_gc, "cron", hour=3, minute=0, id="gc")
64
+ setup_sync(_scheduler)
65
+ _scheduler.start()
66
+
67
+ logger.info("AgentGraph server started (backend=%s)", settings.backend)
68
+ logger.info("Web viewer: %s", viewer_url(settings.server_host, settings.server_port))
69
+ yield
70
+
71
+ if _scheduler:
72
+ _scheduler.shutdown(wait=False)
73
+ try:
74
+ await shutdown_poll_tasks()
75
+ finally:
76
+ await backend.close()
77
+ clear_backend()
78
+ logger.info("AgentGraph server stopped")
79
+
80
+
81
+ app = FastAPI(title="AgentGraph", lifespan=lifespan)
82
+
83
+
84
+ @app.middleware("http")
85
+ async def log_request_timing(request: Request, call_next: Any) -> Any:
86
+ start = perf_counter()
87
+ response = await call_next(request)
88
+ elapsed_ms = (perf_counter() - start) * 1000
89
+ size = response.headers.get("content-length")
90
+ logger.debug(
91
+ "request %s %s -> %s in %.1fms%s",
92
+ request.method,
93
+ request.url.path,
94
+ response.status_code,
95
+ elapsed_ms,
96
+ f" bytes={size}" if size else "",
97
+ )
98
+ return response
99
+
100
+ app.include_router(graph_router)
101
+ app.include_router(cli_router)
102
+ app.mount("/static", StaticFiles(directory=str(_STATIC_DIR)), name="static")
103
+
104
+
105
+ class ReportDwellRequest(BaseModel):
106
+ url: str
107
+ dwell_ms: int
108
+ meta: dict[str, str] | None = None
109
+
110
+
111
+ @app.post("/report-dwell", status_code=202)
112
+ async def report_dwell(req: ReportDwellRequest) -> dict[str, Any]:
113
+ """
114
+ Receive an incremental dwell time report (either upon hitting the threshold
115
+ or when a tab loses focus/closes). Records the cumulative dwell time in the
116
+ database for ranking, and dispatches a background connector fetch if the
117
+ dwell time meets the threshold.
118
+ """
119
+ from agentgraph.server.dwell import record_dwell_time
120
+
121
+ result = await record_dwell_time(req.url, req.dwell_ms, req.meta)
122
+ return result
123
+
124
+
125
+ @app.get("/viewer", include_in_schema=False)
126
+ async def viewer() -> FileResponse:
127
+ """Serve the graph viewer HTML."""
128
+ return FileResponse(str(_STATIC_DIR / "viewer.html"))
129
+
130
+
131
+ @app.get("/health")
132
+ async def health() -> dict[str, str]:
133
+ return {"status": "ok"}