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 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