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/routes.py
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MemOS REST API routes.
|
|
3
|
+
|
|
4
|
+
All endpoints are mounted under `/api/mem` and communicate with
|
|
5
|
+
the Node.js MemOS subprocess via JSON-RPC over stdin/stdout.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import io
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import zipfile
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from fastapi import APIRouter, HTTPException, Query
|
|
17
|
+
from fastapi.responses import StreamingResponse
|
|
18
|
+
from pydantic import BaseModel, Field
|
|
19
|
+
|
|
20
|
+
router = APIRouter()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
# Request / Response Models
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class StoreRequest(BaseModel):
|
|
29
|
+
"""Request body for storing a new memory."""
|
|
30
|
+
|
|
31
|
+
content: str = Field(..., min_length=1, description="Text content to remember.")
|
|
32
|
+
type: str = Field(
|
|
33
|
+
"fact",
|
|
34
|
+
description="Memory type: fact, preference, context, relationship, entity, custom.",
|
|
35
|
+
)
|
|
36
|
+
metadata: dict[str, Any] = Field(default_factory=dict, description="Arbitrary metadata.")
|
|
37
|
+
summary: str | None = Field(None, description="Optional summary. Auto-generated if omitted.")
|
|
38
|
+
importance: float = Field(0.5, ge=0.0, le=1.0, description="Importance score [0, 1].")
|
|
39
|
+
ttl: int | None = Field(None, description="TTL in seconds.")
|
|
40
|
+
tags: list[str] = Field(default_factory=list, description="Custom tags.")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class SearchRequest(BaseModel):
|
|
44
|
+
"""Request body for searching memories."""
|
|
45
|
+
|
|
46
|
+
query: str | None = Field(None, description="Full-text search query.")
|
|
47
|
+
type: str | None = Field(None, description="Filter by memory type.")
|
|
48
|
+
min_importance: float | None = Field(None, ge=0.0, le=1.0)
|
|
49
|
+
max_importance: float | None = Field(None, ge=0.0, le=1.0)
|
|
50
|
+
metadata: dict[str, Any] | None = Field(None, description="Metadata filter.")
|
|
51
|
+
limit: int = Field(20, ge=1, le=1000)
|
|
52
|
+
offset: int = Field(0, ge=0)
|
|
53
|
+
sort_by: str = Field("updated_at", description="Sort field.")
|
|
54
|
+
sort_order: str = Field("desc", description="Sort order: asc or desc.")
|
|
55
|
+
tags: list[str] = Field(default_factory=list, description="Filter by tags (AND logic).")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class LinkRequest(BaseModel):
|
|
59
|
+
"""Request body for creating a manual link between memories."""
|
|
60
|
+
|
|
61
|
+
source_id: str = Field(..., description="Source node ID.")
|
|
62
|
+
target_id: str = Field(..., description="Target node ID.")
|
|
63
|
+
relation: str = Field("relates_to", description="Edge relation type.")
|
|
64
|
+
weight: float = Field(0.5, ge=0.0, le=1.0)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class UpdateRequest(BaseModel):
|
|
68
|
+
"""Request body for updating an existing memory."""
|
|
69
|
+
|
|
70
|
+
content: str | None = None
|
|
71
|
+
summary: str | None = None
|
|
72
|
+
type: str | None = None
|
|
73
|
+
metadata: dict[str, Any] | None = None
|
|
74
|
+
importance: float | None = Field(None, ge=0.0, le=1.0)
|
|
75
|
+
tags: list[str] | None = None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class ForgetRequest(BaseModel):
|
|
79
|
+
"""Request body for forgetting (deleting) a memory."""
|
|
80
|
+
|
|
81
|
+
id: str = Field(..., description="Memory node ID to forget.")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class TtlRequest(BaseModel):
|
|
85
|
+
"""Request body for setting TTL."""
|
|
86
|
+
|
|
87
|
+
seconds: int = Field(..., ge=1, description="TTL in seconds.")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class TagsRequest(BaseModel):
|
|
91
|
+
"""Request body for adding tags."""
|
|
92
|
+
|
|
93
|
+
tags: list[str] = Field(..., min_length=1, description="Tags to add.")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
# Bridge communication
|
|
98
|
+
# ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _get_bridge():
|
|
102
|
+
"""Get the Node.js bridge subprocess from the app state."""
|
|
103
|
+
from .main import _node_process
|
|
104
|
+
|
|
105
|
+
if _node_process is None or _node_process.poll() is not None:
|
|
106
|
+
raise HTTPException(status_code=503, detail="MemOS bridge process not running.")
|
|
107
|
+
return _node_process
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _rpc_call(method: str, params: dict[str, Any] | None = None) -> Any:
|
|
111
|
+
"""Send a JSON-RPC call to the Node.js bridge and return the result."""
|
|
112
|
+
import uuid as _uuid
|
|
113
|
+
|
|
114
|
+
bridge = _get_bridge()
|
|
115
|
+
msg_id = str(_uuid.uuid4())
|
|
116
|
+
payload = json.dumps({"id": msg_id, "method": method, "params": params or {}}) + "\n"
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
bridge.stdin.write(payload)
|
|
120
|
+
bridge.stdin.flush()
|
|
121
|
+
|
|
122
|
+
line = bridge.stdout.readline()
|
|
123
|
+
if not line:
|
|
124
|
+
raise HTTPException(status_code=502, detail="No response from MemOS bridge.")
|
|
125
|
+
|
|
126
|
+
response = json.loads(line.strip())
|
|
127
|
+
|
|
128
|
+
if "error" in response and response["error"]:
|
|
129
|
+
raise HTTPException(status_code=400, detail=response["error"])
|
|
130
|
+
|
|
131
|
+
return response.get("result")
|
|
132
|
+
except BrokenPipeError as err:
|
|
133
|
+
raise HTTPException(status_code=503, detail="MemOS bridge process crashed.") from err
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# ---------------------------------------------------------------------------
|
|
137
|
+
# Endpoints
|
|
138
|
+
# ---------------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@router.post("/store", summary="Store a new memory")
|
|
142
|
+
async def store_memory(req: StoreRequest):
|
|
143
|
+
"""
|
|
144
|
+
Store a new memory node. Automatically generates a summary
|
|
145
|
+
and links related memories when auto-linking is enabled.
|
|
146
|
+
"""
|
|
147
|
+
opts: dict[str, Any] = {
|
|
148
|
+
"type": req.type,
|
|
149
|
+
"metadata": req.metadata,
|
|
150
|
+
"importance": req.importance,
|
|
151
|
+
}
|
|
152
|
+
if req.summary:
|
|
153
|
+
opts["summary"] = req.summary
|
|
154
|
+
if req.ttl is not None:
|
|
155
|
+
opts["ttl"] = req.ttl
|
|
156
|
+
if req.tags:
|
|
157
|
+
opts["tags"] = req.tags
|
|
158
|
+
|
|
159
|
+
result = _rpc_call("store", {"content": req.content, "opts": opts})
|
|
160
|
+
return result
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@router.post("/retrieve", summary="Retrieve a memory by ID")
|
|
164
|
+
async def retrieve_memory(body: dict[str, str]):
|
|
165
|
+
"""Retrieve a single memory node by its unique ID."""
|
|
166
|
+
node_id = body.get("id")
|
|
167
|
+
if not node_id:
|
|
168
|
+
raise HTTPException(status_code=400, detail="Missing 'id' field.")
|
|
169
|
+
result = _rpc_call("retrieve", {"id": node_id})
|
|
170
|
+
if result is None:
|
|
171
|
+
raise HTTPException(status_code=404, detail=f"Memory not found: {node_id}")
|
|
172
|
+
return result
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@router.post("/search", summary="Search memories")
|
|
176
|
+
async def search_memories(req: SearchRequest):
|
|
177
|
+
"""Search memories by text query and/or structured filters."""
|
|
178
|
+
filter_obj: dict[str, Any] = {
|
|
179
|
+
"limit": req.limit,
|
|
180
|
+
"offset": req.offset,
|
|
181
|
+
"sortBy": req.sort_by,
|
|
182
|
+
"sortOrder": req.sort_order,
|
|
183
|
+
}
|
|
184
|
+
if req.query:
|
|
185
|
+
filter_obj["query"] = req.query
|
|
186
|
+
if req.type:
|
|
187
|
+
filter_obj["type"] = req.type
|
|
188
|
+
if req.min_importance is not None:
|
|
189
|
+
filter_obj["minImportance"] = req.min_importance
|
|
190
|
+
if req.max_importance is not None:
|
|
191
|
+
filter_obj["maxImportance"] = req.max_importance
|
|
192
|
+
if req.metadata:
|
|
193
|
+
filter_obj["metadata"] = req.metadata
|
|
194
|
+
if req.tags:
|
|
195
|
+
filter_obj["tags"] = req.tags
|
|
196
|
+
|
|
197
|
+
result = _rpc_call("search", {"filter": filter_obj})
|
|
198
|
+
return result
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@router.post("/forget", summary="Forget (delete) a memory")
|
|
202
|
+
async def forget_memory(req: ForgetRequest):
|
|
203
|
+
"""Permanently remove a memory and all its connected edges."""
|
|
204
|
+
result = _rpc_call("forget", {"id": req.id})
|
|
205
|
+
if not result:
|
|
206
|
+
raise HTTPException(status_code=404, detail=f"Memory not found: {req.id}")
|
|
207
|
+
return {"deleted": True, "id": req.id}
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@router.post("/summarize", summary="Summarize all memories")
|
|
211
|
+
async def summarize_memories():
|
|
212
|
+
"""Generate an extractive summary of all stored memories."""
|
|
213
|
+
result = _rpc_call("summarize")
|
|
214
|
+
return {"summary": result}
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@router.post("/link", summary="Link two memories")
|
|
218
|
+
async def link_memories(req: LinkRequest):
|
|
219
|
+
"""Manually create a link (edge) between two memory nodes."""
|
|
220
|
+
result = _rpc_call(
|
|
221
|
+
"link",
|
|
222
|
+
{
|
|
223
|
+
"sourceId": req.source_id,
|
|
224
|
+
"targetId": req.target_id,
|
|
225
|
+
"relation": req.relation,
|
|
226
|
+
"weight": req.weight,
|
|
227
|
+
},
|
|
228
|
+
)
|
|
229
|
+
return result
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
@router.get("/graph", summary="Get the full memory graph")
|
|
233
|
+
async def get_graph():
|
|
234
|
+
"""Return all nodes and edges in the memory graph."""
|
|
235
|
+
result = _rpc_call("graph")
|
|
236
|
+
return result
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@router.post("/neighbours", summary="Get neighbours of a memory")
|
|
240
|
+
async def get_neighbours(body: dict[str, str]):
|
|
241
|
+
"""Get all nodes directly connected to the given node."""
|
|
242
|
+
node_id = body.get("nodeId")
|
|
243
|
+
if not node_id:
|
|
244
|
+
raise HTTPException(status_code=400, detail="Missing 'nodeId' field.")
|
|
245
|
+
result = _rpc_call("neighbours", {"nodeId": node_id})
|
|
246
|
+
return result
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
@router.get("/count", summary="Memory count")
|
|
250
|
+
async def get_count():
|
|
251
|
+
"""Return the total number of stored memories."""
|
|
252
|
+
result = _rpc_call("count")
|
|
253
|
+
return {"count": result}
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@router.post("/update", summary="Update an existing memory")
|
|
257
|
+
async def update_memory(node_id: str, req: UpdateRequest):
|
|
258
|
+
"""Partially update an existing memory node."""
|
|
259
|
+
params: dict[str, Any] = {"id": node_id}
|
|
260
|
+
if req.content is not None:
|
|
261
|
+
params["content"] = req.content
|
|
262
|
+
if req.summary is not None:
|
|
263
|
+
params["summary"] = req.summary
|
|
264
|
+
if req.type is not None:
|
|
265
|
+
params["type"] = req.type
|
|
266
|
+
if req.metadata is not None:
|
|
267
|
+
params["metadata"] = req.metadata
|
|
268
|
+
if req.importance is not None:
|
|
269
|
+
params["importance"] = req.importance
|
|
270
|
+
if req.tags is not None:
|
|
271
|
+
params["tags"] = req.tags
|
|
272
|
+
|
|
273
|
+
result = _rpc_call("update", params)
|
|
274
|
+
if result is None:
|
|
275
|
+
raise HTTPException(status_code=404, detail=f"Memory not found: {node_id}")
|
|
276
|
+
return result
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
# ---------------------------------------------------------------------------
|
|
280
|
+
# TTL Endpoints
|
|
281
|
+
# ---------------------------------------------------------------------------
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
@router.patch("/mem/{mem_id}/ttl", summary="Set TTL on a memory")
|
|
285
|
+
async def set_ttl(mem_id: str, req: TtlRequest):
|
|
286
|
+
"""Set a time-to-live on a memory node."""
|
|
287
|
+
_rpc_call("setTTL", {"id": mem_id, "seconds": req.seconds})
|
|
288
|
+
return {"id": mem_id, "ttl": req.seconds}
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
@router.delete("/mem/{mem_id}/ttl", summary="Clear TTL on a memory")
|
|
292
|
+
async def clear_ttl(mem_id: str):
|
|
293
|
+
"""Clear the TTL on a memory node."""
|
|
294
|
+
_rpc_call("clearTTL", {"id": mem_id})
|
|
295
|
+
return {"id": mem_id, "ttl": None}
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
# ---------------------------------------------------------------------------
|
|
299
|
+
# Tags Endpoints
|
|
300
|
+
# ---------------------------------------------------------------------------
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
@router.post("/mem/{mem_id}/tags", summary="Add tags to a memory")
|
|
304
|
+
async def add_tags(mem_id: str, req: TagsRequest):
|
|
305
|
+
"""Add tags to a memory node."""
|
|
306
|
+
_rpc_call("tag", {"id": mem_id, "tags": req.tags})
|
|
307
|
+
return {"id": mem_id, "tags": req.tags}
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
@router.delete("/mem/{mem_id}/tags/{tag}", summary="Remove a tag from a memory")
|
|
311
|
+
async def remove_tag(mem_id: str, tag: str):
|
|
312
|
+
"""Remove a tag from a memory node."""
|
|
313
|
+
_rpc_call("untag", {"id": mem_id, "tags": [tag]})
|
|
314
|
+
return {"id": mem_id, "removed": tag}
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
@router.get("/tags/{tag}", summary="List memories by tag")
|
|
318
|
+
async def list_by_tag(tag: str):
|
|
319
|
+
"""List all memories that have a specific tag."""
|
|
320
|
+
result = _rpc_call("listByTag", {"tag": tag})
|
|
321
|
+
return result
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
# ---------------------------------------------------------------------------
|
|
325
|
+
# Export Endpoint
|
|
326
|
+
# ---------------------------------------------------------------------------
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
@router.get("/export", summary="Export memories")
|
|
330
|
+
async def export_memories(
|
|
331
|
+
format: str = Query("json", description="Export format: json, markdown, obsidian"),
|
|
332
|
+
tag: str | None = Query(None, description="Filter by tag"),
|
|
333
|
+
):
|
|
334
|
+
"""Export memories in the specified format. Returns JSON or a zip file."""
|
|
335
|
+
opts: dict[str, Any] = {"format": format}
|
|
336
|
+
if tag:
|
|
337
|
+
opts["tag"] = tag
|
|
338
|
+
|
|
339
|
+
result = _rpc_call("export", {"opts": opts})
|
|
340
|
+
|
|
341
|
+
if format == "json":
|
|
342
|
+
return json.loads(result["data"])
|
|
343
|
+
|
|
344
|
+
# For markdown/obsidian, create a zip file
|
|
345
|
+
export_dir = result["data"]
|
|
346
|
+
buf = io.BytesIO()
|
|
347
|
+
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf:
|
|
348
|
+
if os.path.isdir(export_dir):
|
|
349
|
+
for root, _dirs, files in os.walk(export_dir):
|
|
350
|
+
for fname in files:
|
|
351
|
+
filepath = os.path.join(root, fname)
|
|
352
|
+
arcname = os.path.relpath(filepath, export_dir)
|
|
353
|
+
zf.write(filepath, arcname)
|
|
354
|
+
|
|
355
|
+
buf.seek(0)
|
|
356
|
+
return StreamingResponse(
|
|
357
|
+
buf,
|
|
358
|
+
media_type="application/zip",
|
|
359
|
+
headers={"Content-Disposition": f'attachment; filename="memos-export-{format}.zip"'},
|
|
360
|
+
)
|