feat: integrate invite API and improve notification handling
- Add invite code display and binding functionality via API - Fix notification unread count sync on auth state change - Improve notification mark read with server state validation - Add auth state listener to trigger notification refresh - Add YaoCoinConverter for coin-to-yao type conversion - Remove YaoLegend from divination screens (UI cleanup) - Abbreviate relation labels in yao detail view - Add re-register notice to account delete screen - Update 'coins' terminology to 'points' in localization - Fix backend points consumption to only run in CHAT mode - Add HttpxAuthNoiseFilter to suppress auth endpoint logging - Fix notification static_schema import path - Add test coverage for notification bloc error handling - Update AGENTS.md page header rules and image handling - Delete deprecated run-dev.sh script
This commit is contained in:
@@ -170,7 +170,7 @@ class AgentRepository:
|
||||
session_row.last_activity_at = datetime.now(timezone.utc)
|
||||
await self._session.flush()
|
||||
|
||||
async def get_user_message_count(self, *, session_id: str) -> int:
|
||||
async def get_assistant_message_count(self, *, session_id: str) -> int:
|
||||
try:
|
||||
session_uuid = UUID(session_id)
|
||||
except ValueError as exc:
|
||||
@@ -184,7 +184,7 @@ class AgentRepository:
|
||||
select(func.count(AgentChatMessage.id))
|
||||
.where(AgentChatMessage.session_id == session_uuid)
|
||||
.where(AgentChatMessage.deleted_at.is_(None))
|
||||
.where(AgentChatMessage.role == AgentChatMessageRole.USER)
|
||||
.where(AgentChatMessage.role == AgentChatMessageRole.ASSISTANT)
|
||||
)
|
||||
count = (await self._session.execute(stmt)).scalar_one()
|
||||
return int(count)
|
||||
@@ -266,7 +266,11 @@ class AgentRepository:
|
||||
).scalar_one_or_none() is not None
|
||||
snapshot_messages: list[dict[str, object]] = []
|
||||
for message in messages:
|
||||
snapshot_messages.append(await self._to_snapshot_message(message))
|
||||
snapshot_messages.append(
|
||||
(await self._to_chat_message_schema(message)).model_dump(
|
||||
mode="json", by_alias=True, exclude_none=True
|
||||
)
|
||||
)
|
||||
return {
|
||||
"day": target_day.isoformat(),
|
||||
"hasMore": has_more,
|
||||
@@ -278,7 +282,7 @@ class AgentRepository:
|
||||
*,
|
||||
session_id: str,
|
||||
visibility_mask: int | None = None,
|
||||
) -> list[dict[str, object]]:
|
||||
) -> list[AgentChatMessageSchema]:
|
||||
try:
|
||||
session_uuid = UUID(session_id)
|
||||
except ValueError as exc:
|
||||
@@ -299,9 +303,9 @@ class AgentRepository:
|
||||
visibility_mask=visibility_mask,
|
||||
)
|
||||
messages = (await self._session.execute(message_stmt)).scalars().all()
|
||||
snapshot_messages: list[dict[str, object]] = []
|
||||
snapshot_messages: list[AgentChatMessageSchema] = []
|
||||
for message in messages:
|
||||
snapshot_messages.append(await self._to_snapshot_message(message))
|
||||
snapshot_messages.append(await self._to_chat_message_schema(message))
|
||||
return snapshot_messages
|
||||
|
||||
async def get_recent_messages_by_user_window(
|
||||
@@ -352,7 +356,11 @@ class AgentRepository:
|
||||
selected = list(reversed(selected_desc))
|
||||
snapshot_messages: list[dict[str, object]] = []
|
||||
for message in selected:
|
||||
snapshot_messages.append(await self._to_snapshot_message(message))
|
||||
snapshot_messages.append(
|
||||
(await self._to_chat_message_schema(message)).model_dump(
|
||||
mode="json", by_alias=True, exclude_none=True
|
||||
)
|
||||
)
|
||||
return snapshot_messages
|
||||
|
||||
async def get_latest_session_id_for_user(self, *, user_id: str) -> str | None:
|
||||
@@ -382,7 +390,7 @@ class AgentRepository:
|
||||
user_id: str,
|
||||
visibility_mask: int | None = None,
|
||||
session_limit: int = 50,
|
||||
) -> list[dict[str, object]]:
|
||||
) -> list[AgentChatMessageSchema]:
|
||||
try:
|
||||
user_uuid = UUID(user_id)
|
||||
except ValueError as exc:
|
||||
@@ -404,7 +412,7 @@ class AgentRepository:
|
||||
if not session_ids:
|
||||
return []
|
||||
|
||||
snapshots: list[dict[str, object]] = []
|
||||
snapshots: list[AgentChatMessageSchema] = []
|
||||
for session_id in session_ids:
|
||||
message_stmt = (
|
||||
select(AgentChatMessage)
|
||||
@@ -423,10 +431,14 @@ class AgentRepository:
|
||||
)
|
||||
if not candidate_messages:
|
||||
continue
|
||||
selected_snapshot: dict[str, object] | None = None
|
||||
selected_snapshot: AgentChatMessageSchema | None = None
|
||||
for message in candidate_messages:
|
||||
snapshot = await self._to_snapshot_message(message)
|
||||
metadata = snapshot.get("metadata")
|
||||
snapshot = await self._to_chat_message_schema(message)
|
||||
metadata = (
|
||||
snapshot.metadata.model_dump(mode="json", exclude_none=True)
|
||||
if snapshot.metadata is not None
|
||||
else None
|
||||
)
|
||||
if not isinstance(metadata, dict):
|
||||
continue
|
||||
agent_output = metadata.get("agent_output")
|
||||
@@ -440,7 +452,7 @@ class AgentRepository:
|
||||
snapshots.append(selected_snapshot)
|
||||
|
||||
snapshots.sort(
|
||||
key=lambda item: str(item.get("timestamp") or ""),
|
||||
key=lambda item: str(item.timestamp),
|
||||
reverse=True,
|
||||
)
|
||||
return snapshots
|
||||
@@ -462,9 +474,9 @@ class AgentRepository:
|
||||
"config": config_payload,
|
||||
}
|
||||
|
||||
async def _to_snapshot_message(
|
||||
async def _to_chat_message_schema(
|
||||
self, message: AgentChatMessage
|
||||
) -> dict[str, object]:
|
||||
) -> AgentChatMessageSchema:
|
||||
role = (
|
||||
message.role.value
|
||||
if isinstance(message.role, AgentChatMessageRole)
|
||||
@@ -487,7 +499,7 @@ class AgentRepository:
|
||||
"timestamp": message.created_at.astimezone(timezone.utc).isoformat(),
|
||||
}
|
||||
)
|
||||
return payload_model.model_dump(mode="json", exclude_none=True)
|
||||
return payload_model
|
||||
|
||||
def _apply_visibility_filter(
|
||||
self,
|
||||
|
||||
@@ -8,6 +8,7 @@ from uuid import UUID
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from schemas.agent.runtime_models import ErrorInfo
|
||||
from schemas.domain.chat_message import AgentChatMessage
|
||||
from schemas.domain.divination import DerivedDivinationData
|
||||
|
||||
|
||||
@@ -37,7 +38,7 @@ class AgentRepositoryLike(Protocol):
|
||||
*,
|
||||
session_id: str,
|
||||
visibility_mask: int | None = None,
|
||||
) -> list[dict[str, object]]: ...
|
||||
) -> list[AgentChatMessage]: ...
|
||||
|
||||
async def get_latest_session_id_for_user(self, *, user_id: str) -> str | None: ...
|
||||
|
||||
@@ -47,7 +48,7 @@ class AgentRepositoryLike(Protocol):
|
||||
user_id: str,
|
||||
visibility_mask: int | None = None,
|
||||
session_limit: int = 50,
|
||||
) -> list[dict[str, object]]: ...
|
||||
) -> list[AgentChatMessage]: ...
|
||||
|
||||
async def persist_user_message(
|
||||
self,
|
||||
@@ -58,7 +59,7 @@ class AgentRepositoryLike(Protocol):
|
||||
visibility_mask: int,
|
||||
) -> None: ...
|
||||
|
||||
async def get_user_message_count(self, *, session_id: str) -> int: ...
|
||||
async def get_assistant_message_count(self, *, session_id: str) -> int: ...
|
||||
|
||||
async def get_system_agent_config(
|
||||
self, *, agent_type: str
|
||||
|
||||
@@ -46,7 +46,7 @@ from v1.agent.utils import (
|
||||
)
|
||||
|
||||
logger = get_logger(__name__)
|
||||
MAX_RUNS_PER_SESSION = 2
|
||||
MAX_ASSISTANT_MESSAGES_PER_SESSION = 2
|
||||
|
||||
|
||||
def ensure_session_owner(*, owner_id: str, current_user: CurrentUser) -> None:
|
||||
@@ -151,6 +151,7 @@ class AgentService:
|
||||
await self._enforce_run_preconditions(
|
||||
thread_id=thread_id,
|
||||
current_user=current_user,
|
||||
runtime_mode=runtime_mode,
|
||||
)
|
||||
except ApiProblemError:
|
||||
if created:
|
||||
@@ -247,7 +248,7 @@ class AgentService:
|
||||
metadata: AgentChatMessageMetadata | None,
|
||||
) -> None:
|
||||
metadata_payload = (
|
||||
metadata.model_dump(mode="json", exclude_none=True)
|
||||
metadata.model_dump(mode="json", by_alias=True, exclude_none=True)
|
||||
if isinstance(metadata, AgentChatMessageMetadata)
|
||||
else None
|
||||
)
|
||||
@@ -494,19 +495,23 @@ class AgentService:
|
||||
*,
|
||||
thread_id: str,
|
||||
current_user: CurrentUser,
|
||||
runtime_mode: RuntimeMode,
|
||||
) -> None:
|
||||
await self._points_service.ensure_run_points_available(user_id=current_user.id)
|
||||
if runtime_mode == RuntimeMode.CHAT:
|
||||
await self._points_service.ensure_run_points_available(
|
||||
user_id=current_user.id
|
||||
)
|
||||
|
||||
user_message_count = await self._repository.get_user_message_count(
|
||||
assistant_message_count = await self._repository.get_assistant_message_count(
|
||||
session_id=thread_id
|
||||
)
|
||||
if user_message_count >= MAX_RUNS_PER_SESSION:
|
||||
if assistant_message_count >= MAX_ASSISTANT_MESSAGES_PER_SESSION:
|
||||
raise ApiProblemError(
|
||||
status_code=409,
|
||||
detail=problem_payload(
|
||||
code="AGENT_SESSION_RUN_LIMIT_EXCEEDED",
|
||||
detail="Session run limit exceeded",
|
||||
params={"maxRuns": MAX_RUNS_PER_SESSION},
|
||||
params={"maxRuns": MAX_ASSISTANT_MESSAGES_PER_SESSION},
|
||||
),
|
||||
)
|
||||
|
||||
@@ -597,7 +602,6 @@ class AgentService:
|
||||
thread_id: str,
|
||||
current_user: CurrentUser,
|
||||
) -> HistorySnapshotResponse:
|
||||
from schemas.domain.chat_message import AgentChatMessage
|
||||
from v1.agent.utils import convert_message_to_history
|
||||
from v1.agent.schemas import HistoryMessage
|
||||
|
||||
@@ -609,11 +613,9 @@ class AgentService:
|
||||
)
|
||||
|
||||
messages: list[HistoryMessage] = []
|
||||
for msg_dict in raw_messages:
|
||||
msg = AgentChatMessage.model_validate(msg_dict)
|
||||
if msg.role == "tool":
|
||||
for msg in raw_messages:
|
||||
if msg.role not in {"user", "assistant"}:
|
||||
continue
|
||||
|
||||
signed_urls: dict[str, str] = {}
|
||||
attachments = extract_user_message_attachments(msg.metadata)
|
||||
if self._attachment_storage and attachments:
|
||||
@@ -653,7 +655,6 @@ class AgentService:
|
||||
current_user: CurrentUser,
|
||||
thread_id: str | None,
|
||||
) -> HistorySnapshotResponse:
|
||||
from schemas.domain.chat_message import AgentChatMessage
|
||||
from v1.agent.utils import convert_message_to_history
|
||||
from v1.agent.schemas import HistoryMessage
|
||||
|
||||
@@ -675,8 +676,9 @@ class AgentService:
|
||||
visible_messages = raw_messages[:summary_limit]
|
||||
|
||||
messages: list[HistoryMessage] = []
|
||||
for msg_dict in visible_messages:
|
||||
msg = AgentChatMessage.model_validate(msg_dict)
|
||||
for msg in visible_messages:
|
||||
if msg.role != "assistant":
|
||||
continue
|
||||
converted = convert_message_to_history(msg)
|
||||
messages.append(HistoryMessage.model_validate(converted))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user