Add memory_manager source files and tests
This commit is contained in:
@@ -1,47 +1,42 @@
|
|||||||
"""FastAPI application for memory manager API."""
|
"""FastAPI REST API for the memory manager."""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from fastapi import FastAPI, HTTPException, Query
|
from fastapi import FastAPI, HTTPException, Query
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
from memory_manager.core.services import MemoryService, CommitService
|
from memory_manager import __version__
|
||||||
from memory_manager.db.repository import MemoryRepository
|
|
||||||
from memory_manager.api.schemas import (
|
from memory_manager.api.schemas import (
|
||||||
MemoryEntryCreate,
|
|
||||||
MemoryEntryUpdate,
|
|
||||||
MemoryEntryResponse,
|
|
||||||
CommitCreate,
|
CommitCreate,
|
||||||
CommitResponse,
|
CommitResponse,
|
||||||
DiffResponse,
|
DiffResponse,
|
||||||
|
HealthResponse,
|
||||||
|
MemoryEntryCreate,
|
||||||
|
MemoryEntryResponse,
|
||||||
|
MemoryEntryUpdate,
|
||||||
StatsResponse,
|
StatsResponse,
|
||||||
)
|
)
|
||||||
|
from memory_manager.core.services import MemoryManager
|
||||||
|
from memory_manager.db.models import MemoryCategory
|
||||||
|
from memory_manager.db.repository import MemoryRepository
|
||||||
|
|
||||||
|
db_path = os.getenv("MEMORY_DB_PATH", ".memory/codebase_memory.db")
|
||||||
repository: Optional[MemoryRepository] = None
|
repository = MemoryRepository(db_path)
|
||||||
memory_service: Optional[MemoryService] = None
|
memory_manager = MemoryManager(repository)
|
||||||
commit_service: Optional[CommitService] = None
|
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(app: FastAPI):
|
async def lifespan(app: FastAPI):
|
||||||
global repository, memory_service, commit_service
|
await memory_manager.initialize()
|
||||||
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)
|
|
||||||
yield
|
yield
|
||||||
if repository:
|
await memory_manager.close()
|
||||||
await repository.close()
|
|
||||||
|
|
||||||
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="Agentic Codebase Memory Manager API",
|
title="Agentic Codebase Memory Manager",
|
||||||
description="A centralized memory store for AI coding agents",
|
description="A centralized memory store for AI coding agents",
|
||||||
version="0.1.0",
|
version=__version__,
|
||||||
lifespan=lifespan,
|
lifespan=lifespan,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -54,141 +49,159 @@ app.add_middleware(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def entry_to_response(entry) -> MemoryEntryResponse:
|
@app.get("/health", response_model=HealthResponse)
|
||||||
return MemoryEntryResponse(
|
async def health():
|
||||||
id=entry.id,
|
return HealthResponse(status="ok", version=__version__)
|
||||||
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("/")
|
@app.get("/api/memory", response_model=list[MemoryEntryResponse])
|
||||||
def root():
|
|
||||||
return {"message": "Agentic Codebase Memory Manager API", "version": "0.1.0"}
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/memory", response_model=List[MemoryEntryResponse])
|
|
||||||
async def list_memory(
|
async def list_memory(
|
||||||
category: Optional[str] = Query(None),
|
category: str | None = None,
|
||||||
agent_id: Optional[str] = Query(None),
|
agent_id: str | None = None,
|
||||||
limit: int = Query(100, ge=1, le=1000),
|
project_path: str | None = None,
|
||||||
offset: int = Query(0, ge=0),
|
limit: int = Query(default=100, ge=1, le=1000),
|
||||||
|
offset: int = Query(default=0, ge=0),
|
||||||
):
|
):
|
||||||
entries = await memory_service.list_entries(
|
category_enum = None
|
||||||
category=category,
|
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,
|
agent_id=agent_id,
|
||||||
|
project_path=project_path,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
offset=offset,
|
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):
|
async def create_memory(entry: MemoryEntryCreate):
|
||||||
new_entry = await memory_service.create_entry(
|
result = await memory_manager.memory_service.create_entry(
|
||||||
title=entry.title,
|
title=entry.title,
|
||||||
content=entry.content,
|
content=entry.content,
|
||||||
category=entry.category,
|
category=entry.category,
|
||||||
tags=entry.tags,
|
tags=entry.tags,
|
||||||
agent_id=entry.agent_id,
|
agent_id=entry.agent_id,
|
||||||
project_path=entry.project_path,
|
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)
|
@app.get("/api/memory/{entry_id}", response_model=MemoryEntryResponse)
|
||||||
async def get_memory(entry_id: int):
|
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:
|
if not entry:
|
||||||
raise HTTPException(status_code=404, detail="Entry not found")
|
raise HTTPException(status_code=404, detail=f"Entry {entry_id} not found")
|
||||||
return entry_to_response(entry)
|
return entry
|
||||||
|
|
||||||
|
|
||||||
@app.put("/api/memory/{entry_id}", response_model=MemoryEntryResponse)
|
@app.put("/api/memory/{entry_id}", response_model=MemoryEntryResponse)
|
||||||
async def update_memory(entry_id: int, entry: MemoryEntryUpdate):
|
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,
|
entry_id=entry_id,
|
||||||
title=entry.title,
|
title=entry.title,
|
||||||
content=entry.content,
|
content=entry.content,
|
||||||
category=entry.category,
|
category=entry.category,
|
||||||
tags=entry.tags,
|
tags=entry.tags,
|
||||||
)
|
)
|
||||||
if not updated:
|
if not result:
|
||||||
raise HTTPException(status_code=404, detail="Entry not found")
|
raise HTTPException(status_code=404, detail=f"Entry {entry_id} not found")
|
||||||
return entry_to_response(updated)
|
return result
|
||||||
|
|
||||||
|
|
||||||
@app.delete("/api/memory/{entry_id}")
|
@app.delete("/api/memory/{entry_id}", status_code=204)
|
||||||
async def delete_memory(entry_id: int):
|
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:
|
if not deleted:
|
||||||
raise HTTPException(status_code=404, detail="Entry not found")
|
raise HTTPException(status_code=404, detail=f"Entry {entry_id} 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)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user