yourmemory 1.4.45__tar.gz → 1.4.46__tar.gz

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 (47) hide show
  1. {yourmemory-1.4.45 → yourmemory-1.4.46}/PKG-INFO +2 -1
  2. {yourmemory-1.4.45 → yourmemory-1.4.46}/pyproject.toml +2 -1
  3. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/app.py +2 -1
  4. yourmemory-1.4.46/src/routes/proxy.py +294 -0
  5. {yourmemory-1.4.45 → yourmemory-1.4.46}/yourmemory.egg-info/PKG-INFO +2 -1
  6. {yourmemory-1.4.45 → yourmemory-1.4.46}/yourmemory.egg-info/SOURCES.txt +1 -0
  7. {yourmemory-1.4.45 → yourmemory-1.4.46}/yourmemory.egg-info/requires.txt +1 -0
  8. {yourmemory-1.4.45 → yourmemory-1.4.46}/LICENSE +0 -0
  9. {yourmemory-1.4.45 → yourmemory-1.4.46}/README.md +0 -0
  10. {yourmemory-1.4.45 → yourmemory-1.4.46}/memory_mcp.py +0 -0
  11. {yourmemory-1.4.45 → yourmemory-1.4.46}/setup.cfg +0 -0
  12. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/__init__.py +0 -0
  13. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/db/connection.py +0 -0
  14. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/db/duckdb_schema.sql +0 -0
  15. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/db/migrate.py +0 -0
  16. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/db/schema.sql +0 -0
  17. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/db/sqlite_schema.sql +0 -0
  18. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/graph/__init__.py +0 -0
  19. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/graph/backend.py +0 -0
  20. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/graph/graph_store.py +0 -0
  21. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/graph/neo4j_backend.py +0 -0
  22. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/graph/networkx_backend.py +0 -0
  23. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/graph/svo_extract.py +0 -0
  24. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/jobs/decay_job.py +0 -0
  25. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/routes/__init__.py +0 -0
  26. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/routes/agents.py +0 -0
  27. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/routes/graph_viz.py +0 -0
  28. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/routes/memories.py +0 -0
  29. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/routes/retrieve.py +0 -0
  30. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/routes/ui.py +0 -0
  31. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/services/__init__.py +0 -0
  32. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/services/agent_registry.py +0 -0
  33. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/services/api_keys.py +0 -0
  34. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/services/decay.py +0 -0
  35. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/services/embed.py +0 -0
  36. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/services/extract.py +0 -0
  37. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/services/extract_fallback.py +0 -0
  38. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/services/resolve.py +0 -0
  39. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/services/resolve_fallback.py +0 -0
  40. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/services/retrieve.py +0 -0
  41. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/services/session.py +0 -0
  42. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/services/temporal.py +0 -0
  43. {yourmemory-1.4.45 → yourmemory-1.4.46}/src/services/utils.py +0 -0
  44. {yourmemory-1.4.45 → yourmemory-1.4.46}/tests/test_features.py +0 -0
  45. {yourmemory-1.4.45 → yourmemory-1.4.46}/yourmemory.egg-info/dependency_links.txt +0 -0
  46. {yourmemory-1.4.45 → yourmemory-1.4.46}/yourmemory.egg-info/entry_points.txt +0 -0
  47. {yourmemory-1.4.45 → yourmemory-1.4.46}/yourmemory.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yourmemory
3
- Version: 1.4.45
3
+ Version: 1.4.46
4
4
  Summary: Persistent memory for Claude — Ebbinghaus forgetting curve, semantic deduplication, MCP-native
5
5
  Author: Sachit Misra
6
6
  Author-email: mishrasachit1@gmail.com
@@ -28,6 +28,7 @@ Requires-Dist: spacy<4.0,>=3.8.13; python_version < "3.14"
28
28
  Requires-Dist: networkx>=3.0
29
29
  Requires-Dist: fastapi
30
30
  Requires-Dist: uvicorn[standard]
31
+ Requires-Dist: httpx
31
32
  Provides-Extra: postgres
32
33
  Requires-Dist: psycopg2-binary; extra == "postgres"
33
34
  Requires-Dist: pgvector; extra == "postgres"
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "yourmemory"
7
- version = "1.4.45"
7
+ version = "1.4.46"
8
8
  description = "Persistent memory for Claude — Ebbinghaus forgetting curve, semantic deduplication, MCP-native"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -33,6 +33,7 @@ dependencies = [
33
33
  "networkx>=3.0",
34
34
  "fastapi",
35
35
  "uvicorn[standard]",
36
+ "httpx",
36
37
  ]
37
38
 
38
39
  [project.optional-dependencies]
@@ -7,7 +7,7 @@ from apscheduler.schedulers.asyncio import AsyncIOScheduler
7
7
  from fastapi import FastAPI
