Skip to content

Commit 95933d5

Browse files
committed
fix (memory): replaced json laods with extractor
1 parent 6da27d2 commit 95933d5

24 files changed

Lines changed: 121 additions & 64 deletions

File tree

src/client/app/api/memories/route.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,4 @@ export const POST = withAuth(async function POST(request, { authHeader }) {
4545
console.error("API Error in /api/memories (POST):", error)
4646
return NextResponse.json({ error: error.message }, { status: 500 })
4747
}
48-
})
48+
})

src/server/main/auth/utils.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from jose.exceptions import JOSEError
1515
from fastapi import HTTPException, status, Depends, WebSocket, WebSocketDisconnect
1616
from fastapi.security import OAuth2PasswordBearer
17+
from json_extractor import JsonExtractor
1718

1819
from main.config import (
1920
ENVIRONMENT, SELF_HOST_AUTH_SECRET,
@@ -122,9 +123,9 @@ async def get_decoded_payload_with_claims(self, token: str = Depends(oauth2_sche
122123
async def ws_authenticate_with_data(self, websocket: WebSocket) -> Optional[Dict]:
123124
try:
124125
auth_message_str = await websocket.receive_text()
125-
auth_message = json.loads(auth_message_str)
126+
auth_message = JsonExtractor.extract_valid_json(auth_message_str)
126127

127-
if auth_message.get("type") != "auth" or not auth_message.get("token"):
128+
if not auth_message or not isinstance(auth_message, dict) or auth_message.get("type") != "auth" or not auth_message.get("token"):
128129
await websocket.send_json({"type": "auth_failure", "message": "Invalid auth message format."})
129130
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
130131
return None
@@ -149,10 +150,6 @@ async def ws_authenticate_with_data(self, websocket: WebSocket) -> Optional[Dict
149150
except WebSocketDisconnect:
150151
print(f"[{datetime.datetime.now()}] [WS_AUTH] WebSocket disconnected during auth.")
151152
return None
152-
except json.JSONDecodeError:
153-
await websocket.send_json({"type": "auth_failure", "message": "Auth message must be JSON."})
154-
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
155-
return None
156153
except HTTPException as e:
157154
await websocket.send_json({"type": "auth_failure", "message": f"Token validation failed: {e.detail}"})
158155
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)

src/server/main/chat/utils.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ def call(self, params: str, **kwargs) -> str:
4141
if isinstance(params, dict):
4242
parsed_params = params
4343
else:
44-
parsed_params = json.loads(params)
44+
parsed_params = JsonExtractor.extract_valid_json(params)
45+
if not parsed_params:
46+
return json.dumps({"status": "failure", "error": "Invalid JSON in params."})
4547
json_string_to_validate = parsed_params.get('json_string', '')
4648
if not json_string_to_validate:
4749
return json.dumps({"status": "failure", "error": "Input json_string is empty."})
@@ -339,12 +341,16 @@ def worker():
339341
def msg_to_str(msg: Dict[str, Any]) -> str:
340342
if msg.get('role') == 'assistant' and msg.get('function_call'):
341343
args_str = msg['function_call'].get('arguments', '')
342-
try: args_pretty = json.dumps(json.loads(args_str), indent=2)
344+
try:
345+
parsed_args = JsonExtractor.extract_valid_json(args_str)
346+
args_pretty = json.dumps(parsed_args, indent=2) if parsed_args else args_str
343347
except: args_pretty = args_str
344348
return f"<tool_code name=\"{msg['function_call'].get('name')}\">\n{args_pretty}\n</tool_code>\n"
345349
elif msg.get('role') == 'function':
346350
content = msg.get('content', '')
347-
try: content_pretty = json.dumps(json.loads(content), indent=2)
351+
try:
352+
parsed_content = JsonExtractor.extract_valid_json(content)
353+
content_pretty = json.dumps(parsed_content, indent=2) if parsed_content else content
348354
except: content_pretty = content
349355
return f"<tool_result tool_name=\"{msg.get('name')}\">\n{content_pretty}\n</tool_result>\n"
350356
elif msg.get('role') == 'assistant' and msg.get('content'):

src/server/main/integrations/utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from main.db import MongoManager
66
from main.auth.utils import aes_encrypt, aes_decrypt
7+
from json_extractor import JsonExtractor
78

89
async def store_encrypted_integration_token(user_id: str, service_name: str, token_data: Dict[str, Any], db_manager: MongoManager) -> bool:
910
"""Encrypts and stores the entire token object for a service."""
@@ -40,7 +41,7 @@ async def get_decrypted_integration_token(user_id: str, service_name: str, db_ma
4041

4142
try:
4243
decrypted_token_str = aes_decrypt(integration_data["encrypted_token"])
43-
return json.loads(decrypted_token_str)
44+
return JsonExtractor.extract_valid_json(decrypted_token_str)
4445
except Exception:
4546
# Handle decryption or JSON parsing errors
4647
return None

src/server/main/memories/routes.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ async def startup_event():
2424

2525
@router.get("/", summary="Get all memories for a user")
2626
async def get_all_memories(
27-
user_id: str = Depends(PermissionChecker(required_permissions=["read:profile"]))
27+
user_id: str = Depends(PermissionChecker(required_permissions=["read:memory"]))
2828
):
2929
pool = None
3030
try:
@@ -55,7 +55,7 @@ async def get_all_memories(
5555

5656
@router.get("/graph", summary="Get memory graph data for a user")
5757
async def get_memory_graph(
58-
user_id: str = Depends(PermissionChecker(required_permissions=["read:profile"]))
58+
user_id: str = Depends(PermissionChecker(required_permissions=["read:memory"]))
5959
):
6060
try:
6161
graph_data = await utils.create_memory_graph(user_id)
@@ -67,7 +67,7 @@ async def get_memory_graph(
6767
@router.post("/", summary="Create a new memory for a user")
6868
async def create_memory(
6969
request: CreateMemoryRequest,
70-
user_id: str = Depends(PermissionChecker(required_permissions=["write:profile"]))
70+
user_id: str = Depends(PermissionChecker(required_permissions=["write:memory"]))
7171
):
7272
try:
7373
result_message = await utils.create_memory(user_id, request.content, request.source)
@@ -80,7 +80,7 @@ async def create_memory(
8080
async def update_memory(
8181
memory_id: int,
8282
request: UpdateMemoryRequest,
83-
user_id: str = Depends(PermissionChecker(required_permissions=["write:profile"]))
83+
user_id: str = Depends(PermissionChecker(required_permissions=["write:memory"]))
8484
):
8585
try:
8686
result_message = await utils.update_memory(user_id, memory_id, request.content)
@@ -94,7 +94,7 @@ async def update_memory(
9494
@router.delete("/{memory_id}", summary="Delete a memory")
9595
async def delete_memory(
9696
memory_id: int,
97-
user_id: str = Depends(PermissionChecker(required_permissions=["write:profile"]))
97+
user_id: str = Depends(PermissionChecker(required_permissions=["write:memory"]))
9898
):
9999
try:
100100
result_message = await utils.delete_memory(user_id, memory_id)

src/server/main/memories/utils.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import google.generativeai as genai
1010
from pgvector.asyncpg import register_vector
11+
from json_extractor import JsonExtractor
1112

1213
from . import db, llm
1314
from .prompts import fact_analysis_user_prompt_template
@@ -110,9 +111,8 @@ async def create_memory(user_id: str, content: str, source: Optional[str] = "man
110111
prompt = fact_analysis_user_prompt_template.format(text=content)
111112
analysis_raw = llm.run_agent_with_prompt(agents["fact_analysis"], prompt)
112113
analysis_cleaned = clean_llm_output(analysis_raw)
113-
try:
114-
analysis = json.loads(analysis_cleaned)
115-
except json.JSONDecodeError:
114+
analysis = JsonExtractor.extract_valid_json(analysis_cleaned)
115+
if not analysis:
116116
logger.error(f"Create memory failed due to analysis JSON error. Output: {analysis_cleaned}")
117117
raise ValueError("Failed to analyze memory content.")
118118
return await _insert_fact_with_analysis(conn, user_id, content, source, analysis)
@@ -130,9 +130,8 @@ async def update_memory(user_id: str, memory_id: int, new_content: str) -> str:
130130
prompt = fact_analysis_user_prompt_template.format(text=new_content)
131131
analysis_raw = llm.run_agent_with_prompt(agents["fact_analysis"], prompt)
132132
analysis_cleaned = clean_llm_output(analysis_raw)
133-
try:
134-
analysis = json.loads(analysis_cleaned)
135-
except json.JSONDecodeError:
133+
analysis = JsonExtractor.extract_valid_json(analysis_cleaned)
134+
if not analysis:
136135
raise ValueError("Failed to analyze updated memory content.")
137136

138137
new_embedding = _get_normalized_embedding(new_content, task_type="RETRIEVAL_DOCUMENT")

src/server/main/tasks/db.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import datetime
33
import uuid
44
import json
5+
from json_extractor import JsonExtractor
56
from typing import Dict, List, Optional
67
import logging
78

@@ -32,10 +33,7 @@ async def create_task(self, user_id: str, task_data: dict) -> dict:
3233

3334
schedule = task_data.get("schedule")
3435
if isinstance(schedule, str):
35-
try:
36-
schedule = json.loads(schedule)
37-
except json.JSONDecodeError:
38-
schedule = None
36+
schedule = JsonExtractor.extract_valid_json(schedule)
3937

4038
task_doc = {
4139
"task_id": task_id,

src/server/mcp_hub/gcal/auth.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from googleapiclient.discovery import build, Resource
1111
from fastmcp import Context
1212
from fastmcp.exceptions import ToolError
13+
from json_extractor import JsonExtractor
1314

1415
from typing import Optional, Dict, Any
1516
from dotenv import load_dotenv
@@ -94,7 +95,9 @@ async def get_google_creds(user_id: str) -> Credentials:
9495

9596
try:
9697
decrypted_creds_str = aes_decrypt(gcal_data["credentials"])
97-
token_info = json.loads(decrypted_creds_str)
98+
token_info = JsonExtractor.extract_valid_json(decrypted_creds_str)
99+
if not token_info:
100+
raise ToolError("Failed to parse decrypted credentials for Google Calendar.")
98101
return Credentials.from_authorized_user_info(token_info)
99102
except Exception as e:
100103
raise ToolError(f"Failed to decrypt or parse default OAuth token for Google Calendar: {e}")

src/server/mcp_hub/gdocs/auth.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from googleapiclient.discovery import build, Resource
1111
from fastmcp import Context
1212
from fastmcp.exceptions import ToolError
13+
from json_extractor import JsonExtractor
1314

1415
from typing import Optional
1516
from dotenv import load_dotenv
@@ -65,7 +66,9 @@ async def get_google_creds(user_id: str) -> Credentials:
6566

6667
try:
6768
decrypted_creds_str = aes_decrypt(gdocs_data["credentials"])
68-
token_info = json.loads(decrypted_creds_str)
69+
token_info = JsonExtractor.extract_valid_json(decrypted_creds_str)
70+
if not token_info:
71+
raise ToolError("Failed to parse decrypted credentials for Google Docs.")
6972
return Credentials.from_authorized_user_info(token_info)
7073
except Exception as e:
7174
raise ToolError(f"Failed to decrypt or parse default OAuth token for Google Docs: {e}")

src/server/mcp_hub/gdrive/auth.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from googleapiclient.discovery import build, Resource
1111
from fastmcp import Context
1212
from fastmcp.exceptions import ToolError
13+
from json_extractor import JsonExtractor
1314

1415
from typing import Optional
1516
from dotenv import load_dotenv
@@ -71,7 +72,9 @@ async def get_google_creds(user_id: str) -> Credentials:
7172

7273
try:
7374
decrypted_creds_str = aes_decrypt(gdrive_data["credentials"])
74-
token_info = json.loads(decrypted_creds_str)
75+
token_info = JsonExtractor.extract_valid_json(decrypted_creds_str)
76+
if not token_info:
77+
raise ToolError("Failed to parse decrypted credentials for Google Drive.")
7578
return Credentials.from_authorized_user_info(token_info)
7679
except Exception as e:
7780
raise ToolError(f"Failed to decrypt or parse default OAuth token for Google Drive: {e}")

0 commit comments

Comments
 (0)