feat: add session persistence to CLI
- Add save-only endpoint (POST /conversations/{id}/save) for persisting
messages without triggering agent execution
- Add sessions command to list previous conversation sessions
- Add --resume flag to chat command for resuming sessions by ID
- Buffer streamed responses and save after completion
- Update AGENTS.md with session commands and remove outdated limitation
- Add 3 new tests for save endpoint (208 total)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,8 @@ from src.domains.conversations.schemas import (
|
||||
ConversationResponse,
|
||||
CreateConversationRequest,
|
||||
MessageResponse,
|
||||
SaveMessagesRequest,
|
||||
SaveMessagesResponse,
|
||||
)
|
||||
from src.domains.conversations.service import ConversationService
|
||||
from src.shared.auth import require_auth
|
||||
@@ -187,3 +189,51 @@ async def add_message(
|
||||
total_tokens=conversation.total_tokens if conversation else 0,
|
||||
summarized=summarized,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/{conversation_id}/save", response_model=SaveMessagesResponse)
|
||||
@logged()
|
||||
async def save_messages(
|
||||
conversation_id: UUID,
|
||||
request: SaveMessagesRequest,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
user=Depends(require_auth),
|
||||
) -> SaveMessagesResponse:
|
||||
"""
|
||||
Save a user/assistant message pair without triggering agent execution.
|
||||
|
||||
Used by CLI when streaming responses separately via /agents/stream.
|
||||
This allows persisting the exchange after streaming completes.
|
||||
"""
|
||||
service = ConversationService(session)
|
||||
|
||||
# Verify conversation exists and user owns it
|
||||
conversation = await service.get(conversation_id)
|
||||
if not conversation:
|
||||
raise HTTPException(status_code=404, detail="Conversation not found")
|
||||
|
||||
if conversation.user_id != user.id:
|
||||
raise HTTPException(status_code=403, detail="Not authorized")
|
||||
|
||||
# Save user message
|
||||
user_message = await service.add_message(
|
||||
conversation_id=conversation_id,
|
||||
role="user",
|
||||
content=request.user_content,
|
||||
)
|
||||
|
||||
# Save assistant message
|
||||
assistant_message = await service.add_message(
|
||||
conversation_id=conversation_id,
|
||||
role="assistant",
|
||||
content=request.assistant_content,
|
||||
)
|
||||
|
||||
# Get updated conversation for total tokens
|
||||
conversation = await service.get(conversation_id)
|
||||
|
||||
return SaveMessagesResponse(
|
||||
user_message=MessageResponse.model_validate(user_message),
|
||||
assistant_message=MessageResponse.model_validate(assistant_message),
|
||||
total_tokens=conversation.total_tokens if conversation else 0,
|
||||
)
|
||||
|
||||
@@ -21,6 +21,15 @@ class AddMessageRequest(BaseModel):
|
||||
content: str = Field(..., min_length=1, description="Message content")
|
||||
|
||||
|
||||
class SaveMessagesRequest(BaseModel):
|
||||
"""Request to save a message pair without triggering agent execution.
|
||||
|
||||
Used by CLI when streaming responses separately via /agents/stream.
|
||||
"""
|
||||
user_content: str = Field(..., min_length=1, description="User message content")
|
||||
assistant_content: str = Field(..., min_length=1, description="Assistant response content")
|
||||
|
||||
|
||||
# === Response Schemas ===
|
||||
|
||||
class MessageResponse(BaseModel):
|
||||
@@ -77,3 +86,10 @@ class AddMessageResponse(BaseModel):
|
||||
default=False,
|
||||
description="Whether context was summarized due to token limit"
|
||||
)
|
||||
|
||||
|
||||
class SaveMessagesResponse(BaseModel):
|
||||
"""Response after saving messages (no agent execution)."""
|
||||
user_message: MessageResponse
|
||||
assistant_message: MessageResponse
|
||||
total_tokens: int
|
||||
|
||||
Reference in New Issue
Block a user