8
8
  from fastapi.responses import StreamingResponse
9
9
  from pydantic import BaseModel
10
- from src.routes import memories, retrieve, agents, ui, graph_viz
10
+ from src.routes import memories, retrieve, agents, ui, graph_viz, proxy
11
11
  from src.jobs.decay_job import run as run_decay
12
12
  from src.db.migrate import migrate
13
13
 
@@ -31,6 +31,7 @@ app.include_router(retrieve.router)
31
31
  app.include_router(agents.router)
32
32
  app.include_router(ui.router)
33
33
  app.include_router(graph_viz.router)
34
+ app.include_router(proxy.router)
34
35
 
35
36
 
36
37
  @app.get("/health")
@@ -0,0 +1,294 @@
1
+ import os
2
+ import json
3
+ import getpass
4
+ from fastapi import APIRouter, Request, HTTPException
5
+ from fastapi.responses import StreamingResponse, JSONResponse
6
+ import httpx
7
+
8
+ from src.services.retrieve import retrieve as _retrieve
9
+
10
+ router = APIRouter(prefix="/proxy")
11
+
12
+ OPENAI_BASE = "https://api.openai.com"
13
+ ANTHROPIC_BASE = "https://api.anthropic.com"
14
+
15
+
16
+ def _user_id(request: Request) -> str:
17
+ return (
18
+ request.headers.get("x-yourmemory-user")
19
+ or os.getenv("YOURMEMORY_USER", "")
20
+ or getpass.getuser()
21
+ )
22
+
23
+
24
+ def _memory_block(user_id: str, query: str) -> str:
25
+ try:
26
+ result = _retrieve(user_id, query, top_k=5)
27
+ mems = result.get("memories", [])
28
+ if not mems:
29
+ return ""
30
+ lines = ["[Recalled from YourMemory]"]
31
+ for m in mems:
32
+ lines.append(f"- {m['content']}")
33
+ return "\n".join(lines)
34
+ except Exception:
35
+ return ""
36
+
37
+
38
+ def _last_user_text(messages: list) -> str:
39
+ for m in reversed(messages):
40
+ if m.get("role") == "user":
41
+ c = m.get("content", "")
42
+ if isinstance(c, list):
43
+ return " ".join(p.get("text", "") for p in c if isinstance(p, dict))
44
+ return str(c)
45
+ return ""
46
+
47
+
48
+ def _execute_tool(name: str, args: dict, user_id: str) -> str:
49
+ try:
50
+ if name == "store_memory":
51
+ from src.services.retrieve import retrieve as _r
52
+ from src.db.connection import get_connection
53
+ from src.services.embed import embed
54
+ import numpy as np
55
+ content = args.get("content", "")
56
+ importance = float(args.get("importance", 0.7))
57
+ category = args.get("category", "fact")
58
+ if not content:
59
+ return "error: content required"
60
+ # Use the existing store logic via HTTP to local server to avoid circular imports
61
+ import urllib.request as _ur
62
+ payload = json.dumps({
63
+ "userId": user_id,
64
+ "content": content,
65
+ "importance": importance,
66
+ "category": category,
67
+ }).encode()
68
+ req = _ur.Request("http://localhost:3033/memories", data=payload,
69
+ headers={"Content-Type": "application/json"}, method="POST")
70
+ with _ur.urlopen(req, timeout=5) as resp:
71
+ return resp.read().decode()
72
+
73
+ if name == "update_memory":
74
+ memory_id = args.get("memory_id") or args.get("id")
75
+ new_content = args.get("new_content") or args.get("content", "")
76
+ importance = float(args.get("importance", 0.7))
77
+ if not memory_id or not new_content:
78
+ return "error: memory_id and new_content required"
79
+ import urllib.request as _ur
80
+ payload = json.dumps({
81
+ "userId": user_id,
82
+ "newContent": new_content,
83
+ "importance": importance,
84
+ }).encode()
85
+ req = _ur.Request(f"http://localhost:3033/memories/{memory_id}", data=payload,
86
+ headers={"Content-Type": "application/json"}, method="PUT")
87
+ with _ur.urlopen(req, timeout=5) as resp:
88
+ return resp.read().decode()
89
+
90
+ except Exception as e:
91
+ return f"error: {e}"
92
+ return "ok"
93
+
94
+
95
+ # ── Tool schemas injected into every request ───────────────────────────────────
96
+
97
+ OPENAI_TOOLS = [
98
+ {
99
+ "type": "function",
100
+ "function": {
101
+ "name": "store_memory",
102
+ "description": "Store a new fact about the user or project for future sessions.",
103
+ "parameters": {
104
+ "type": "object",
105
+ "properties": {
106
+ "content": {"type": "string", "description": "One sentence fact to remember."},
107
+ "importance": {"type": "number", "description": "0.0–1.0. Use 0.9 for core identity, 0.7 for preferences, 0.5 for regular facts."},
108
+ "category": {"type": "string", "enum": ["fact", "strategy", "assumption", "failure"]},
109
+ },
110
+ "required": ["content", "importance"],
111
+ },
112
+ },
113
+ },
114
+ {
115
+ "type": "function",
116
+ "function": {
117
+ "name": "update_memory",
118
+ "description": "Update or correct an existing memory by ID.",
119
+ "parameters": {
120
+ "type": "object",
121
+ "properties": {
122
+ "memory_id": {"type": "string", "description": "ID of the memory to update."},
123
+ "new_content": {"type": "string", "description": "Replacement content."},
124
+ "importance": {"type": "number"},
125
+ },
126
+ "required": ["memory_id", "new_content", "importance"],
127
+ },
128
+ },
129
+ },
130
+ ]
131
+
132
+ ANTHROPIC_TOOLS = [
133
+ {
134
+ "name": "store_memory",
135
+ "description": "Store a new fact about the user or project for future sessions.",
136
+ "input_schema": {
137
+ "type": "object",
138
+ "properties": {
139
+ "content": {"type": "string"},
140
+ "importance": {"type": "number"},
141
+ "category": {"type": "string", "enum": ["fact", "strategy", "assumption", "failure"]},
142
+ },
143
+ "required": ["content", "importance"],
144
+ },
145
+ },
146
+ {
147
+ "name": "update_memory",
148
+ "description": "Update or correct an existing memory by ID.",
149
+ "input_schema": {
150
+ "type": "object",
151
+ "properties": {
152
+ "memory_id": {"type": "string"},
153
+ "new_content": {"type": "string"},
154
+ "importance": {"type": "number"},
155
+ },
156
+ "required": ["memory_id", "new_content", "importance"],
157
+ },
158
+ },
159
+ ]
160
+
161
+
162
+ # ── OpenAI-compatible proxy ────────────────────────────────────────────────────
163
+
164
+ @router.post("/openai/v1/chat/completions")
165
+ async def proxy_openai(request: Request):
166
+ auth = request.headers.get("authorization", "")
167
+ if not auth.startswith("Bearer "):
168
+ raise HTTPException(status_code=401, detail="Missing OpenAI API key in Authorization header")
169
+
170
+ body = await request.json()
171
+ uid = _user_id(request)
172
+ messages = body.get("messages", [])
173
+ block = _memory_block(uid, _last_user_text(messages))
174
+
175
+ if block:
176
+ if messages and messages[0].get("role") == "system":
177
+ messages[0]["content"] = block + "\n\n" + messages[0]["content"]
178
+ else:
179
+ messages.insert(0, {"role": "system", "content": block})
180
+ body["messages"] = messages
181
+
182
+ # Merge memory tools with any existing tools
183
+ existing_tools = body.get("tools", [])
184
+ body["tools"] = existing_tools + OPENAI_TOOLS
185
+ if body.get("tool_choice") is None:
186
+ body["tool_choice"] = "auto"
187
+
188
+ headers = {"Authorization": auth, "Content-Type": "application/json"}
189
+ stream = body.get("stream", False)
190
+
191
+ async with httpx.AsyncClient(timeout=120) as client:
192
+ if stream:
193
+ # For streaming: pass through — tool calls still appear in stream chunks
194
+ # but we can't intercept mid-stream easily. Model stores via next turn.
195
+ async def _stream():
196
+ async with client.stream("POST", f"{OPENAI_BASE}/v1/chat/completions",
197
+ headers=headers, json=body) as r:
198
+ async for chunk in r.aiter_bytes():
199
+ yield chunk
200
+ return StreamingResponse(_stream(), media_type="text/event-stream")
201
+
202
+ # Non-streaming: intercept tool calls and execute them
203
+ r = await client.post(f"{OPENAI_BASE}/v1/chat/completions", headers=headers, json=body)
204
+ data = r.json()
205
+
206
+ choice = (data.get("choices") or [{}])[0]
207
+ msg = choice.get("message", {})
208
+ tool_calls = msg.get("tool_calls", [])
209
+
210
+ if tool_calls:
211
+ # Execute each memory tool call
212
+ messages.append(msg)
213
+ for tc in tool_calls:
214
+ fn = tc.get("function", {})
215
+ name = fn.get("name", "")
216
+ args = json.loads(fn.get("arguments", "{}"))
217
+ if name in ("store_memory", "update_memory"):
218
+ result = _execute_tool(name, args, uid)
219
+ messages.append({
220
+ "role": "tool",
221
+ "tool_call_id": tc["id"],
222
+ "content": result,
223
+ })
224
+
225
+ # Second call to get the final response
226
+ body["messages"] = messages
227
+ body.pop("tools", None)
228
+ body.pop("tool_choice", None)
229
+ r2 = await client.post(f"{OPENAI_BASE}/v1/chat/completions", headers=headers, json=body)
230
+ return JSONResponse(content=r2.json(), status_code=r2.status_code)
231
+
232
+ return JSONResponse(content=data, status_code=r.status_code)
233
+
234
+
235
+ # ── Anthropic-compatible proxy ─────────────────────────────────────────────────
236
+
237
+ @router.post("/anthropic/v1/messages")
238
+ async def proxy_anthropic(request: Request):
239
+ api_key = request.headers.get("x-api-key", "")
240
+ if not api_key:
241
+ raise HTTPException(status_code=401, detail="Missing Anthropic API key in x-api-key header")
242
+
243
+ body = await request.json()
244
+ uid = _user_id(request)
245
+ messages = body.get("messages", [])
246
+ block = _memory_block(uid, _last_user_text(messages))
247
+
248
+ if block:
249
+ existing = body.get("system", "")
250
+ body["system"] = block + ("\n\n" + existing if existing else "")
251
+
252
+ body["tools"] = body.get("tools", []) + ANTHROPIC_TOOLS
253
+
254
+ headers = {
255
+ "x-api-key": api_key,
256
+ "anthropic-version": request.headers.get("anthropic-version", "2023-06-01"),
257
+ "content-type": "application/json",
258
+ }
259
+ stream = body.get("stream", False)
260
+
261
+ async with httpx.AsyncClient(timeout=120) as client:
262
+ if stream:
263
+ async def _stream():
264
+ async with client.stream("POST", f"{ANTHROPIC_BASE}/v1/messages",
265
+ headers=headers, json=body) as r:
266
+ async for chunk in r.aiter_bytes():
267
+ yield chunk
268
+ return StreamingResponse(_stream(), media_type="text/event-stream")
269
+
270
+ r = await client.post(f"{ANTHROPIC_BASE}/v1/messages", headers=headers, json=body)
271
+ data = r.json()
272
+
273
+ # Intercept tool use blocks
274
+ content_blocks = data.get("content", [])
275
+ tool_uses = [b for b in content_blocks if b.get("type") == "tool_use"
276
+ and b.get("name") in ("store_memory", "update_memory")]
277
+
278
+ if tool_uses:
279
+ messages.append({"role": "assistant", "content": content_blocks})
280
+ tool_results = []
281
+ for tu in tool_uses:
282
+ result = _execute_tool(tu["name"], tu.get("input", {}), uid)
283
+ tool_results.append({
284
+ "type": "tool_result",
285
+ "tool_use_id": tu["id"],
286
+ "content": result,
287
+ })
288
+ messages.append({"role": "user", "content": tool_results})
289
+ body["messages"] = messages
290
+ body.pop("tools", None)
291
+ r2 = await client.post(f"{ANTHROPIC_BASE}/v1/messages", headers=headers, json=body)
292
+ return JSONResponse(content=r2.json(), status_code=r2.status_code)
293
+
294
+ return JSONResponse(content=data, status_code=r.status_code)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yourmemory
3
- Version: 1.4.45
3
+ Version: 1.4.46
4
4
  Summary: Persistent memory for Claude — Ebbinghaus forgetting curve, semantic deduplication, MCP-native
