memos-engine 0.1.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.
- memos/__init__.py +0 -0
- memos/api/__init__.py +0 -0
- memos/api/main.py +267 -0
- memos/api/schemas.py +118 -0
- memos/cli/__init__.py +0 -0
- memos/cli/main.py +430 -0
- memos/core/__init__.py +0 -0
- memos/core/db.py +293 -0
- memos/core/migrations/0001_init.sql +66 -0
- memos/core/migrations/0002_vec.sql +5 -0
- memos/core/migrations/0003_prompt_version.sql +3 -0
- memos/core/migrations/0004_memory_fts.sql +24 -0
- memos/core/migrations/__init__.py +0 -0
- memos/core/models.py +65 -0
- memos/core/schema.sql +73 -0
- memos/indexer/__init__.py +0 -0
- memos/indexer/base.py +41 -0
- memos/indexer/diff.py +24 -0
- memos/indexer/go.py +209 -0
- memos/indexer/typescript.py +201 -0
- memos/mcp/__init__.py +0 -0
- memos/mcp/server.py +734 -0
- memos/query/__init__.py +0 -0
- memos/query/core.py +689 -0
- memos/search/__init__.py +0 -0
- memos/search/base.py +36 -0
- memos/search/embeddings.py +30 -0
- memos/search/sqlite_vec_store.py +57 -0
- memos_engine-0.1.0.dist-info/METADATA +296 -0
- memos_engine-0.1.0.dist-info/RECORD +33 -0
- memos_engine-0.1.0.dist-info/WHEEL +4 -0
- memos_engine-0.1.0.dist-info/entry_points.txt +2 -0
- memos_engine-0.1.0.dist-info/licenses/LICENSE +21 -0
memos/__init__.py
ADDED
|
File without changes
|
memos/api/__init__.py
ADDED
|
File without changes
|
memos/api/main.py
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from fastapi import FastAPI, HTTPException, Query
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
from memos.api.schemas import (
|
|
8
|
+
CallEdgeResponse,
|
|
9
|
+
ContextResponse,
|
|
10
|
+
DeadImportsResponse,
|
|
11
|
+
DependencyGraphResponse,
|
|
12
|
+
DiffImpactResponse,
|
|
13
|
+
ImportCyclesResponse,
|
|
14
|
+
MemoryCreateRequest,
|
|
15
|
+
MemoryEntryResponse,
|
|
16
|
+
MemoryPruneRequest,
|
|
17
|
+
MemoryPruneResponse,
|
|
18
|
+
MemorySearchResponse,
|
|
19
|
+
ModuleResponse,
|
|
20
|
+
RenameImpactResponse,
|
|
21
|
+
SemanticSearchResponse,
|
|
22
|
+
SymbolResponse,
|
|
23
|
+
UnusedSymbolsResponse,
|
|
24
|
+
)
|
|
25
|
+
from memos.core.db import get_connection, get_project_by_root, run_migrations
|
|
26
|
+
from memos.query.core import (
|
|
27
|
+
add_memory_entry,
|
|
28
|
+
find_calls_by_id,
|
|
29
|
+
find_dead_imports,
|
|
30
|
+
find_import_cycles,
|
|
31
|
+
find_symbol,
|
|
32
|
+
find_unused_symbols,
|
|
33
|
+
get_context,
|
|
34
|
+
get_dependency_graph,
|
|
35
|
+
get_diff_impact,
|
|
36
|
+
get_memory_entries,
|
|
37
|
+
get_module,
|
|
38
|
+
get_rename_impact,
|
|
39
|
+
memory_prune,
|
|
40
|
+
memory_search,
|
|
41
|
+
semantic_search,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class SearchRequest(BaseModel):
|
|
46
|
+
query: str
|
|
47
|
+
top_k: int = 10
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
PROJECT_PATH: str = os.environ.get("MEMOS_PROJECT_PATH", ".")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
app = FastAPI(title="Memory OS API")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _get_conn_and_project():
|
|
57
|
+
root = str(Path(PROJECT_PATH).resolve())
|
|
58
|
+
db_path = str(Path(root) / ".memos" / "memory.db")
|
|
59
|
+
if not Path(db_path).exists():
|
|
60
|
+
raise HTTPException(
|
|
61
|
+
status_code=404,
|
|
62
|
+
detail=f"no .memos/memory.db found at {root} — run 'memos index' first",
|
|
63
|
+
)
|
|
64
|
+
conn = get_connection(db_path)
|
|
65
|
+
run_migrations(conn)
|
|
66
|
+
project = get_project_by_root(conn, root)
|
|
67
|
+
if project is None:
|
|
68
|
+
conn.close()
|
|
69
|
+
raise HTTPException(status_code=404, detail=f"no project found for {root}")
|
|
70
|
+
return conn, project
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@app.get("/symbols", response_model=list[SymbolResponse])
|
|
74
|
+
def api_find_symbol(
|
|
75
|
+
name: str = Query(...),
|
|
76
|
+
kind: str | None = Query(None),
|
|
77
|
+
file: str | None = Query(None),
|
|
78
|
+
):
|
|
79
|
+
conn, project = _get_conn_and_project()
|
|
80
|
+
try:
|
|
81
|
+
return find_symbol(
|
|
82
|
+
conn,
|
|
83
|
+
name,
|
|
84
|
+
kind=kind,
|
|
85
|
+
file_path=file,
|
|
86
|
+
project_id=project.id,
|
|
87
|
+
)
|
|
88
|
+
finally:
|
|
89
|
+
conn.close()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@app.get("/symbols/{symbol_id}/context", response_model=ContextResponse)
|
|
93
|
+
def api_get_context(symbol_id: int):
|
|
94
|
+
conn, _ = _get_conn_and_project()
|
|
95
|
+
try:
|
|
96
|
+
result = get_context(conn, symbol_id)
|
|
97
|
+
if "error" in result:
|
|
98
|
+
raise HTTPException(status_code=404, detail=result["error"])
|
|
99
|
+
return result
|
|
100
|
+
finally:
|
|
101
|
+
conn.close()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@app.get("/symbols/{symbol_id}/rename-impact", response_model=RenameImpactResponse)
|
|
105
|
+
def api_rename_impact(symbol_id: int):
|
|
106
|
+
conn, _ = _get_conn_and_project()
|
|
107
|
+
try:
|
|
108
|
+
result = get_rename_impact(conn, symbol_id)
|
|
109
|
+
if "error" in result:
|
|
110
|
+
raise HTTPException(status_code=404, detail=result["error"])
|
|
111
|
+
return result
|
|
112
|
+
finally:
|
|
113
|
+
conn.close()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@app.get("/dependency-graph", response_model=DependencyGraphResponse)
|
|
117
|
+
def api_dependency_graph():
|
|
118
|
+
conn, project = _get_conn_and_project()
|
|
119
|
+
try:
|
|
120
|
+
return get_dependency_graph(conn, project.id)
|
|
121
|
+
finally:
|
|
122
|
+
conn.close()
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@app.get("/import-cycles", response_model=ImportCyclesResponse)
|
|
126
|
+
def api_import_cycles():
|
|
127
|
+
conn, project = _get_conn_and_project()
|
|
128
|
+
try:
|
|
129
|
+
return {"cycles": find_import_cycles(conn, project.id)}
|
|
130
|
+
finally:
|
|
131
|
+
conn.close()
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@app.get("/unused-symbols", response_model=UnusedSymbolsResponse)
|
|
135
|
+
def api_unused_symbols():
|
|
136
|
+
conn, project = _get_conn_and_project()
|
|
137
|
+
try:
|
|
138
|
+
return {"symbols": find_unused_symbols(conn, project.id)}
|
|
139
|
+
finally:
|
|
140
|
+
conn.close()
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@app.get("/dead-imports", response_model=DeadImportsResponse)
|
|
144
|
+
def api_dead_imports():
|
|
145
|
+
conn, project = _get_conn_and_project()
|
|
146
|
+
try:
|
|
147
|
+
return {"imports": find_dead_imports(conn, project.id)}
|
|
148
|
+
finally:
|
|
149
|
+
conn.close()
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@app.get("/modules/{path:path}/diff-impact", response_model=DiffImpactResponse)
|
|
153
|
+
def api_diff_impact(path: str):
|
|
154
|
+
conn, project = _get_conn_and_project()
|
|
155
|
+
try:
|
|
156
|
+
result = get_diff_impact(conn, path, project.id)
|
|
157
|
+
if "error" in result:
|
|
158
|
+
raise HTTPException(status_code=404, detail=result["error"])
|
|
159
|
+
return result
|
|
160
|
+
finally:
|
|
161
|
+
conn.close()
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@app.get("/symbols/{symbol_id}/calls", response_model=list[CallEdgeResponse])
|
|
165
|
+
def api_find_calls_by_id(
|
|
166
|
+
symbol_id: int,
|
|
167
|
+
direction: str = Query("callers", pattern="^(callers|callees)$"),
|
|
168
|
+
):
|
|
169
|
+
conn, _ = _get_conn_and_project()
|
|
170
|
+
try:
|
|
171
|
+
return find_calls_by_id(conn, symbol_id, direction=direction)
|
|
172
|
+
finally:
|
|
173
|
+
conn.close()
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@app.get("/modules/{path:path}", response_model=ModuleResponse)
|
|
177
|
+
def api_get_module(path: str):
|
|
178
|
+
conn, project = _get_conn_and_project()
|
|
179
|
+
try:
|
|
180
|
+
results = get_module(conn, path, project.id)
|
|
181
|
+
if "error" in results:
|
|
182
|
+
raise HTTPException(status_code=404, detail=results["error"])
|
|
183
|
+
return results
|
|
184
|
+
finally:
|
|
185
|
+
conn.close()
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@app.post("/search/semantic", response_model=SemanticSearchResponse)
|
|
189
|
+
def api_search_semantic(body: SearchRequest):
|
|
190
|
+
conn, project = _get_conn_and_project()
|
|
191
|
+
try:
|
|
192
|
+
results = semantic_search(
|
|
193
|
+
conn,
|
|
194
|
+
body.query,
|
|
195
|
+
top_k=body.top_k,
|
|
196
|
+
project_id=project.id,
|
|
197
|
+
)
|
|
198
|
+
return {"query": body.query, "top_k": body.top_k, "results": results}
|
|
199
|
+
finally:
|
|
200
|
+
conn.close()
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@app.post("/memories", response_model=MemoryEntryResponse)
|
|
204
|
+
def api_create_memory(body: MemoryCreateRequest):
|
|
205
|
+
conn, project = _get_conn_and_project()
|
|
206
|
+
try:
|
|
207
|
+
result = add_memory_entry(
|
|
208
|
+
conn,
|
|
209
|
+
project.id,
|
|
210
|
+
body.content,
|
|
211
|
+
scope_type=body.scope_type,
|
|
212
|
+
scope_id=body.scope_id,
|
|
213
|
+
kind=body.kind,
|
|
214
|
+
source=body.source,
|
|
215
|
+
)
|
|
216
|
+
conn.commit()
|
|
217
|
+
return result
|
|
218
|
+
finally:
|
|
219
|
+
conn.close()
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@app.get("/memories/search", response_model=MemorySearchResponse)
|
|
223
|
+
def api_memory_search(
|
|
224
|
+
query: str = Query(...),
|
|
225
|
+
top_k: int = Query(10),
|
|
226
|
+
):
|
|
227
|
+
conn, project = _get_conn_and_project()
|
|
228
|
+
try:
|
|
229
|
+
results = memory_search(conn, project.id, query, top_k=top_k)
|
|
230
|
+
return {"query": query, "top_k": top_k, "results": results}
|
|
231
|
+
finally:
|
|
232
|
+
conn.close()
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
@app.post("/memories/prune", response_model=MemoryPruneResponse)
|
|
236
|
+
def api_memory_prune(body: MemoryPruneRequest):
|
|
237
|
+
conn, project = _get_conn_and_project()
|
|
238
|
+
try:
|
|
239
|
+
count = memory_prune(
|
|
240
|
+
conn,
|
|
241
|
+
project.id,
|
|
242
|
+
older_than_days=body.older_than_days,
|
|
243
|
+
kind=body.kind,
|
|
244
|
+
apply=body.apply,
|
|
245
|
+
)
|
|
246
|
+
if body.apply:
|
|
247
|
+
conn.commit()
|
|
248
|
+
return {"count": count, "dry_run": not body.apply}
|
|
249
|
+
finally:
|
|
250
|
+
conn.close()
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
@app.get("/memories", response_model=list[MemoryEntryResponse])
|
|
254
|
+
def api_get_memories(
|
|
255
|
+
scope_type: str | None = Query(None),
|
|
256
|
+
scope_id: int | None = Query(None),
|
|
257
|
+
):
|
|
258
|
+
conn, project = _get_conn_and_project()
|
|
259
|
+
try:
|
|
260
|
+
return get_memory_entries(
|
|
261
|
+
conn,
|
|
262
|
+
project.id,
|
|
263
|
+
scope_type=scope_type,
|
|
264
|
+
scope_id=scope_id,
|
|
265
|
+
)
|
|
266
|
+
finally:
|
|
267
|
+
conn.close()
|
memos/api/schemas.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ContextResponse(BaseModel):
|
|
7
|
+
symbol: dict[str, Any]
|
|
8
|
+
callers: list[dict[str, Any]]
|
|
9
|
+
callees: list[dict[str, Any]]
|
|
10
|
+
memories: list[dict[str, Any]]
|
|
11
|
+
summary: dict[str, Any] | None = None
|
|
12
|
+
generation_context: dict[str, Any] | None = None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RenameImpactResponse(BaseModel):
|
|
16
|
+
symbol: dict[str, Any]
|
|
17
|
+
callers: list[dict[str, Any]]
|
|
18
|
+
type_references: list[dict[str, Any]]
|
|
19
|
+
import_references: list[dict[str, Any]]
|
|
20
|
+
warning: str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class DependencyGraphResponse(BaseModel):
|
|
24
|
+
nodes: list[dict[str, Any]]
|
|
25
|
+
edges: list[dict[str, Any]]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ImportCyclesResponse(BaseModel):
|
|
29
|
+
cycles: list[list[str]]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class UnusedSymbolsResponse(BaseModel):
|
|
33
|
+
symbols: list[dict[str, Any]]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class DeadImportsResponse(BaseModel):
|
|
37
|
+
imports: list[dict[str, Any]]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class DiffImpactResponse(BaseModel):
|
|
41
|
+
file: dict[str, Any]
|
|
42
|
+
exported_symbols: list[dict[str, Any]]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class SymbolResponse(BaseModel):
|
|
46
|
+
id: int
|
|
47
|
+
file_id: int
|
|
48
|
+
parent_symbol_id: int | None = None
|
|
49
|
+
name: str
|
|
50
|
+
kind: str
|
|
51
|
+
signature: str | None = None
|
|
52
|
+
start_line: int
|
|
53
|
+
end_line: int
|
|
54
|
+
exported: int
|
|
55
|
+
content_hash: str
|
|
56
|
+
file_path: str
|
|
57
|
+
file_language: str
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class CallEdgeResponse(BaseModel):
|
|
61
|
+
caller_name: str
|
|
62
|
+
caller_kind: str
|
|
63
|
+
caller_symbol_id: int
|
|
64
|
+
callee_name: str
|
|
65
|
+
line: int
|
|
66
|
+
file: str
|
|
67
|
+
file_id: int
|
|
68
|
+
callee_resolved_name: str = ""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class ModuleResponse(BaseModel):
|
|
72
|
+
file: dict[str, Any]
|
|
73
|
+
symbols: list[dict[str, Any]]
|
|
74
|
+
calls: list[dict[str, Any]]
|
|
75
|
+
imports: list[dict[str, Any]]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class SemanticSearchResponse(BaseModel):
|
|
79
|
+
query: str
|
|
80
|
+
top_k: int
|
|
81
|
+
results: list[dict[str, Any]]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class MemorySearchResponse(BaseModel):
|
|
85
|
+
query: str
|
|
86
|
+
top_k: int
|
|
87
|
+
results: list[dict[str, Any]]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class MemoryPruneResponse(BaseModel):
|
|
91
|
+
count: int
|
|
92
|
+
dry_run: bool
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class MemoryPruneRequest(BaseModel):
|
|
96
|
+
older_than_days: int | None = None
|
|
97
|
+
kind: str | None = None
|
|
98
|
+
apply: bool = False
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class MemoryEntryResponse(BaseModel):
|
|
102
|
+
id: int
|
|
103
|
+
project_id: int
|
|
104
|
+
scope_type: str
|
|
105
|
+
scope_id: int | None = None
|
|
106
|
+
kind: str
|
|
107
|
+
content: str
|
|
108
|
+
source: str
|
|
109
|
+
source_hash: str | None = None
|
|
110
|
+
created_at: str
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class MemoryCreateRequest(BaseModel):
|
|
114
|
+
content: str
|
|
115
|
+
scope_type: str = "project"
|
|
116
|
+
scope_id: int | None = None
|
|
117
|
+
kind: str = "note"
|
|
118
|
+
source: str = "agent"
|
memos/cli/__init__.py
ADDED
|
File without changes
|