mem-os-python 1.6.27__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.
- mem_os_python-1.6.27.dist-info/METADATA +1032 -0
- mem_os_python-1.6.27.dist-info/RECORD +253 -0
- mem_os_python-1.6.27.dist-info/WHEEL +4 -0
- mem_os_python-1.6.27.dist-info/entry_points.txt +2 -0
- mem_os_python-1.6.27.dist-info/licenses/LICENSE +21 -0
- memos/__init__.py +16 -0
- memos/_js/adapters/anthropic.d.ts +279 -0
- memos/_js/adapters/anthropic.d.ts.map +1 -0
- memos/_js/adapters/anthropic.js +348 -0
- memos/_js/adapters/anthropic.js.map +1 -0
- memos/_js/adapters/base.d.ts +10 -0
- memos/_js/adapters/base.d.ts.map +1 -0
- memos/_js/adapters/base.js +2 -0
- memos/_js/adapters/base.js.map +1 -0
- memos/_js/adapters/openai.d.ts +264 -0
- memos/_js/adapters/openai.d.ts.map +1 -0
- memos/_js/adapters/openai.js +342 -0
- memos/_js/adapters/openai.js.map +1 -0
- memos/_js/apps/explorer.d.ts +244 -0
- memos/_js/apps/explorer.d.ts.map +1 -0
- memos/_js/apps/explorer.js +738 -0
- memos/_js/apps/explorer.js.map +1 -0
- memos/_js/citations.d.ts +65 -0
- memos/_js/citations.d.ts.map +1 -0
- memos/_js/citations.js +143 -0
- memos/_js/citations.js.map +1 -0
- memos/_js/cli-browse.d.ts +3 -0
- memos/_js/cli-browse.d.ts.map +1 -0
- memos/_js/cli-browse.js +129 -0
- memos/_js/cli-browse.js.map +1 -0
- memos/_js/cli-completion.d.ts +31 -0
- memos/_js/cli-completion.d.ts.map +1 -0
- memos/_js/cli-completion.js +340 -0
- memos/_js/cli-completion.js.map +1 -0
- memos/_js/cli-help.d.ts +18 -0
- memos/_js/cli-help.d.ts.map +1 -0
- memos/_js/cli-help.js +357 -0
- memos/_js/cli-help.js.map +1 -0
- memos/_js/cli.d.ts +28 -0
- memos/_js/cli.d.ts.map +1 -0
- memos/_js/cli.js +2880 -0
- memos/_js/cli.js.map +1 -0
- memos/_js/compression.d.ts +25 -0
- memos/_js/compression.d.ts.map +1 -0
- memos/_js/compression.js +319 -0
- memos/_js/compression.js.map +1 -0
- memos/_js/confidence-machine.d.ts +99 -0
- memos/_js/confidence-machine.d.ts.map +1 -0
- memos/_js/confidence-machine.js +222 -0
- memos/_js/confidence-machine.js.map +1 -0
- memos/_js/context-pack.d.ts +506 -0
- memos/_js/context-pack.d.ts.map +1 -0
- memos/_js/context-pack.js +1435 -0
- memos/_js/context-pack.js.map +1 -0
- memos/_js/contradictions.d.ts +160 -0
- memos/_js/contradictions.d.ts.map +1 -0
- memos/_js/contradictions.js +255 -0
- memos/_js/contradictions.js.map +1 -0
- memos/_js/dashboard.d.ts +30 -0
- memos/_js/dashboard.d.ts.map +1 -0
- memos/_js/dashboard.js +302 -0
- memos/_js/dashboard.js.map +1 -0
- memos/_js/ebbinghaus.d.ts +29 -0
- memos/_js/ebbinghaus.d.ts.map +1 -0
- memos/_js/ebbinghaus.js +32 -0
- memos/_js/ebbinghaus.js.map +1 -0
- memos/_js/embedding-queue.d.ts +190 -0
- memos/_js/embedding-queue.d.ts.map +1 -0
- memos/_js/embedding-queue.js +316 -0
- memos/_js/embedding-queue.js.map +1 -0
- memos/_js/embeddings.d.ts +187 -0
- memos/_js/embeddings.d.ts.map +1 -0
- memos/_js/embeddings.js +633 -0
- memos/_js/embeddings.js.map +1 -0
- memos/_js/entity-extraction.d.ts +54 -0
- memos/_js/entity-extraction.d.ts.map +1 -0
- memos/_js/entity-extraction.js +325 -0
- memos/_js/entity-extraction.js.map +1 -0
- memos/_js/entity-index.d.ts +93 -0
- memos/_js/entity-index.d.ts.map +1 -0
- memos/_js/entity-index.js +165 -0
- memos/_js/entity-index.js.map +1 -0
- memos/_js/event-memory.d.ts +147 -0
- memos/_js/event-memory.d.ts.map +1 -0
- memos/_js/event-memory.js +290 -0
- memos/_js/event-memory.js.map +1 -0
- memos/_js/external-import.d.ts +98 -0
- memos/_js/external-import.d.ts.map +1 -0
- memos/_js/external-import.js +669 -0
- memos/_js/external-import.js.map +1 -0
- memos/_js/fidelity.d.ts +367 -0
- memos/_js/fidelity.d.ts.map +1 -0
- memos/_js/fidelity.js +911 -0
- memos/_js/fidelity.js.map +1 -0
- memos/_js/graph-expansion.d.ts +119 -0
- memos/_js/graph-expansion.d.ts.map +1 -0
- memos/_js/graph-expansion.js +213 -0
- memos/_js/graph-expansion.js.map +1 -0
- memos/_js/graph-mermaid.d.ts +13 -0
- memos/_js/graph-mermaid.d.ts.map +1 -0
- memos/_js/graph-mermaid.js +32 -0
- memos/_js/graph-mermaid.js.map +1 -0
- memos/_js/graph.d.ts +170 -0
- memos/_js/graph.d.ts.map +1 -0
- memos/_js/graph.js +482 -0
- memos/_js/graph.js.map +1 -0
- memos/_js/harness.d.ts +63 -0
- memos/_js/harness.d.ts.map +1 -0
- memos/_js/harness.js +93 -0
- memos/_js/harness.js.map +1 -0
- memos/_js/importance.d.ts +118 -0
- memos/_js/importance.d.ts.map +1 -0
- memos/_js/importance.js +144 -0
- memos/_js/importance.js.map +1 -0
- memos/_js/index.d.ts +51 -0
- memos/_js/index.d.ts.map +1 -0
- memos/_js/index.js +53 -0
- memos/_js/index.js.map +1 -0
- memos/_js/mcp.d.ts +58 -0
- memos/_js/mcp.d.ts.map +1 -0
- memos/_js/mcp.js +1853 -0
- memos/_js/mcp.js.map +1 -0
- memos/_js/memory.d.ts +1762 -0
- memos/_js/memory.d.ts.map +1 -0
- memos/_js/memory.js +6283 -0
- memos/_js/memory.js.map +1 -0
- memos/_js/procedural.d.ts +74 -0
- memos/_js/procedural.d.ts.map +1 -0
- memos/_js/procedural.js +125 -0
- memos/_js/procedural.js.map +1 -0
- memos/_js/project-config.d.ts +68 -0
- memos/_js/project-config.d.ts.map +1 -0
- memos/_js/project-config.js +59 -0
- memos/_js/project-config.js.map +1 -0
- memos/_js/proof-of-forgetting.d.ts +57 -0
- memos/_js/proof-of-forgetting.d.ts.map +1 -0
- memos/_js/proof-of-forgetting.js +80 -0
- memos/_js/proof-of-forgetting.js.map +1 -0
- memos/_js/provenance.d.ts +122 -0
- memos/_js/provenance.d.ts.map +1 -0
- memos/_js/provenance.js +182 -0
- memos/_js/provenance.js.map +1 -0
- memos/_js/quarantine-commands.d.ts +23 -0
- memos/_js/quarantine-commands.d.ts.map +1 -0
- memos/_js/quarantine-commands.js +42 -0
- memos/_js/quarantine-commands.js.map +1 -0
- memos/_js/quarantine.d.ts +120 -0
- memos/_js/quarantine.d.ts.map +1 -0
- memos/_js/quarantine.js +873 -0
- memos/_js/quarantine.js.map +1 -0
- memos/_js/redact.d.ts +12 -0
- memos/_js/redact.d.ts.map +1 -0
- memos/_js/redact.js +26 -0
- memos/_js/redact.js.map +1 -0
- memos/_js/repl.d.ts +57 -0
- memos/_js/repl.d.ts.map +1 -0
- memos/_js/repl.js +295 -0
- memos/_js/repl.js.map +1 -0
- memos/_js/rerank-local.d.ts +61 -0
- memos/_js/rerank-local.d.ts.map +1 -0
- memos/_js/rerank-local.js +210 -0
- memos/_js/rerank-local.js.map +1 -0
- memos/_js/retain-filter.d.ts +98 -0
- memos/_js/retain-filter.d.ts.map +1 -0
- memos/_js/retain-filter.js +232 -0
- memos/_js/retain-filter.js.map +1 -0
- memos/_js/retrieval.d.ts +265 -0
- memos/_js/retrieval.d.ts.map +1 -0
- memos/_js/retrieval.js +1018 -0
- memos/_js/retrieval.js.map +1 -0
- memos/_js/revert.d.ts +77 -0
- memos/_js/revert.d.ts.map +1 -0
- memos/_js/revert.js +252 -0
- memos/_js/revert.js.map +1 -0
- memos/_js/scope.d.ts +30 -0
- memos/_js/scope.d.ts.map +1 -0
- memos/_js/scope.js +71 -0
- memos/_js/scope.js.map +1 -0
- memos/_js/semantic-screen.d.ts +152 -0
- memos/_js/semantic-screen.d.ts.map +1 -0
- memos/_js/semantic-screen.js +290 -0
- memos/_js/semantic-screen.js.map +1 -0
- memos/_js/session-hooks.d.ts +127 -0
- memos/_js/session-hooks.d.ts.map +1 -0
- memos/_js/session-hooks.js +482 -0
- memos/_js/session-hooks.js.map +1 -0
- memos/_js/storage/sqlite.d.ts +427 -0
- memos/_js/storage/sqlite.d.ts.map +1 -0
- memos/_js/storage/sqlite.js +2428 -0
- memos/_js/storage/sqlite.js.map +1 -0
- memos/_js/sync.d.ts +213 -0
- memos/_js/sync.d.ts.map +1 -0
- memos/_js/sync.js +389 -0
- memos/_js/sync.js.map +1 -0
- memos/_js/tamper-log.d.ts +98 -0
- memos/_js/tamper-log.d.ts.map +1 -0
- memos/_js/tamper-log.js +327 -0
- memos/_js/tamper-log.js.map +1 -0
- memos/_js/temporal.d.ts +92 -0
- memos/_js/temporal.d.ts.map +1 -0
- memos/_js/temporal.js +536 -0
- memos/_js/temporal.js.map +1 -0
- memos/_js/time-travel.d.ts +38 -0
- memos/_js/time-travel.d.ts.map +1 -0
- memos/_js/time-travel.js +46 -0
- memos/_js/time-travel.js.map +1 -0
- memos/_js/timestamp-format.d.ts +18 -0
- memos/_js/timestamp-format.d.ts.map +1 -0
- memos/_js/timestamp-format.js +52 -0
- memos/_js/timestamp-format.js.map +1 -0
- memos/_js/triple-extraction.d.ts +31 -0
- memos/_js/triple-extraction.d.ts.map +1 -0
- memos/_js/triple-extraction.js +116 -0
- memos/_js/triple-extraction.js.map +1 -0
- memos/_js/types.d.ts +2474 -0
- memos/_js/types.d.ts.map +1 -0
- memos/_js/types.js +20 -0
- memos/_js/types.js.map +1 -0
- memos/_js/updater.d.ts +95 -0
- memos/_js/updater.d.ts.map +1 -0
- memos/_js/updater.js +268 -0
- memos/_js/updater.js.map +1 -0
- memos/_js/version.d.ts +17 -0
- memos/_js/version.d.ts.map +1 -0
- memos/_js/version.js +34 -0
- memos/_js/version.js.map +1 -0
- memos/_js/vision/clip-query-cache.d.ts +58 -0
- memos/_js/vision/clip-query-cache.d.ts.map +1 -0
- memos/_js/vision/clip-query-cache.js +79 -0
- memos/_js/vision/clip-query-cache.js.map +1 -0
- memos/_js/vision/clip.d.ts +113 -0
- memos/_js/vision/clip.d.ts.map +1 -0
- memos/_js/vision/clip.js +224 -0
- memos/_js/vision/clip.js.map +1 -0
- memos/_js/vision/fake-clip.d.ts +55 -0
- memos/_js/vision/fake-clip.d.ts.map +1 -0
- memos/_js/vision/fake-clip.js +122 -0
- memos/_js/vision/fake-clip.js.map +1 -0
- memos/_js/vision/index.d.ts +15 -0
- memos/_js/vision/index.d.ts.map +1 -0
- memos/_js/vision/index.js +13 -0
- memos/_js/vision/index.js.map +1 -0
- memos/_js/vision/store.d.ts +137 -0
- memos/_js/vision/store.d.ts.map +1 -0
- memos/_js/vision/store.js +383 -0
- memos/_js/vision/store.js.map +1 -0
- memos/adapters/__init__.py +0 -0
- memos/adapters/crewai.py +385 -0
- memos/adapters/langchain.py +301 -0
- memos/adapters/ollama.py +396 -0
- memos/server/__init__.py +0 -0
- memos/server/main.py +547 -0
- memos/server/routes.py +360 -0
memos/server/main.py
ADDED
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MemOS Python HTTP Server — FastAPI application entry point.
|
|
3
|
+
|
|
4
|
+
Provides a REST API to the MemOS memory layer, allowing Python
|
|
5
|
+
applications (and non-JS frameworks) to interact with MemOS
|
|
6
|
+
over HTTP. The server wraps a headless Node.js subprocess that
|
|
7
|
+
runs the TypeScript SDK.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
python -m memos.server.main # Default: localhost:7400
|
|
11
|
+
MEMOS_PORT=8080 python -m memos.server.main
|
|
12
|
+
memos-server backup # Backup the database
|
|
13
|
+
memos-server restore <path> # Restore from backup
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import shutil
|
|
21
|
+
import subprocess
|
|
22
|
+
import sys
|
|
23
|
+
from contextlib import asynccontextmanager
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
from fastapi import FastAPI
|
|
27
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
28
|
+
|
|
29
|
+
from .. import __version__
|
|
30
|
+
from .routes import router
|
|
31
|
+
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
# Configuration
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
MEMOS_PORT = int(os.environ.get("MEMOS_PORT", 7400))
|
|
37
|
+
MEMOS_HOST = os.environ.get("MEMOS_HOST", "0.0.0.0")
|
|
38
|
+
MEMOS_DB_PATH = os.environ.get("MEMOS_DB_PATH", str(Path.home() / ".memos" / "memos.db"))
|
|
39
|
+
MEMOS_LOG_LEVEL = os.environ.get("MEMOS_LOG_LEVEL", "info").lower()
|
|
40
|
+
|
|
41
|
+
# better-sqlite3 is a native Node module, so it cannot ship inside the
|
|
42
|
+
# Python wheel — it is installed via npm on first server start when the
|
|
43
|
+
# bundled SDK copy is used (see _ensure_runtime_sdk below).
|
|
44
|
+
_BETTER_SQLITE3_SPEC = "better-sqlite3@^12.11.1"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
# Node.js SDK resolution
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _find_node_binary() -> str:
|
|
53
|
+
"""Locate the Node.js binary on the system."""
|
|
54
|
+
node = shutil.which("node")
|
|
55
|
+
if node is None:
|
|
56
|
+
raise RuntimeError(
|
|
57
|
+
"Node.js not found. Install Node.js >= 18 to run the MemOS server.\n"
|
|
58
|
+
" https://nodejs.org/"
|
|
59
|
+
)
|
|
60
|
+
return node
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _native_binding_present(pkg_dir: Path) -> bool:
|
|
64
|
+
"""True if better-sqlite3's native binding was actually built/installed.
|
|
65
|
+
|
|
66
|
+
A bare ``node_modules/better-sqlite3`` folder is not enough — if npm's
|
|
67
|
+
install scripts were blocked, the folder exists but the ``.node`` binary
|
|
68
|
+
is missing and ``require()`` crashes at runtime.
|
|
69
|
+
"""
|
|
70
|
+
if (pkg_dir / "build" / "Release" / "better_sqlite3.node").is_file():
|
|
71
|
+
return True
|
|
72
|
+
# prebuild-install layout: prebuilds/<platform-arch>/*.node
|
|
73
|
+
prebuilds = pkg_dir / "prebuilds"
|
|
74
|
+
if prebuilds.is_dir():
|
|
75
|
+
for sub in prebuilds.iterdir():
|
|
76
|
+
if sub.is_dir() and any(sub.glob("*.node")):
|
|
77
|
+
return True
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _better_sqlite3_in(node_modules: Path) -> bool:
|
|
82
|
+
"""True if better-sqlite3 sits in THIS node_modules with a native binding.
|
|
83
|
+
|
|
84
|
+
Deliberately does not walk up the directory tree: ancestor node_modules
|
|
85
|
+
(e.g. a stray one in the user's home directory) may hold a broken copy
|
|
86
|
+
that Node would resolve and crash on.
|
|
87
|
+
"""
|
|
88
|
+
pkg = node_modules / "better-sqlite3"
|
|
89
|
+
return pkg.is_dir() and _native_binding_present(pkg)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _ensure_runtime_sdk() -> Path:
|
|
93
|
+
"""Return a directory containing index.js plus a working better-sqlite3.
|
|
94
|
+
|
|
95
|
+
Two layouts are supported:
|
|
96
|
+
|
|
97
|
+
1. **Source checkout** — the compiled ``dist/`` sits below the repo's
|
|
98
|
+
``node_modules`` (from ``npm install``), so it is used directly.
|
|
99
|
+
2. **pip-installed wheel** — the compiled SDK is bundled at
|
|
100
|
+
``memos/_js``. It is staged into ``~/.memos/runtime/js`` and
|
|
101
|
+
better-sqlite3 is npm-installed there on first run (the native
|
|
102
|
+
module cannot ship inside a wheel).
|
|
103
|
+
"""
|
|
104
|
+
here = Path(__file__).resolve().parent # memos/server/
|
|
105
|
+
pkg_root = here.parent # memos/
|
|
106
|
+
repo_root = pkg_root.parent # repository root (dev layout)
|
|
107
|
+
|
|
108
|
+
# Layout 1: source checkout with a working repo-level better-sqlite3
|
|
109
|
+
checkout_dist = repo_root / "dist"
|
|
110
|
+
if (checkout_dist / "index.js").is_file() and _better_sqlite3_in(repo_root / "node_modules"):
|
|
111
|
+
return checkout_dist
|
|
112
|
+
|
|
113
|
+
# Layout 2: bundled wheel SDK
|
|
114
|
+
bundled = pkg_root / "_js"
|
|
115
|
+
if not (bundled / "index.js").is_file():
|
|
116
|
+
raise RuntimeError(
|
|
117
|
+
"MemOS TypeScript SDK not found. Build it first:\n"
|
|
118
|
+
" npm install && npm run build\n"
|
|
119
|
+
"(looked for dist/index.js in the source checkout and the bundled copy)"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Stage the bundled SDK into a writable runtime directory. A version
|
|
123
|
+
# marker keeps the staged copy in sync across package upgrades.
|
|
124
|
+
runtime = Path(
|
|
125
|
+
os.environ.get("MEMOS_RUNTIME_DIR", str(Path.home() / ".memos" / "runtime" / "js"))
|
|
126
|
+
)
|
|
127
|
+
marker = runtime / ".memos-sdk-version"
|
|
128
|
+
needs_copy = (
|
|
129
|
+
not marker.is_file()
|
|
130
|
+
or marker.read_text(encoding="utf-8").strip() != __version__
|
|
131
|
+
or not (runtime / "index.js").is_file()
|
|
132
|
+
)
|
|
133
|
+
if needs_copy:
|
|
134
|
+
runtime.mkdir(parents=True, exist_ok=True)
|
|
135
|
+
for item in bundled.iterdir():
|
|
136
|
+
if item.name == "node_modules":
|
|
137
|
+
continue
|
|
138
|
+
dest = runtime / item.name
|
|
139
|
+
if item.is_dir():
|
|
140
|
+
if dest.exists():
|
|
141
|
+
shutil.rmtree(dest)
|
|
142
|
+
shutil.copytree(item, dest)
|
|
143
|
+
else:
|
|
144
|
+
shutil.copy2(item, dest)
|
|
145
|
+
marker.write_text(__version__, encoding="utf-8")
|
|
146
|
+
|
|
147
|
+
if not _better_sqlite3_in(runtime / "node_modules"):
|
|
148
|
+
npm = shutil.which("npm")
|
|
149
|
+
if npm is None:
|
|
150
|
+
raise RuntimeError(
|
|
151
|
+
"better-sqlite3 is missing and npm was not found to install it.\n"
|
|
152
|
+
f" Run manually: cd {runtime} && npm install {_BETTER_SQLITE3_SPEC}"
|
|
153
|
+
)
|
|
154
|
+
# npm 11+ gates install scripts behind package.json "allowScripts".
|
|
155
|
+
# Without this, better-sqlite3's prebuild-install step is blocked
|
|
156
|
+
# and the native binding never lands. "type": "module" also keeps
|
|
157
|
+
# Node from reparsing the ESM bridge files.
|
|
158
|
+
runtime_pkg = runtime / "package.json"
|
|
159
|
+
if not runtime_pkg.exists():
|
|
160
|
+
runtime_pkg.write_text(
|
|
161
|
+
json.dumps(
|
|
162
|
+
{
|
|
163
|
+
"name": "memos-runtime",
|
|
164
|
+
"private": True,
|
|
165
|
+
"type": "module",
|
|
166
|
+
"allowScripts": {"better-sqlite3": True},
|
|
167
|
+
},
|
|
168
|
+
indent=2,
|
|
169
|
+
),
|
|
170
|
+
encoding="utf-8",
|
|
171
|
+
)
|
|
172
|
+
print(f" First run: installing {_BETTER_SQLITE3_SPEC} into {runtime} ...")
|
|
173
|
+
subprocess.run(
|
|
174
|
+
[npm, "install", "--no-audit", "--no-fund", _BETTER_SQLITE3_SPEC],
|
|
175
|
+
cwd=str(runtime),
|
|
176
|
+
check=True,
|
|
177
|
+
)
|
|
178
|
+
return runtime
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _start_node_server() -> subprocess.Popen:
|
|
182
|
+
"""Start the Node.js MemOS server as a subprocess."""
|
|
183
|
+
sdk_dir = _ensure_runtime_sdk()
|
|
184
|
+
bridge_script = sdk_dir / "_bridge.mjs"
|
|
185
|
+
|
|
186
|
+
# Write bridge script if it doesn't exist
|
|
187
|
+
if not bridge_script.exists():
|
|
188
|
+
bridge_script.write_text(_BRIDGE_SCRIPT, encoding="utf-8")
|
|
189
|
+
|
|
190
|
+
node_bin = _find_node_binary()
|
|
191
|
+
proc = subprocess.Popen(
|
|
192
|
+
[node_bin, str(bridge_script), MEMOS_DB_PATH],
|
|
193
|
+
stdin=subprocess.PIPE,
|
|
194
|
+
stdout=subprocess.PIPE,
|
|
195
|
+
stderr=subprocess.PIPE,
|
|
196
|
+
text=True,
|
|
197
|
+
)
|
|
198
|
+
return proc
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
# ---------------------------------------------------------------------------
|
|
202
|
+
# Lifespan — start/stop Node.js subprocess
|
|
203
|
+
# ---------------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
_node_process: subprocess.Popen | None = None
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@asynccontextmanager
|
|
209
|
+
async def lifespan(app: FastAPI):
|
|
210
|
+
"""Application lifespan — manage the Node.js subprocess."""
|
|
211
|
+
global _node_process
|
|
212
|
+
_node_process = _start_node_server()
|
|
213
|
+
yield
|
|
214
|
+
if _node_process:
|
|
215
|
+
_node_process.terminate()
|
|
216
|
+
try:
|
|
217
|
+
_node_process.wait(timeout=5)
|
|
218
|
+
except subprocess.TimeoutExpired:
|
|
219
|
+
_node_process.kill()
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
# ---------------------------------------------------------------------------
|
|
223
|
+
# App factory
|
|
224
|
+
# ---------------------------------------------------------------------------
|
|
225
|
+
|
|
226
|
+
app = FastAPI(
|
|
227
|
+
title="MemOS",
|
|
228
|
+
description="Universal memory layer for AI agents — REST API",
|
|
229
|
+
version=__version__,
|
|
230
|
+
lifespan=lifespan,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
app.add_middleware(
|
|
234
|
+
CORSMiddleware,
|
|
235
|
+
allow_origins=["*"],
|
|
236
|
+
allow_credentials=True,
|
|
237
|
+
allow_methods=["*"],
|
|
238
|
+
allow_headers=["*"],
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
app.include_router(router, prefix="/api/mem")
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@app.get("/health")
|
|
245
|
+
async def health():
|
|
246
|
+
"""Health check endpoint."""
|
|
247
|
+
return {"status": "ok", "service": "memos", "version": __version__}
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
# ---------------------------------------------------------------------------
|
|
251
|
+
# Entry point
|
|
252
|
+
# ---------------------------------------------------------------------------
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _safe_cli_path(path: str) -> str:
|
|
256
|
+
"""Resolve a CLI file path and require it to stay under the current
|
|
257
|
+
working directory (blocks ../ and absolute-path escapes)."""
|
|
258
|
+
resolved = os.path.realpath(path)
|
|
259
|
+
base = os.path.realpath(os.getcwd())
|
|
260
|
+
try:
|
|
261
|
+
contained = os.path.commonpath([resolved, base]) == base
|
|
262
|
+
except ValueError:
|
|
263
|
+
# Different drives / unreachable roots on Windows
|
|
264
|
+
contained = False
|
|
265
|
+
if not contained:
|
|
266
|
+
print(f"Error: Path must stay inside the working directory: {path}")
|
|
267
|
+
sys.exit(1)
|
|
268
|
+
return resolved
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _cli_backup(output: str | None = None) -> None:
|
|
272
|
+
"""CLI backup subcommand."""
|
|
273
|
+
import json
|
|
274
|
+
import sqlite3
|
|
275
|
+
from datetime import datetime
|
|
276
|
+
|
|
277
|
+
db_path = MEMOS_DB_PATH
|
|
278
|
+
if not os.path.exists(db_path):
|
|
279
|
+
print(f"Error: Database not found at {db_path}")
|
|
280
|
+
sys.exit(1)
|
|
281
|
+
|
|
282
|
+
timestamp = datetime.now().strftime("%Y-%m-%dT%H-%M-%S")
|
|
283
|
+
default_output = f"./memos-backup-{timestamp}.db"
|
|
284
|
+
output_path = _safe_cli_path(output or default_output)
|
|
285
|
+
|
|
286
|
+
shutil.copy2(db_path, output_path)
|
|
287
|
+
|
|
288
|
+
# Get stats
|
|
289
|
+
db_size = os.path.getsize(db_path)
|
|
290
|
+
conn = sqlite3.connect(db_path)
|
|
291
|
+
node_count = conn.execute("SELECT COUNT(*) FROM nodes").fetchone()[0]
|
|
292
|
+
edge_count = conn.execute("SELECT COUNT(*) FROM edges").fetchone()[0]
|
|
293
|
+
conn.close()
|
|
294
|
+
|
|
295
|
+
manifest = {
|
|
296
|
+
"timestamp": datetime.now().isoformat(),
|
|
297
|
+
"version": __version__,
|
|
298
|
+
"nodeCount": node_count,
|
|
299
|
+
"edgeCount": edge_count,
|
|
300
|
+
"dbSizeBytes": db_size,
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
# Rebuild the manifest path from sanitized components; basename()
|
|
304
|
+
# strips any directory the resolved path may still carry.
|
|
305
|
+
manifest_path = _safe_cli_path(
|
|
306
|
+
os.path.join(
|
|
307
|
+
os.path.dirname(output_path),
|
|
308
|
+
os.path.basename(output_path) + ".manifest.json",
|
|
309
|
+
)
|
|
310
|
+
)
|
|
311
|
+
Path(manifest_path).write_text(json.dumps(manifest, indent=2))
|
|
312
|
+
|
|
313
|
+
print(f"Backup created: {output_path}")
|
|
314
|
+
print(f" Nodes: {node_count}, Edges: {edge_count}")
|
|
315
|
+
print(f" DB size: {db_size / 1024:.1f} KB")
|
|
316
|
+
print(f" Manifest: {manifest_path}")
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _cli_restore(path: str) -> None:
|
|
320
|
+
"""CLI restore subcommand."""
|
|
321
|
+
import json
|
|
322
|
+
|
|
323
|
+
backup_path = _safe_cli_path(path)
|
|
324
|
+
manifest_path = f"{backup_path}.manifest.json"
|
|
325
|
+
|
|
326
|
+
if not os.path.exists(backup_path):
|
|
327
|
+
print(f"Error: Backup file not found: {backup_path}")
|
|
328
|
+
sys.exit(1)
|
|
329
|
+
|
|
330
|
+
if os.path.exists(manifest_path):
|
|
331
|
+
with open(manifest_path) as f:
|
|
332
|
+
manifest = json.load(f)
|
|
333
|
+
print("Backup manifest:")
|
|
334
|
+
print(f" Timestamp: {manifest['timestamp']}")
|
|
335
|
+
print(f" Version: {manifest['version']}")
|
|
336
|
+
print(f" Nodes: {manifest['nodeCount']}, Edges: {manifest['edgeCount']}")
|
|
337
|
+
|
|
338
|
+
db_path = MEMOS_DB_PATH
|
|
339
|
+
db_dir = os.path.dirname(db_path)
|
|
340
|
+
if db_dir and not os.path.exists(db_dir):
|
|
341
|
+
os.makedirs(db_dir, exist_ok=True)
|
|
342
|
+
|
|
343
|
+
shutil.copy2(backup_path, db_path)
|
|
344
|
+
|
|
345
|
+
# Report restored state
|
|
346
|
+
import sqlite3
|
|
347
|
+
|
|
348
|
+
conn = sqlite3.connect(db_path)
|
|
349
|
+
node_count = conn.execute("SELECT COUNT(*) FROM nodes").fetchone()[0]
|
|
350
|
+
edge_count = conn.execute("SELECT COUNT(*) FROM edges").fetchone()[0]
|
|
351
|
+
conn.close()
|
|
352
|
+
|
|
353
|
+
print(f"\nRestored from: {backup_path}")
|
|
354
|
+
print(f" Nodes: {node_count}, Edges: {edge_count}")
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def main():
|
|
358
|
+
"""Run the MemOS server with uvicorn, or handle CLI subcommands."""
|
|
359
|
+
# Handle CLI subcommands
|
|
360
|
+
if len(sys.argv) > 1:
|
|
361
|
+
subcmd = sys.argv[1]
|
|
362
|
+
if subcmd == "backup":
|
|
363
|
+
output = None
|
|
364
|
+
if "--output" in sys.argv:
|
|
365
|
+
idx = sys.argv.index("--output")
|
|
366
|
+
if idx + 1 < len(sys.argv):
|
|
367
|
+
output = sys.argv[idx + 1]
|
|
368
|
+
_cli_backup(output)
|
|
369
|
+
return
|
|
370
|
+
elif subcmd == "restore":
|
|
371
|
+
if len(sys.argv) < 3:
|
|
372
|
+
print(
|
|
373
|
+
"Error: restore requires a path argument.\n Usage: memos-server restore <path>"
|
|
374
|
+
)
|
|
375
|
+
sys.exit(1)
|
|
376
|
+
_cli_restore(sys.argv[2])
|
|
377
|
+
return
|
|
378
|
+
|
|
379
|
+
import uvicorn
|
|
380
|
+
|
|
381
|
+
print(f" MemOS server starting on {MEMOS_HOST}:{MEMOS_PORT}")
|
|
382
|
+
print(f" Database: {MEMOS_DB_PATH}")
|
|
383
|
+
print(f" Docs: http://{MEMOS_HOST}:{MEMOS_PORT}/docs")
|
|
384
|
+
|
|
385
|
+
uvicorn.run(
|
|
386
|
+
"memos.server.main:app",
|
|
387
|
+
host=MEMOS_HOST,
|
|
388
|
+
port=MEMOS_PORT,
|
|
389
|
+
log_level=MEMOS_LOG_LEVEL,
|
|
390
|
+
reload=False,
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
if __name__ == "__main__":
|
|
395
|
+
main()
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
# ---------------------------------------------------------------------------
|
|
399
|
+
# Node.js bridge script (inline, written to disk on first run)
|
|
400
|
+
# ---------------------------------------------------------------------------
|
|
401
|
+
|
|
402
|
+
_BRIDGE_SCRIPT = r"""/**
|
|
403
|
+
* Node.js bridge — reads JSON-RPC messages from stdin, executes
|
|
404
|
+
* MemOS operations, and writes results to stdout.
|
|
405
|
+
*
|
|
406
|
+
* This allows the Python server to call the TypeScript SDK
|
|
407
|
+
* without an HTTP round-trip.
|
|
408
|
+
*/
|
|
409
|
+
|
|
410
|
+
import { MemOS } from './index.js';
|
|
411
|
+
|
|
412
|
+
const dbPath = process.argv[2] || undefined;
|
|
413
|
+
|
|
414
|
+
// Embedding configuration via MEMOS_EMBEDDING_* environment variables.
|
|
415
|
+
// Without MEMOS_EMBEDDING_PROVIDER the bridge falls back to the SDK
|
|
416
|
+
// default (local hash), keeping zero-config behavior for plain installs.
|
|
417
|
+
const env = process.env;
|
|
418
|
+
const memosConfig = { dbPath };
|
|
419
|
+
if (env.MEMOS_EMBEDDING_PROVIDER) {
|
|
420
|
+
const dimensions = env.MEMOS_EMBEDDING_DIMENSIONS;
|
|
421
|
+
memosConfig.experimental = { semanticSearch: true };
|
|
422
|
+
memosConfig.embeddings = {
|
|
423
|
+
enabled: true,
|
|
424
|
+
provider: env.MEMOS_EMBEDDING_PROVIDER,
|
|
425
|
+
...(env.MEMOS_EMBEDDING_MODEL ? { model: env.MEMOS_EMBEDDING_MODEL } : {}),
|
|
426
|
+
...(env.MEMOS_EMBEDDING_BASE_URL ? { baseUrl: env.MEMOS_EMBEDDING_BASE_URL } : {}),
|
|
427
|
+
...(env.MEMOS_EMBEDDING_API_KEY ? { apiKey: env.MEMOS_EMBEDDING_API_KEY } : {}),
|
|
428
|
+
...(dimensions ? { dimensions: parseInt(dimensions, 10) } : {}),
|
|
429
|
+
...(env.MEMOS_EMBEDDING_QUERY_PREFIX ? { queryPrefix: env.MEMOS_EMBEDDING_QUERY_PREFIX } : {}),
|
|
430
|
+
...(env.MEMOS_EMBEDDING_DOCUMENT_PREFIX ? { documentPrefix: env.MEMOS_EMBEDDING_DOCUMENT_PREFIX } : {}),
|
|
431
|
+
};
|
|
432
|
+
if (env.MEMOS_EMBEDDING_BATCH_SIZE) {
|
|
433
|
+
memosConfig.embeddingQueue = {
|
|
434
|
+
batchSize: parseInt(env.MEMOS_EMBEDDING_BATCH_SIZE, 10),
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const memos = new MemOS(memosConfig);
|
|
440
|
+
await memos.init();
|
|
441
|
+
|
|
442
|
+
const rl = (await import('readline')).createInterface({
|
|
443
|
+
input: process.stdin,
|
|
444
|
+
output: process.stdout,
|
|
445
|
+
terminal: false,
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
rl.on('line', async (line) => {
|
|
449
|
+
let msg;
|
|
450
|
+
try {
|
|
451
|
+
msg = JSON.parse(line);
|
|
452
|
+
} catch {
|
|
453
|
+
writeResponse({ error: 'Invalid JSON' });
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const { id, method, params } = msg;
|
|
458
|
+
|
|
459
|
+
try {
|
|
460
|
+
let result;
|
|
461
|
+
switch (method) {
|
|
462
|
+
case 'store':
|
|
463
|
+
result = await memos.store(params.content, params.opts || {});
|
|
464
|
+
break;
|
|
465
|
+
case 'retrieve':
|
|
466
|
+
result = await memos.retrieve(params.id);
|
|
467
|
+
break;
|
|
468
|
+
case 'search':
|
|
469
|
+
result = await memos.search(params.filter || params.query);
|
|
470
|
+
break;
|
|
471
|
+
case 'forget':
|
|
472
|
+
result = await memos.forget(params.id);
|
|
473
|
+
break;
|
|
474
|
+
case 'summarize':
|
|
475
|
+
result = await memos.summarize();
|
|
476
|
+
break;
|
|
477
|
+
case 'link':
|
|
478
|
+
result = await memos.link(
|
|
479
|
+
params.sourceId,
|
|
480
|
+
params.targetId,
|
|
481
|
+
params.relation,
|
|
482
|
+
params.weight
|
|
483
|
+
);
|
|
484
|
+
break;
|
|
485
|
+
case 'graph':
|
|
486
|
+
result = await memos.getGraph();
|
|
487
|
+
break;
|
|
488
|
+
case 'neighbours':
|
|
489
|
+
result = await memos.getNeighbours(params.nodeId);
|
|
490
|
+
break;
|
|
491
|
+
case 'edges':
|
|
492
|
+
result = await memos.getEdges(params.nodeId);
|
|
493
|
+
break;
|
|
494
|
+
case 'count':
|
|
495
|
+
result = memos.count;
|
|
496
|
+
break;
|
|
497
|
+
case 'setTTL':
|
|
498
|
+
await memos.setTTL(params.id, params.seconds);
|
|
499
|
+
result = true;
|
|
500
|
+
break;
|
|
501
|
+
case 'clearTTL':
|
|
502
|
+
await memos.clearTTL(params.id);
|
|
503
|
+
result = true;
|
|
504
|
+
break;
|
|
505
|
+
case 'tag':
|
|
506
|
+
await memos.tag(params.id, params.tags);
|
|
507
|
+
result = true;
|
|
508
|
+
break;
|
|
509
|
+
case 'untag':
|
|
510
|
+
await memos.untag(params.id, params.tags);
|
|
511
|
+
result = true;
|
|
512
|
+
break;
|
|
513
|
+
case 'listByTag':
|
|
514
|
+
result = await memos.listByTag(params.tag);
|
|
515
|
+
break;
|
|
516
|
+
case 'export':
|
|
517
|
+
result = await memos.export(params.opts || {});
|
|
518
|
+
break;
|
|
519
|
+
case 'semanticSearch':
|
|
520
|
+
result = await memos.semanticSearch(params.query, params.limit, params.threshold);
|
|
521
|
+
break;
|
|
522
|
+
case 'graphViz':
|
|
523
|
+
result = await memos.graphViz();
|
|
524
|
+
break;
|
|
525
|
+
case 'listNamespaces':
|
|
526
|
+
result = await memos.listNamespaces();
|
|
527
|
+
break;
|
|
528
|
+
case 'namespaceCount':
|
|
529
|
+
result = await memos.namespaceCount(params.namespace);
|
|
530
|
+
break;
|
|
531
|
+
case 'injectContext':
|
|
532
|
+
result = await memos.injectContext(params.id, params.depth, params.maxChars);
|
|
533
|
+
break;
|
|
534
|
+
default:
|
|
535
|
+
writeResponse({ id, error: `Unknown method: ${method}` });
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
writeResponse({ id, result });
|
|
539
|
+
} catch (err) {
|
|
540
|
+
writeResponse({ id, error: err.message || String(err) });
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
function writeResponse(obj) {
|
|
545
|
+
process.stdout.write(JSON.stringify(obj) + '\n');
|
|
546
|
+
}
|
|
547
|
+
"""
|