feat: make repository reads alias-aware
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a049a4-ee9f-78e1-9d66-2cb0f9bea3e3
This commit is contained in:
parent
639b9aed08
commit
2e2ae1e5d0
19 changed files with 982 additions and 94 deletions
|
|
@ -1,7 +1,153 @@
|
|||
from datetime import datetime, timezone
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy import or_, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from api.database import get_session
|
||||
from api.models.agent_message import AgentMessage
|
||||
from hub_core.routers.messages import create_messages_router
|
||||
from api.schemas.agent_message import MessageCreate, MessageRead, MessageReply
|
||||
from api.services.repository_aliases import (
|
||||
canonicalize_repository_slug,
|
||||
resolve_repository_slug,
|
||||
)
|
||||
from hub_core.message_identity import resolve_message_reference
|
||||
from hub_core.models.message_identity_alias import MessageIdentityAlias
|
||||
|
||||
router = APIRouter(prefix="/messages", tags=["messages"])
|
||||
|
||||
|
||||
async def _get_message(reference: str, session: AsyncSession) -> AgentMessage:
|
||||
message_id = await resolve_message_reference(
|
||||
session, reference, alias_model=MessageIdentityAlias
|
||||
)
|
||||
if message_id is None:
|
||||
raise HTTPException(
|
||||
status_code=404, detail=f"Message reference {reference!r} not found"
|
||||
)
|
||||
message = await session.get(AgentMessage, message_id)
|
||||
if message is None:
|
||||
raise HTTPException(
|
||||
status_code=404, detail=f"Message reference {reference!r} not found"
|
||||
)
|
||||
return message
|
||||
|
||||
|
||||
@router.post("/", response_model=MessageRead, status_code=status.HTTP_201_CREATED)
|
||||
async def send_message(
|
||||
body: MessageCreate,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> AgentMessage:
|
||||
if body.thread_id and await session.get(AgentMessage, body.thread_id) is None:
|
||||
raise HTTPException(status_code=404, detail=f"Thread root {body.thread_id} not found")
|
||||
payload = body.model_dump()
|
||||
payload["from_agent"] = await canonicalize_repository_slug(session, body.from_agent)
|
||||
payload["to_agent"] = await canonicalize_repository_slug(session, body.to_agent)
|
||||
message = AgentMessage(**payload)
|
||||
session.add(message)
|
||||
await session.commit()
|
||||
await session.refresh(message)
|
||||
return message
|
||||
|
||||
|
||||
@router.get("/", response_model=list[MessageRead])
|
||||
async def list_messages(
|
||||
to_agent: str | None = None,
|
||||
from_agent: str | None = None,
|
||||
unread_only: bool = False,
|
||||
limit: int = 50,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> list[AgentMessage]:
|
||||
query = select(AgentMessage).where(AgentMessage.archived_at.is_(None))
|
||||
if to_agent:
|
||||
resolution = await resolve_repository_slug(session, to_agent, required=False)
|
||||
values = resolution.slug_values if resolution else (to_agent,)
|
||||
query = query.where(
|
||||
or_(AgentMessage.to_agent.in_(values), AgentMessage.to_agent == "broadcast")
|
||||
)
|
||||
if from_agent:
|
||||
resolution = await resolve_repository_slug(session, from_agent, required=False)
|
||||
values = resolution.slug_values if resolution else (from_agent,)
|
||||
query = query.where(AgentMessage.from_agent.in_(values))
|
||||
if unread_only:
|
||||
query = query.where(AgentMessage.read_at.is_(None))
|
||||
result = await session.execute(
|
||||
query.order_by(AgentMessage.created_at.desc()).limit(limit)
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
|
||||
|
||||
@router.get("/thread/{thread_id}", response_model=list[MessageRead])
|
||||
async def get_thread(
|
||||
thread_id: str,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> list[AgentMessage]:
|
||||
resolved = await resolve_message_reference(
|
||||
session, thread_id, alias_model=MessageIdentityAlias
|
||||
)
|
||||
if resolved is None:
|
||||
raise HTTPException(
|
||||
status_code=404, detail=f"Message reference {thread_id!r} not found"
|
||||
)
|
||||
result = await session.execute(
|
||||
select(AgentMessage)
|
||||
.where(or_(AgentMessage.id == resolved, AgentMessage.thread_id == resolved))
|
||||
.order_by(AgentMessage.created_at)
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
|
||||
|
||||
@router.patch("/{message_id}/read", response_model=MessageRead)
|
||||
async def mark_read(
|
||||
message_id: str,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> AgentMessage:
|
||||
message = await _get_message(message_id, session)
|
||||
if message.read_at is None:
|
||||
message.read_at = datetime.now(timezone.utc)
|
||||
await session.commit()
|
||||
await session.refresh(message)
|
||||
return message
|
||||
|
||||
|
||||
@router.patch("/{message_id}/archive", response_model=MessageRead)
|
||||
async def archive_message(
|
||||
message_id: str,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> AgentMessage:
|
||||
message = await _get_message(message_id, session)
|
||||
message.archived_at = datetime.now(timezone.utc)
|
||||
if message.read_at is None:
|
||||
message.read_at = message.archived_at
|
||||
await session.commit()
|
||||
await session.refresh(message)
|
||||
return message
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{message_id}/reply",
|
||||
response_model=MessageRead,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
)
|
||||
async def reply_to_message(
|
||||
message_id: str,
|
||||
body: MessageReply,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> AgentMessage:
|
||||
original = await _get_message(message_id, session)
|
||||
if original.read_at is None:
|
||||
original.read_at = datetime.now(timezone.utc)
|
||||
reply = AgentMessage(
|
||||
from_agent=await canonicalize_repository_slug(session, body.from_agent),
|
||||
to_agent=await canonicalize_repository_slug(session, original.from_agent),
|
||||
subject=f"Re: {original.subject}",
|
||||
body=body.body,
|
||||
thread_id=original.thread_id or original.id,
|
||||
)
|
||||
session.add(reply)
|
||||
await session.commit()
|
||||
await session.refresh(reply)
|
||||
return reply
|
||||
|
||||
router = create_messages_router(get_session, message_model=AgentMessage)
|
||||
|
||||
__all__ = ["router"]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue