Add memory_manager source files and tests
Some checks failed
CI / test (push) Has been cancelled
CI / build (push) Has been cancelled

This commit is contained in:
2026-03-22 16:18:58 +00:00
parent a5ee2d1868
commit 71a6e02e04

View File

@@ -1,47 +1,42 @@
"""FastAPI application for memory manager API."""
"""FastAPI REST API for the memory manager."""
import os
from contextlib import asynccontextmanager
from typing import List, Optional
from fastapi import FastAPI, HTTPException, Query
from fastapi.middleware.cors import CORSMiddleware
from memory_manager.core.services import MemoryService, CommitService
from memory_manager.db.repository import MemoryRepository
from memory_manager import __version__
from memory_manager.api.schemas import (
MemoryEntryCreate,
MemoryEntryUpdate,
MemoryEntryResponse,
CommitCreate,
CommitResponse,
DiffResponse,
HealthResponse,
MemoryEntryCreate,
MemoryEntryResponse,
MemoryEntryUpdate,
StatsResponse,
)
from memory_manager.core.services import MemoryManager
from memory_manager.db.models import MemoryCategory
from memory_manager.db.repository import MemoryRepository
repository: Optional[MemoryRepository] = None
memory_service: Optional[MemoryService] = None
commit_service: Optional[CommitService] = None
db_path = os.getenv("MEMORY_DB_PATH", ".memory/codebase_memory.db")
repository = MemoryRepository(db_path)
memory_manager = MemoryManager(repository)
@asynccontextmanager
async def lifespan(app: FastAPI):
global repository, memory_service, commit_service
db_path = os.getenv("MEMORY_DB_PATH", ".memory/codebase_memory.db")
os.makedirs(os.path.dirname(db_path), exist_ok=True)
repository = MemoryRepository(db_path)
await repository.init_db()
memory_service = MemoryService(repository)
commit_service = CommitService(repository)
await memory_manager.initialize()
yield
if repository:
await repository.close()
await memory_manager.close()
app = FastAPI(
title="Agentic Codebase Memory Manager API",
title="Agentic Codebase Memory Manager",
description="A centralized memory store for AI coding agents",
version="0.1.0",
version=__version__,
lifespan=lifespan,
)
@@ -54,141 +49,159 @@ app.add_middleware(
)
def entry_to_response(entry) -> MemoryEntryResponse:
return MemoryEntryResponse(
id=entry.id,
title=entry.title,
content=entry.content,
category=entry.category.value,
tags=entry.tags or [],
agent_id=entry.agent_id,
project_path=entry.project_path,
created_at=entry.created_at,
updated_at=entry.updated_at,
parent_id=entry.parent_id,
)
@app.get("/health", response_model=HealthResponse)
async def health():
return HealthResponse(status="ok", version=__version__)
@app.get("/")
def root():
return {"message": "Agentic Codebase Memory Manager API", "version": "0.1.0"}
@app.get("/api/memory", response_model=List[MemoryEntryResponse])
@app.get("/api/memory", response_model=list[MemoryEntryResponse])
async def list_memory(
category: Optional[str] = Query(None),
agent_id: Optional[str] = Query(None),
limit: int = Query(100, ge=1, le=1000),
offset: int = Query(0, ge=0),
category: str | None = None,
agent_id: str | None = None,
project_path: str | None = None,
limit: int = Query(default=100, ge=1, le=1000),
offset: int = Query(default=0, ge=0),
):
entries = await memory_service.list_entries(
category=category,
category_enum = None
if category:
try:
category_enum = MemoryCategory(category)
except ValueError:
raise HTTPException(status_code=422, detail=f"Invalid category: {category}")
entries = await memory_manager.memory_service.list_entries(
category=category_enum,
agent_id=agent_id,
project_path=project_path,
limit=limit,
offset=offset,
)
return [entry_to_response(e) for e in entries]
return entries
@app.post("/api/memory", response_model=MemoryEntryResponse)
@app.post("/api/memory", response_model=MemoryEntryResponse, status_code=201)
async def create_memory(entry: MemoryEntryCreate):
new_entry = await memory_service.create_entry(
result = await memory_manager.memory_service.create_entry(
title=entry.title,
content=entry.content,
category=entry.category,
tags=entry.tags,
agent_id=entry.agent_id,
project_path=entry.project_path,
parent_id=entry.parent_id,
)
return entry_to_response(new_entry)
return result
@app.get("/api/memory/log", response_model=list[CommitResponse])
async def get_log(
agent_id: str | None = None,
project_path: str | None = None,
limit: int = Query(default=100, ge=1, le=1000),
offset: int = Query(default=0, ge=0),
):
commits = await memory_manager.commit_service.list_commits(
agent_id=agent_id,
project_path=project_path,
limit=limit,
offset=offset,
)
return commits
@app.get("/api/memory/stats", response_model=StatsResponse)
async def get_stats(project_path: str | None = None):
entries = await memory_manager.memory_service.list_entries(
project_path=project_path,
limit=10000,
)
entries_by_category: dict[str, int] = {}
for entry in entries:
cat = entry["category"]
entries_by_category[cat] = entries_by_category.get(cat, 0) + 1
commits = await memory_manager.commit_service.list_commits(
project_path=project_path,
limit=10000,
)
return StatsResponse(
total_entries=len(entries),
entries_by_category=entries_by_category,
total_commits=len(commits),
)
@app.get("/api/memory/search", response_model=list[MemoryEntryResponse])
async def search_memory(
q: str = Query(..., min_length=1),
category: str | None = None,
agent_id: str | None = None,
project_path: str | None = None,
limit: int = Query(default=100, ge=1, le=1000),
):
category_enum = None
if category:
try:
category_enum = MemoryCategory(category)
except ValueError:
raise HTTPException(status_code=422, detail=f"Invalid category: {category}")
results = await memory_manager.search_service.search(
query=q,
category=category_enum,
agent_id=agent_id,
project_path=project_path,
limit=limit,
)
return results
@app.post("/api/memory/commit", response_model=CommitResponse, status_code=201)
async def create_commit(commit: CommitCreate):
result = await memory_manager.commit_service.create_commit(
message=commit.message,
agent_id=commit.agent_id,
project_path=commit.project_path,
)
return result
@app.get("/api/memory/diff/{hash1}/{hash2}", response_model=DiffResponse)
async def get_diff(hash1: str, hash2: str):
diff = await memory_manager.commit_service.diff(hash1, hash2)
if not diff:
raise HTTPException(
status_code=404,
detail=f"Commit(s) not found: {hash1}, {hash2}. Check available commits with /api/memory/log"
)
return diff
@app.get("/api/memory/{entry_id}", response_model=MemoryEntryResponse)
async def get_memory(entry_id: int):
entry = await memory_service.get_entry(entry_id)
entry = await memory_manager.memory_service.get_entry(entry_id)
if not entry:
raise HTTPException(status_code=404, detail="Entry not found")
return entry_to_response(entry)
raise HTTPException(status_code=404, detail=f"Entry {entry_id} not found")
return entry
@app.put("/api/memory/{entry_id}", response_model=MemoryEntryResponse)
async def update_memory(entry_id: int, entry: MemoryEntryUpdate):
updated = await memory_service.update_entry(
result = await memory_manager.memory_service.update_entry(
entry_id=entry_id,
title=entry.title,
content=entry.content,
category=entry.category,
tags=entry.tags,
)
if not updated:
raise HTTPException(status_code=404, detail="Entry not found")
return entry_to_response(updated)
if not result:
raise HTTPException(status_code=404, detail=f"Entry {entry_id} not found")
return result
@app.delete("/api/memory/{entry_id}")
@app.delete("/api/memory/{entry_id}", status_code=204)
async def delete_memory(entry_id: int):
deleted = await memory_service.delete_entry(entry_id)
deleted = await memory_manager.memory_service.delete_entry(entry_id)
if not deleted:
raise HTTPException(status_code=404, detail="Entry not found")
return {"message": "Entry deleted successfully"}
@app.get("/api/memory/search/query", response_model=List[MemoryEntryResponse])
async def search_memory(
q: str = Query(..., min_length=1),
category: Optional[str] = Query(None),
tags: Optional[str] = Query(None),
):
tag_list = tags.split(",") if tags else None
entries = await memory_service.search_entries(
query=q,
category=category,
tags=tag_list,
)
return [entry_to_response(e) for e in entries]
@app.post("/api/memory/commit", response_model=CommitResponse)
async def create_commit(commit: CommitCreate):
new_commit = await commit_service.create_commit(
message=commit.message,
agent_id=commit.agent_id,
project_path=commit.project_path,
)
return CommitResponse(
id=new_commit.id,
hash=new_commit.hash,
message=new_commit.message,
agent_id=new_commit.agent_id,
project_path=new_commit.project_path,
created_at=new_commit.created_at,
)
@app.get("/api/memory/log", response_model=List[CommitResponse])
async def get_log(limit: int = Query(50, ge=1, le=1000)):
commits = await commit_service.get_commits(limit=limit)
return [
CommitResponse(
id=c.id,
hash=c.hash,
message=c.message,
agent_id=c.agent_id,
project_path=c.project_path,
created_at=c.created_at,
)
for c in commits
]
@app.get("/api/memory/diff/{hash1}/{hash2}", response_model=DiffResponse)
async def diff_commits(hash1: str, hash2: str):
diff_result = await commit_service.diff_commits(hash1, hash2)
return DiffResponse(**diff_result)
@app.get("/api/memory/stats", response_model=StatsResponse)
async def get_stats():
stats = await repository.get_stats()
return StatsResponse(**stats)
raise HTTPException(status_code=404, detail=f"Entry {entry_id} not found")