5
5
  Author: Sachit Misra
6
6
  Author-email: mishrasachit1@gmail.com
@@ -28,6 +28,7 @@ Requires-Dist: spacy<4.0,>=3.8.13; python_version < "3.14"
28
28
  Requires-Dist: networkx>=3.0
29
29
  Requires-Dist: fastapi
30
30
  Requires-Dist: uvicorn[standard]
31
+ Requires-Dist: httpx
31
32
  Provides-Extra: postgres
32
33
  Requires-Dist: psycopg2-binary; extra == "postgres"
33
34
  Requires-Dist: pgvector; extra == "postgres"
@@ -20,6 +20,7 @@ src/routes/__init__.py
20
20
  src/routes/agents.py
21
21
  src/routes/graph_viz.py
22
22
  src/routes/memories.py
23
+ src/routes/proxy.py
23
24
  src/routes/retrieve.py
24
25
  src/routes/ui.py
25
26
  src/services/__init__.py
@@ -8,6 +8,7 @@ apscheduler
8
8
  networkx>=3.0
9
9
  fastapi
10
10
  uvicorn[standard]
11
+ httpx
11
12
 
12
13
  [:python_version < "3.14"]
13
14
  spacy<4.0,>=3.8.13
File without changes
File without changes
File without changes
File without changes
File without changes