refactor: 重构 schemas 结构,统一枚举定义
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Modify this file to customize your launch splash screen -->
|
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:drawable="?android:colorBackground" />
|
<item android:drawable="@color/launchBackground" />
|
||||||
|
|
||||||
<!-- You can insert your own image assets here -->
|
<!-- You can insert your own image assets here -->
|
||||||
<!-- <item>
|
<!-- <item>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Modify this file to customize your launch splash screen -->
|
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:drawable="@android:color/white" />
|
<item android:drawable="@color/launchBackground" />
|
||||||
|
|
||||||
<!-- You can insert your own image assets here -->
|
<!-- You can insert your own image assets here -->
|
||||||
<!-- <item>
|
<!-- <item>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="ic_launcher_background">#FFFFFF</color>
|
<color name="ic_launcher_background">#FFFFFF</color>
|
||||||
|
<color name="launchBackground">#EFF8FF</color>
|
||||||
</resources>
|
</resources>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
@@ -1,23 +1,17 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../../../../core/theme/design_tokens.dart';
|
|
||||||
import '../../../../shared/widgets/app_loading_indicator.dart';
|
|
||||||
|
|
||||||
class AuthBootScreen extends StatelessWidget {
|
class AuthBootScreen extends StatelessWidget {
|
||||||
const AuthBootScreen({super.key});
|
const AuthBootScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return const Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: AppColors.authBackgroundTop,
|
backgroundColor: const Color(0xFFEFF8FF),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: AppLoadingIndicator(
|
child: Image.asset(
|
||||||
variant: AppLoadingVariant.surface,
|
'assets/branding/assistant_octopus_foreground.png',
|
||||||
size: 28,
|
width: 260,
|
||||||
strokeWidth: 2.5,
|
|
||||||
color: AppColors.authPrimaryButton,
|
|
||||||
trackColor: AppColors.authPrimaryButtonDisabled,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
enum ScheduleSourceType { manual, imported, agentGenerated }
|
enum ScheduleSourceType { manual, imported, agentGenerated }
|
||||||
|
|
||||||
enum ScheduleStatus { active, completed, canceled, archived }
|
enum ScheduleStatus { active, archived }
|
||||||
|
|
||||||
class ScheduleItemModel {
|
class ScheduleItemModel {
|
||||||
final String id;
|
final String id;
|
||||||
@@ -279,9 +279,7 @@ ScheduleSourceType _sourceTypeFromApi(String? raw) {
|
|||||||
ScheduleStatus _statusFromApi(String? raw) {
|
ScheduleStatus _statusFromApi(String? raw) {
|
||||||
switch (raw) {
|
switch (raw) {
|
||||||
case 'completed':
|
case 'completed':
|
||||||
return ScheduleStatus.completed;
|
|
||||||
case 'canceled':
|
case 'canceled':
|
||||||
return ScheduleStatus.canceled;
|
|
||||||
case 'archived':
|
case 'archived':
|
||||||
return ScheduleStatus.archived;
|
return ScheduleStatus.archived;
|
||||||
case 'active':
|
case 'active':
|
||||||
@@ -294,10 +292,6 @@ String _statusToApi(ScheduleStatus status) {
|
|||||||
switch (status) {
|
switch (status) {
|
||||||
case ScheduleStatus.active:
|
case ScheduleStatus.active:
|
||||||
return 'active';
|
return 'active';
|
||||||
case ScheduleStatus.completed:
|
|
||||||
return 'completed';
|
|
||||||
case ScheduleStatus.canceled:
|
|
||||||
return 'canceled';
|
|
||||||
case ScheduleStatus.archived:
|
case ScheduleStatus.archived:
|
||||||
return 'archived';
|
return 'archived';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ flutter:
|
|||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
assets:
|
assets:
|
||||||
- assets/images/
|
- assets/images/
|
||||||
|
- assets/branding/
|
||||||
|
|
||||||
flutter_launcher_icons:
|
flutter_launcher_icons:
|
||||||
android: true
|
android: true
|
||||||
|
|||||||
@@ -45,6 +45,10 @@ This file governs `backend/**` only. Keep it minimal, enforceable, and non-dupli
|
|||||||
- Use service-role DB access only in backend.
|
- Use service-role DB access only in backend.
|
||||||
- Soft delete uses `deleted_at`; reads must exclude deleted records by default.
|
- Soft delete uses `deleted_at`; reads must exclude deleted records by default.
|
||||||
- Alembic is the only schema migration source of truth.
|
- Alembic is the only schema migration source of truth.
|
||||||
|
- Database migrations use `./infra/scripts/dev-migrate.sh`:
|
||||||
|
- `migrate` - run migrations only
|
||||||
|
- `init-data` - seed data only
|
||||||
|
- `bootstrap` - migrate + init-data
|
||||||
|
|
||||||
## Agent Runtime & Tools
|
## Agent Runtime & Tools
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
"""converge schedule item status to active or archived
|
||||||
|
|
||||||
|
Revision ID: 202603250001
|
||||||
|
Revises: 202603240001
|
||||||
|
Create Date: 2026-03-25 10:00:00
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
revision: str = "202603250001"
|
||||||
|
down_revision: Union[str, Sequence[str], None] = "202603240001"
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
op.execute(
|
||||||
|
"""
|
||||||
|
UPDATE public.schedule_items
|
||||||
|
SET status = 'archived'
|
||||||
|
WHERE status IN ('completed', 'canceled')
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
op.execute(
|
||||||
|
"""
|
||||||
|
ALTER TABLE public.schedule_items
|
||||||
|
DROP CONSTRAINT IF EXISTS chk_schedule_item_status
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
op.execute(
|
||||||
|
"""
|
||||||
|
ALTER TABLE public.schedule_items
|
||||||
|
ADD CONSTRAINT chk_schedule_item_status
|
||||||
|
CHECK (status IN ('active', 'archived'))
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
op.execute(
|
||||||
|
"""
|
||||||
|
ALTER TABLE public.schedule_items
|
||||||
|
DROP CONSTRAINT IF EXISTS chk_schedule_item_status
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
op.execute(
|
||||||
|
"""
|
||||||
|
ALTER TABLE public.schedule_items
|
||||||
|
ADD CONSTRAINT chk_schedule_item_status
|
||||||
|
CHECK (status IN ('active', 'completed', 'canceled', 'archived'))
|
||||||
|
"""
|
||||||
|
)
|
||||||
@@ -7,8 +7,9 @@ from uuid import UUID
|
|||||||
from sqlalchemy import func, select
|
from sqlalchemy import func, select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from models.agent_chat_message import AgentChatMessage, AgentChatMessageRole
|
from models.agent_chat_message import AgentChatMessage
|
||||||
from models.agent_chat_session import AgentChatSession, AgentChatSessionStatus
|
from models.agent_chat_session import AgentChatSession
|
||||||
|
from schemas.enums import AgentChatMessageRole, AgentChatSessionStatus
|
||||||
|
|
||||||
|
|
||||||
class MessageRepository:
|
class MessageRepository:
|
||||||
|
|||||||
@@ -6,12 +6,11 @@ from uuid import UUID
|
|||||||
|
|
||||||
from core.agentscope.events.persistence import MessageRepository, SessionRepository
|
from core.agentscope.events.persistence import MessageRepository, SessionRepository
|
||||||
from core.logging import get_logger
|
from core.logging import get_logger
|
||||||
from models.agent_chat_message import AgentChatMessageRole
|
from schemas.enums import AgentChatMessageRole, AgentChatSessionStatus
|
||||||
from models.agent_chat_session import AgentChatSessionStatus
|
|
||||||
from schemas.agent.system_agent import AgentType
|
from schemas.agent.system_agent import AgentType
|
||||||
from schemas.agent.runtime_models import AgentOutput, RouterAgentOutput, ToolAgentOutput
|
from schemas.agent.runtime_models import AgentOutput, RouterAgentOutput, ToolAgentOutput
|
||||||
from schemas.agent.visibility import SystemVisibilityBit, bit_mask
|
from schemas.agent.visibility import SystemVisibilityBit, bit_mask
|
||||||
from schemas.messages.chat_message import AgentChatMessageMetadata
|
from schemas.domain.chat_message import AgentChatMessageMetadata
|
||||||
|
|
||||||
|
|
||||||
class EventStore(Protocol):
|
class EventStore(Protocol):
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from uuid import UUID
|
|||||||
import redis.asyncio as redis
|
import redis.asyncio as redis
|
||||||
from core.config.settings import config
|
from core.config.settings import config
|
||||||
from core.logging import get_logger
|
from core.logging import get_logger
|
||||||
from schemas.user import (
|
from schemas.shared.user import (
|
||||||
UserContext,
|
UserContext,
|
||||||
parse_profile_settings,
|
parse_profile_settings,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from schemas.memories.memory_content import UserMemoryContent, WorkProfileContent
|
from schemas.domain.memory_content import UserMemoryContent, WorkProfileContent
|
||||||
|
|
||||||
|
|
||||||
def _wrap_section(section: str, content: str) -> str:
|
def _wrap_section(section: str, content: str) -> str:
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ from core.agentscope.prompts.route_prompt import build_frontend_route_prompt
|
|||||||
from core.agentscope.prompts.tool_prompt import build_tools_prompt
|
from core.agentscope.prompts.tool_prompt import build_tools_prompt
|
||||||
from schemas.agent.system_agent import AgentType, SystemAgentLLMConfig
|
from schemas.agent.system_agent import AgentType, SystemAgentLLMConfig
|
||||||
from schemas.agent.forwarded_props import ClientTimeContext
|
from schemas.agent.forwarded_props import ClientTimeContext
|
||||||
from schemas.memories.memory_content import UserMemoryContent, WorkProfileContent
|
from schemas.domain.memory_content import UserMemoryContent, WorkProfileContent
|
||||||
from schemas.user.context import UserContext
|
from schemas.shared.user import UserContext
|
||||||
|
|
||||||
|
|
||||||
def _wrap_section(section: str, content: str) -> str:
|
def _wrap_section(section: str, content: str) -> str:
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ from ag_ui.core.types import RunAgentInput
|
|||||||
from agentscope.message import Msg
|
from agentscope.message import Msg
|
||||||
from core.agentscope.runtime.runner import AgentScopeRunner
|
from core.agentscope.runtime.runner import AgentScopeRunner
|
||||||
from core.logging import get_logger
|
from core.logging import get_logger
|
||||||
from schemas.automation import RuntimeConfig
|
from schemas.domain.automation import RuntimeConfig
|
||||||
from schemas.memories.memory_content import UserMemoryContent, WorkProfileContent
|
from schemas.domain.memory_content import UserMemoryContent, WorkProfileContent
|
||||||
from schemas.user import UserContext
|
from schemas.shared.user import UserContext
|
||||||
|
|
||||||
logger = get_logger("core.agentscope.runtime.orchestrator")
|
logger = get_logger("core.agentscope.runtime.orchestrator")
|
||||||
|
|
||||||
|
|||||||
@@ -40,9 +40,9 @@ from schemas.agent.system_agent import (
|
|||||||
AgentType,
|
AgentType,
|
||||||
SystemAgentLLMConfig,
|
SystemAgentLLMConfig,
|
||||||
)
|
)
|
||||||
from schemas.automation import RuntimeConfig
|
from schemas.domain.automation import RuntimeConfig
|
||||||
from schemas.memories.memory_content import UserMemoryContent, WorkProfileContent
|
from schemas.domain.memory_content import UserMemoryContent, WorkProfileContent
|
||||||
from schemas.user import UserContext
|
from schemas.shared.user import UserContext
|
||||||
from services.litellm.service import LiteLLMService
|
from services.litellm.service import LiteLLMService
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|||||||
@@ -20,13 +20,13 @@ from core.config.settings import config
|
|||||||
from core.db.session import AsyncSessionLocal
|
from core.db.session import AsyncSessionLocal
|
||||||
from core.logging import get_logger
|
from core.logging import get_logger
|
||||||
from core.taskiq.app import worker_agent_broker, worker_automation_broker
|
from core.taskiq.app import worker_agent_broker, worker_automation_broker
|
||||||
from schemas.automation import MessageContextConfig, RuntimeConfig
|
from schemas.domain.automation import MessageContextConfig, RuntimeConfig
|
||||||
from schemas.memories.memory_content import UserMemoryContent, WorkProfileContent
|
from schemas.domain.memory_content import UserMemoryContent, WorkProfileContent
|
||||||
from schemas.messages.chat_message import (
|
from schemas.domain.chat_message import (
|
||||||
AgentChatMessageMetadata,
|
AgentChatMessageMetadata,
|
||||||
extract_user_message_attachments,
|
extract_user_message_attachments,
|
||||||
)
|
)
|
||||||
from schemas.user import UserContext
|
from schemas.shared.user import UserContext
|
||||||
from services.base.redis import get_or_init_redis_client
|
from services.base.redis import get_or_init_redis_client
|
||||||
from services.base.supabase import supabase_service
|
from services.base.supabase import supabase_service
|
||||||
from v1.agent.repository import AgentRepository
|
from v1.agent.repository import AgentRepository
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from typing import Any, Protocol
|
|||||||
|
|
||||||
from schemas.agent.visibility import SystemVisibilityBit, bit_mask
|
from schemas.agent.visibility import SystemVisibilityBit, bit_mask
|
||||||
|
|
||||||
from schemas.automation import ContextWindowMode, MessageContextConfig
|
from schemas.domain.automation import ContextWindowMode, MessageContextConfig
|
||||||
|
|
||||||
|
|
||||||
_DEFAULT_CONTEXT_WINDOW_USER_MESSAGES = 20
|
_DEFAULT_CONTEXT_WINDOW_USER_MESSAGES = 20
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ class CalendarWriteOperation(BaseModel):
|
|||||||
le=10080,
|
le=10080,
|
||||||
description="Reminder minutes before event start.",
|
description="Reminder minutes before event start.",
|
||||||
)
|
)
|
||||||
status: Literal["active", "completed", "canceled", "archived"] | None = Field(
|
status: Literal["active", "archived"] | None = Field(
|
||||||
default=None,
|
default=None,
|
||||||
description="Optional status for update action.",
|
description="Optional status for update action.",
|
||||||
)
|
)
|
||||||
@@ -163,6 +163,10 @@ async def calendar_read(
|
|||||||
) -> ToolResponse:
|
) -> ToolResponse:
|
||||||
"""Read calendar events with optional keyword filtering and pagination.
|
"""Read calendar events with optional keyword filtering and pagination.
|
||||||
|
|
||||||
|
Status semantics for returned events:
|
||||||
|
- active: Event is actionable.
|
||||||
|
- archived: Event is historical/expired and should not trigger reminders.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
query: Optional keyword used to filter events by text fields.
|
query: Optional keyword used to filter events by text fields.
|
||||||
page: Page number starting from 1.
|
page: Page number starting from 1.
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ from core.agentscope.tools.utils.tool_response_builder import (
|
|||||||
build_error_output,
|
build_error_output,
|
||||||
build_tool_response,
|
build_tool_response,
|
||||||
)
|
)
|
||||||
from models.memories import MemoryType
|
from schemas.enums import MemoryType
|
||||||
from schemas.agent.runtime_models import ErrorInfo, ToolAgentOutput, ToolStatus
|
from schemas.agent.runtime_models import ErrorInfo, ToolAgentOutput, ToolStatus
|
||||||
from schemas.memories.memory_content import UserMemoryContent, WorkProfileContent
|
from schemas.domain.memory_content import UserMemoryContent, WorkProfileContent
|
||||||
|
|
||||||
|
|
||||||
class MemoryWriteArgs(BaseModel):
|
class MemoryWriteArgs(BaseModel):
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ def create_schedule_service(
|
|||||||
def schedule_event_to_dict(event: object) -> dict[str, Any]:
|
def schedule_event_to_dict(event: object) -> dict[str, Any]:
|
||||||
event_id = str(getattr(event, "id"))
|
event_id = str(getattr(event, "id"))
|
||||||
metadata = getattr(event, "metadata", None)
|
metadata = getattr(event, "metadata", None)
|
||||||
|
status_value = getattr(event, "status", None)
|
||||||
|
if hasattr(status_value, "value"):
|
||||||
|
status_value = getattr(status_value, "value")
|
||||||
location_value = getattr(metadata, "location", None)
|
location_value = getattr(metadata, "location", None)
|
||||||
color_value = getattr(metadata, "color", None) or "#4F46E5"
|
color_value = getattr(metadata, "color", None) or "#4F46E5"
|
||||||
reminder_minutes_value = getattr(metadata, "reminder_minutes", None)
|
reminder_minutes_value = getattr(metadata, "reminder_minutes", None)
|
||||||
@@ -58,6 +61,7 @@ def schedule_event_to_dict(event: object) -> dict[str, Any]:
|
|||||||
if getattr(event, "end_at") is not None
|
if getattr(event, "end_at") is not None
|
||||||
else None,
|
else None,
|
||||||
"timezone": getattr(event, "timezone"),
|
"timezone": getattr(event, "timezone"),
|
||||||
|
"status": status_value,
|
||||||
"location": location_value,
|
"location": location_value,
|
||||||
"color": color_value,
|
"color": color_value,
|
||||||
"reminderMinutes": reminder_minutes_value,
|
"reminderMinutes": reminder_minutes_value,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from uuid import UUID
|
|||||||
|
|
||||||
from core.config.settings import config
|
from core.config.settings import config
|
||||||
from core.logging import get_logger
|
from core.logging import get_logger
|
||||||
from schemas.automation import RuntimeConfig
|
from schemas.domain.automation import RuntimeConfig
|
||||||
|
|
||||||
logger = get_logger("core.automation.scheduler")
|
logger = get_logger("core.automation.scheduler")
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from models.automation_jobs import AutomationJob
|
|||||||
from models.group_members import GroupMember
|
from models.group_members import GroupMember
|
||||||
from models.groups import Group
|
from models.groups import Group
|
||||||
from models.inbox_messages import InboxMessage
|
from models.inbox_messages import InboxMessage
|
||||||
from models.invite_code import InviteCode, InviteCodeStatus
|
from models.invite_code import InviteCode
|
||||||
from models.llm import Llm
|
from models.llm import Llm
|
||||||
from models.llm_factory import LlmFactory
|
from models.llm_factory import LlmFactory
|
||||||
from models.memories import Memory
|
from models.memories import Memory
|
||||||
@@ -16,6 +16,7 @@ from models.schedule_subscriptions import ScheduleSubscription
|
|||||||
from models.system_agents import SystemAgents
|
from models.system_agents import SystemAgents
|
||||||
from models.todos import Todo
|
from models.todos import Todo
|
||||||
from models.todo_sources import TodoSource
|
from models.todo_sources import TodoSource
|
||||||
|
from schemas.enums import InviteCodeStatus
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"AgentChatMessage",
|
"AgentChatMessage",
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
import uuid
|
import uuid
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
from sqlalchemy import (
|
from sqlalchemy import (
|
||||||
BigInteger,
|
BigInteger,
|
||||||
@@ -19,13 +18,9 @@ from sqlalchemy.dialects.postgresql import JSONB, UUID
|
|||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
from core.db.base import Base, SoftDeleteMixin, TimestampMixin
|
from core.db.base import Base, SoftDeleteMixin, TimestampMixin
|
||||||
|
from schemas.enums import AgentChatMessageRole
|
||||||
|
|
||||||
|
__all__ = ["AgentChatMessage", "AgentChatMessageRole"]
|
||||||
class AgentChatMessageRole(str, Enum):
|
|
||||||
USER = "user"
|
|
||||||
ASSISTANT = "assistant"
|
|
||||||
SYSTEM = "system"
|
|
||||||
TOOL = "tool"
|
|
||||||
|
|
||||||
|
|
||||||
class AgentChatMessage(TimestampMixin, SoftDeleteMixin, Base):
|
class AgentChatMessage(TimestampMixin, SoftDeleteMixin, Base):
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
import uuid
|
import uuid
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
from sqlalchemy import (
|
from sqlalchemy import (
|
||||||
DateTime,
|
DateTime,
|
||||||
@@ -19,18 +18,9 @@ from sqlalchemy.dialects.postgresql import JSONB, UUID
|
|||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
from core.db.base import Base, SoftDeleteMixin, TimestampMixin
|
from core.db.base import Base, SoftDeleteMixin, TimestampMixin
|
||||||
|
from schemas.enums import AgentChatSessionStatus, SessionType
|
||||||
|
|
||||||
|
__all__ = ["AgentChatSession", "AgentChatSessionStatus", "SessionType"]
|
||||||
class AgentChatSessionStatus(str, Enum):
|
|
||||||
PENDING = "pending"
|
|
||||||
RUNNING = "running"
|
|
||||||
COMPLETED = "completed"
|
|
||||||
FAILED = "failed"
|
|
||||||
|
|
||||||
|
|
||||||
class SessionType(str, Enum):
|
|
||||||
CHAT = "chat"
|
|
||||||
AUTOMATION = "automation"
|
|
||||||
|
|
||||||
|
|
||||||
class AgentChatSession(TimestampMixin, SoftDeleteMixin, Base):
|
class AgentChatSession(TimestampMixin, SoftDeleteMixin, Base):
|
||||||
|
|||||||
@@ -2,23 +2,15 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
from sqlalchemy import DateTime, JSON, String
|
from sqlalchemy import DateTime, JSON, String
|
||||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
from core.db.base import Base, SoftDeleteMixin, TimestampMixin
|
from core.db.base import Base, SoftDeleteMixin, TimestampMixin
|
||||||
|
from schemas.enums import AutomationJobStatus, ScheduleType
|
||||||
|
|
||||||
|
__all__ = ["AutomationJob", "AutomationJobStatus", "ScheduleType"]
|
||||||
class AutomationJobStatus(str, Enum):
|
|
||||||
ACTIVE = "active"
|
|
||||||
DISABLED = "disabled"
|
|
||||||
|
|
||||||
|
|
||||||
class ScheduleType(str, Enum):
|
|
||||||
DAILY = "daily"
|
|
||||||
WEEKLY = "weekly"
|
|
||||||
|
|
||||||
|
|
||||||
class AutomationJob(TimestampMixin, SoftDeleteMixin, Base):
|
class AutomationJob(TimestampMixin, SoftDeleteMixin, Base):
|
||||||
|
|||||||
@@ -2,21 +2,15 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
from sqlalchemy import DateTime, String
|
from sqlalchemy import DateTime, String
|
||||||
from sqlalchemy.dialects.postgresql import UUID
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
from core.db.base import Base, SoftDeleteMixin, TimestampMixin
|
from core.db.base import Base, SoftDeleteMixin, TimestampMixin
|
||||||
|
from schemas.enums import FriendshipStatus
|
||||||
|
|
||||||
|
__all__ = ["Friendship", "FriendshipStatus"]
|
||||||
class FriendshipStatus(str, Enum):
|
|
||||||
PENDING = "pending"
|
|
||||||
ACCEPTED = "accepted"
|
|
||||||
BLOCKED = "blocked"
|
|
||||||
DECLINED = "declined"
|
|
||||||
CANCELED = "canceled"
|
|
||||||
|
|
||||||
|
|
||||||
class Friendship(TimestampMixin, SoftDeleteMixin, Base):
|
class Friendship(TimestampMixin, SoftDeleteMixin, Base):
|
||||||
|
|||||||
@@ -1,30 +1,20 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
from sqlalchemy import String
|
from sqlalchemy import String
|
||||||
from sqlalchemy.dialects.postgresql import UUID
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
from core.db.base import Base, SoftDeleteMixin, TimestampMixin
|
from core.db.base import Base, SoftDeleteMixin, TimestampMixin
|
||||||
|
from schemas.enums import GroupMemberRole, GroupMemberSource, GroupMemberStatus
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
class GroupMemberRole(str, Enum):
|
"GroupMember",
|
||||||
OWNER = "owner"
|
"GroupMemberRole",
|
||||||
ADMIN = "admin"
|
"GroupMemberSource",
|
||||||
MEMBER = "member"
|
"GroupMemberStatus",
|
||||||
|
]
|
||||||
|
|
||||||
class GroupMemberSource(str, Enum):
|
|
||||||
INVITED = "invited"
|
|
||||||
JOINED = "joined"
|
|
||||||
|
|
||||||
|
|
||||||
class GroupMemberStatus(str, Enum):
|
|
||||||
ACTIVE = "active"
|
|
||||||
MUTED = "muted"
|
|
||||||
REMOVED = "removed"
|
|
||||||
|
|
||||||
|
|
||||||
class GroupMember(TimestampMixin, SoftDeleteMixin, Base):
|
class GroupMember(TimestampMixin, SoftDeleteMixin, Base):
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
from sqlalchemy import String, Text
|
from sqlalchemy import String, Text
|
||||||
from sqlalchemy.dialects.postgresql import UUID
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
from core.db.base import Base, SoftDeleteMixin, TimestampMixin
|
from core.db.base import Base, SoftDeleteMixin, TimestampMixin
|
||||||
|
from schemas.enums import GroupStatus
|
||||||
|
|
||||||
|
__all__ = ["Group", "GroupStatus"]
|
||||||
class GroupStatus(str, Enum):
|
|
||||||
ACTIVE = "active"
|
|
||||||
ARCHIVED = "archived"
|
|
||||||
|
|
||||||
|
|
||||||
class Group(TimestampMixin, SoftDeleteMixin, Base):
|
class Group(TimestampMixin, SoftDeleteMixin, Base):
|
||||||
|
|||||||
@@ -1,27 +1,15 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
from sqlalchemy import Boolean, String
|
from sqlalchemy import Boolean, String
|
||||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
from core.db.base import Base, TimestampMixin
|
from core.db.base import Base, TimestampMixin
|
||||||
|
from schemas.enums import InboxMessageStatus, InboxMessageType
|
||||||
|
|
||||||
|
__all__ = ["InboxMessage", "InboxMessageType", "InboxMessageStatus"]
|
||||||
class InboxMessageType(str, Enum):
|
|
||||||
FRIEND_REQUEST = "friend_request"
|
|
||||||
CALENDAR = "calendar"
|
|
||||||
SYSTEM = "system"
|
|
||||||
GROUP = "group"
|
|
||||||
|
|
||||||
|
|
||||||
class InboxMessageStatus(str, Enum):
|
|
||||||
PENDING = "pending"
|
|
||||||
ACCEPTED = "accepted"
|
|
||||||
REJECTED = "rejected"
|
|
||||||
DISMISSED = "dismissed"
|
|
||||||
|
|
||||||
|
|
||||||
class InboxMessage(TimestampMixin, Base):
|
class InboxMessage(TimestampMixin, Base):
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
from sqlalchemy import CheckConstraint, DateTime, ForeignKey, Integer, String
|
from sqlalchemy import CheckConstraint, DateTime, ForeignKey, Integer, String
|
||||||
from sqlalchemy.dialects.postgresql import UUID
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
@@ -10,12 +9,9 @@ from sqlalchemy.orm import Mapped, mapped_column
|
|||||||
|
|
||||||
from core.db.base import Base, TimestampMixin
|
from core.db.base import Base, TimestampMixin
|
||||||
from core.db.types import json_jsonb
|
from core.db.types import json_jsonb
|
||||||
|
from schemas.enums import InviteCodeStatus
|
||||||
|
|
||||||
|
__all__ = ["InviteCode", "InviteCodeStatus"]
|
||||||
class InviteCodeStatus(str, Enum):
|
|
||||||
ACTIVE = "active"
|
|
||||||
DISABLED = "disabled"
|
|
||||||
EXPIRED = "expired"
|
|
||||||
|
|
||||||
|
|
||||||
class InviteCode(TimestampMixin, Base):
|
class InviteCode(TimestampMixin, Base):
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
from sqlalchemy import String
|
from sqlalchemy import String
|
||||||
from sqlalchemy.dialects.postgresql import UUID
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
@@ -9,16 +8,9 @@ from sqlalchemy.orm import Mapped, mapped_column
|
|||||||
|
|
||||||
from core.db.base import Base, TimestampMixin
|
from core.db.base import Base, TimestampMixin
|
||||||
from core.db.types import json_jsonb
|
from core.db.types import json_jsonb
|
||||||
|
from schemas.enums import MemoryStatus, MemoryType
|
||||||
|
|
||||||
|
__all__ = ["Memory", "MemoryType", "MemoryStatus"]
|
||||||
class MemoryType(str, Enum):
|
|
||||||
USER = "user"
|
|
||||||
WORK = "work"
|
|
||||||
|
|
||||||
|
|
||||||
class MemoryStatus(str, Enum):
|
|
||||||
ACTIVE = "active"
|
|
||||||
DISABLED = "disabled"
|
|
||||||
|
|
||||||
|
|
||||||
class Memory(TimestampMixin, Base):
|
class Memory(TimestampMixin, Base):
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
from sqlalchemy import DateTime, String, Text
|
from sqlalchemy import DateTime, String, Text
|
||||||
from sqlalchemy.dialects.postgresql import UUID
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
@@ -10,19 +9,9 @@ from sqlalchemy.orm import Mapped, mapped_column
|
|||||||
|
|
||||||
from core.db.base import Base, SoftDeleteMixin, TimestampMixin
|
from core.db.base import Base, SoftDeleteMixin, TimestampMixin
|
||||||
from core.db.types import json_jsonb
|
from core.db.types import json_jsonb
|
||||||
|
from schemas.enums import ScheduleItemSourceType, ScheduleItemStatus
|
||||||
|
|
||||||
|
__all__ = ["ScheduleItem", "ScheduleItemStatus", "ScheduleItemSourceType"]
|
||||||
class ScheduleItemStatus(str, Enum):
|
|
||||||
ACTIVE = "active"
|
|
||||||
COMPLETED = "completed"
|
|
||||||
CANCELED = "canceled"
|
|
||||||
ARCHIVED = "archived"
|
|
||||||
|
|
||||||
|
|
||||||
class ScheduleItemSourceType(str, Enum):
|
|
||||||
MANUAL = "manual"
|
|
||||||
IMPORTED = "imported"
|
|
||||||
AGENT_GENERATED = "agent_generated"
|
|
||||||
|
|
||||||
|
|
||||||
class ScheduleItem(TimestampMixin, SoftDeleteMixin, Base):
|
class ScheduleItem(TimestampMixin, SoftDeleteMixin, Base):
|
||||||
|
|||||||
@@ -1,33 +1,20 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
from sqlalchemy import Integer, String
|
from sqlalchemy import Integer, String
|
||||||
from sqlalchemy.dialects.postgresql import UUID
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
from core.db.base import Base, TimestampMixin
|
from core.db.base import Base, TimestampMixin
|
||||||
|
from schemas.enums import NotifyLevel, SubscriptionPermission, SubscriptionStatus
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
class SubscriptionStatus(str, Enum):
|
"ScheduleSubscription",
|
||||||
ACTIVE = "active"
|
"SubscriptionStatus",
|
||||||
PENDING = "pending"
|
"NotifyLevel",
|
||||||
PAUSED = "paused"
|
"SubscriptionPermission",
|
||||||
UNSUBSCRIBED = "unsubscribed"
|
]
|
||||||
|
|
||||||
|
|
||||||
class NotifyLevel(str, Enum):
|
|
||||||
ALL = "all"
|
|
||||||
MENTIONS = "mentions"
|
|
||||||
NONE = "none"
|
|
||||||
|
|
||||||
|
|
||||||
class SubscriptionPermission(int, Enum):
|
|
||||||
VIEW = 1 # 001 - 可查看
|
|
||||||
INVITE = 2 # 010 - 可邀请
|
|
||||||
EDIT = 4 # 100 - 可编辑
|
|
||||||
OWNER = 7 # 111 - 所有者(VIEW + INVITE + EDIT)
|
|
||||||
|
|
||||||
|
|
||||||
class ScheduleSubscription(TimestampMixin, Base):
|
class ScheduleSubscription(TimestampMixin, Base):
|
||||||
|
|||||||
@@ -2,26 +2,15 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
from sqlalchemy import DateTime, Integer, String
|
from sqlalchemy import DateTime, Integer, String
|
||||||
from sqlalchemy.dialects.postgresql import UUID
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
from core.db.base import Base, SoftDeleteMixin, TimestampMixin
|
from core.db.base import Base, SoftDeleteMixin, TimestampMixin
|
||||||
|
from schemas.enums import TodoPriority, TodoStatus
|
||||||
|
|
||||||
|
__all__ = ["Todo", "TodoStatus", "TodoPriority"]
|
||||||
class TodoStatus(str, Enum):
|
|
||||||
PENDING = "pending"
|
|
||||||
DONE = "done"
|
|
||||||
CANCELED = "canceled"
|
|
||||||
|
|
||||||
|
|
||||||
class TodoPriority(int, Enum):
|
|
||||||
IMPORTANT_URGENT = 1
|
|
||||||
IMPORTANT_NOT_URGENT = 2
|
|
||||||
NOT_IMPORTANT_URGENT = 3
|
|
||||||
NOT_IMPORTANT_NOT_URGENT = 4
|
|
||||||
|
|
||||||
|
|
||||||
class Todo(TimestampMixin, SoftDeleteMixin, Base):
|
class Todo(TimestampMixin, SoftDeleteMixin, Base):
|
||||||
|
|||||||
@@ -1,45 +1 @@
|
|||||||
"""Centralized shared schemas for cross-module contracts."""
|
"""Backend reusable schemas package."""
|
||||||
|
|
||||||
from schemas.inbox.messages import (
|
|
||||||
CalendarContent,
|
|
||||||
CalendarDeleteContent,
|
|
||||||
CalendarInviteContent,
|
|
||||||
CalendarUpdateContent,
|
|
||||||
FriendshipContent,
|
|
||||||
InboxMessageContent,
|
|
||||||
InboxMessageStatus,
|
|
||||||
InboxMessageType,
|
|
||||||
parse_calendar_content,
|
|
||||||
)
|
|
||||||
from schemas.invite_codes import InviteCodeRewardConfig
|
|
||||||
from schemas.messages import AgentChatMessageMetadata
|
|
||||||
from schemas.schedule.items import (
|
|
||||||
AttachmentType,
|
|
||||||
ScheduleItemMetadata,
|
|
||||||
ScheduleItemMetadataAttachment,
|
|
||||||
ScheduleItemSourceType,
|
|
||||||
ScheduleItemStatus,
|
|
||||||
)
|
|
||||||
from schemas.sessions import SessionStateSnapshot
|
|
||||||
from schemas.user.context import UserContext
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"AgentChatMessageMetadata",
|
|
||||||
"AttachmentType",
|
|
||||||
"CalendarContent",
|
|
||||||
"CalendarDeleteContent",
|
|
||||||
"CalendarInviteContent",
|
|
||||||
"CalendarUpdateContent",
|
|
||||||
"FriendshipContent",
|
|
||||||
"InboxMessageContent",
|
|
||||||
"InboxMessageStatus",
|
|
||||||
"InboxMessageType",
|
|
||||||
"InviteCodeRewardConfig",
|
|
||||||
"ScheduleItemMetadata",
|
|
||||||
"ScheduleItemMetadataAttachment",
|
|
||||||
"ScheduleItemSourceType",
|
|
||||||
"ScheduleItemStatus",
|
|
||||||
"SessionStateSnapshot",
|
|
||||||
"UserContext",
|
|
||||||
"parse_calendar_content",
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ Version: 2.0
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, Literal, NotRequired, TypedDict, Union
|
from typing import Any, Literal, TypedDict, Union
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Enums
|
# Enums
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
"""Reusable domain schemas shared across backend modules."""
|
||||||
+30
-17
@@ -2,13 +2,27 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from typing import Protocol
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from core.agentscope.tools.tool_config import AgentTool
|
from core.agentscope.tools.tool_config import AgentTool
|
||||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||||
|
from schemas.enums import AutomationJobStatus, ScheduleType
|
||||||
|
|
||||||
from models.automation_jobs import AutomationJob as OrmAutomationJob
|
|
||||||
from models.automation_jobs import AutomationJobStatus, ScheduleType
|
class AutomationJobLike(Protocol):
|
||||||
|
id: UUID
|
||||||
|
owner_id: UUID
|
||||||
|
bootstrap_key: str | None
|
||||||
|
title: str
|
||||||
|
config: dict[str, object]
|
||||||
|
next_run_at: datetime
|
||||||
|
timezone: str
|
||||||
|
last_run_at: datetime | None
|
||||||
|
status: AutomationJobStatus
|
||||||
|
created_by: UUID | None
|
||||||
|
created_at: datetime
|
||||||
|
updated_at: datetime
|
||||||
|
|
||||||
|
|
||||||
class ContextSource(str, Enum):
|
class ContextSource(str, Enum):
|
||||||
@@ -50,8 +64,7 @@ class ScheduleConfig(BaseModel):
|
|||||||
invalid = [day for day in self.weekdays if day < 1 or day > 7]
|
invalid = [day for day in self.weekdays if day < 1 or day > 7]
|
||||||
if invalid:
|
if invalid:
|
||||||
raise ValueError("weekdays must be within 1-7")
|
raise ValueError("weekdays must be within 1-7")
|
||||||
deduped = sorted(set(self.weekdays))
|
self.weekdays = sorted(set(self.weekdays))
|
||||||
self.weekdays = deduped
|
|
||||||
else:
|
else:
|
||||||
self.weekdays = None
|
self.weekdays = None
|
||||||
return self
|
return self
|
||||||
@@ -90,20 +103,20 @@ class AutomationJob(BaseModel):
|
|||||||
updated_at: datetime
|
updated_at: datetime
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_orm(cls, obj: OrmAutomationJob) -> "AutomationJob":
|
def from_orm(cls, obj: object) -> "AutomationJob":
|
||||||
return cls(
|
return cls(
|
||||||
id=obj.id,
|
id=getattr(obj, "id"),
|
||||||
owner_id=obj.owner_id,
|
owner_id=getattr(obj, "owner_id"),
|
||||||
bootstrap_key=obj.bootstrap_key,
|
bootstrap_key=getattr(obj, "bootstrap_key"),
|
||||||
title=obj.title,
|
title=getattr(obj, "title"),
|
||||||
config=AutomationJobConfig.model_validate(obj.config or {}),
|
config=AutomationJobConfig.model_validate(getattr(obj, "config", {}) or {}),
|
||||||
next_run_at=obj.next_run_at,
|
next_run_at=getattr(obj, "next_run_at"),
|
||||||
timezone=obj.timezone,
|
timezone=getattr(obj, "timezone"),
|
||||||
last_run_at=obj.last_run_at,
|
last_run_at=getattr(obj, "last_run_at"),
|
||||||
status=obj.status,
|
status=getattr(obj, "status"),
|
||||||
created_by=obj.created_by,
|
created_by=getattr(obj, "created_by"),
|
||||||
created_at=obj.created_at,
|
created_at=getattr(obj, "created_at"),
|
||||||
updated_at=obj.updated_at,
|
updated_at=getattr(obj, "updated_at"),
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -1,24 +1,22 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from enum import Enum
|
|
||||||
from typing import ClassVar, Literal, Union
|
from typing import ClassVar, Literal, Union
|
||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict, Field
|
from pydantic import BaseModel, ConfigDict, Field
|
||||||
|
from schemas.enums import InboxMessageStatus, InboxMessageType
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
class InboxMessageType(str, Enum):
|
"InboxMessageType",
|
||||||
FRIEND_REQUEST = "friend_request"
|
"InboxMessageStatus",
|
||||||
CALENDAR = "calendar"
|
"CalendarInviteContent",
|
||||||
SYSTEM = "system"
|
"CalendarUpdateContent",
|
||||||
GROUP = "group"
|
"CalendarDeleteContent",
|
||||||
|
"FriendshipContent",
|
||||||
|
"CalendarContent",
|
||||||
class InboxMessageStatus(str, Enum):
|
"InboxMessageContent",
|
||||||
PENDING = "pending"
|
"parse_calendar_content",
|
||||||
ACCEPTED = "accepted"
|
]
|
||||||
REJECTED = "rejected"
|
|
||||||
DISMISSED = "dismissed"
|
|
||||||
|
|
||||||
|
|
||||||
class CalendarInviteContent(BaseModel):
|
class CalendarInviteContent(BaseModel):
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import Enum
|
|
||||||
from typing import ClassVar, Literal
|
from typing import ClassVar, Literal
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict
|
from pydantic import BaseModel, ConfigDict
|
||||||
|
from schemas.domain.memory_content import (
|
||||||
from schemas.memories.memory_content import (
|
|
||||||
TeamMember,
|
TeamMember,
|
||||||
UserMemoryContent,
|
UserMemoryContent,
|
||||||
UserPreferences,
|
UserPreferences,
|
||||||
@@ -15,16 +13,7 @@ from schemas.memories.memory_content import (
|
|||||||
WorkProfileContent,
|
WorkProfileContent,
|
||||||
WorkProject,
|
WorkProject,
|
||||||
)
|
)
|
||||||
|
from schemas.enums import MemoryStatus, MemoryType
|
||||||
|
|
||||||
class MemoryType(str, Enum):
|
|
||||||
USER = "user"
|
|
||||||
WORK = "work"
|
|
||||||
|
|
||||||
|
|
||||||
class MemoryStatus(str, Enum):
|
|
||||||
ACTIVE = "active"
|
|
||||||
DISABLED = "disabled"
|
|
||||||
|
|
||||||
|
|
||||||
class MemoryModel(BaseModel):
|
class MemoryModel(BaseModel):
|
||||||
@@ -5,6 +5,15 @@ from typing import ClassVar, Literal
|
|||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict, Field
|
from pydantic import BaseModel, ConfigDict, Field
|
||||||
|
from schemas.enums import ScheduleItemSourceType, ScheduleItemStatus
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"AttachmentType",
|
||||||
|
"ScheduleItemMetadataAttachment",
|
||||||
|
"ScheduleItemMetadata",
|
||||||
|
"ScheduleItemSourceType",
|
||||||
|
"ScheduleItemStatus",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class AttachmentType(str, Enum):
|
class AttachmentType(str, Enum):
|
||||||
@@ -32,16 +41,3 @@ class ScheduleItemMetadata(BaseModel):
|
|||||||
attachments: list[ScheduleItemMetadataAttachment] = Field(default_factory=list)
|
attachments: list[ScheduleItemMetadataAttachment] = Field(default_factory=list)
|
||||||
reminder_minutes: int | None = Field(default=None, ge=0, le=10080)
|
reminder_minutes: int | None = Field(default=None, ge=0, le=10080)
|
||||||
version: Literal[1] = 1
|
version: Literal[1] = 1
|
||||||
|
|
||||||
|
|
||||||
class ScheduleItemStatus(str, Enum):
|
|
||||||
ACTIVE = "active"
|
|
||||||
COMPLETED = "completed"
|
|
||||||
CANCELED = "canceled"
|
|
||||||
ARCHIVED = "archived"
|
|
||||||
|
|
||||||
|
|
||||||
class ScheduleItemSourceType(str, Enum):
|
|
||||||
MANUAL = "manual"
|
|
||||||
IMPORTED = "imported"
|
|
||||||
AGENT_GENERATED = "agent_generated"
|
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class ScheduleItemStatus(str, Enum):
|
||||||
|
ACTIVE = "active"
|
||||||
|
ARCHIVED = "archived"
|
||||||
|
|
||||||
|
|
||||||
|
class ScheduleItemSourceType(str, Enum):
|
||||||
|
MANUAL = "manual"
|
||||||
|
IMPORTED = "imported"
|
||||||
|
AGENT_GENERATED = "agent_generated"
|
||||||
|
|
||||||
|
|
||||||
|
class AutomationJobStatus(str, Enum):
|
||||||
|
ACTIVE = "active"
|
||||||
|
DISABLED = "disabled"
|
||||||
|
|
||||||
|
|
||||||
|
class ScheduleType(str, Enum):
|
||||||
|
DAILY = "daily"
|
||||||
|
WEEKLY = "weekly"
|
||||||
|
|
||||||
|
|
||||||
|
class MemoryType(str, Enum):
|
||||||
|
USER = "user"
|
||||||
|
WORK = "work"
|
||||||
|
|
||||||
|
|
||||||
|
class MemoryStatus(str, Enum):
|
||||||
|
ACTIVE = "active"
|
||||||
|
DISABLED = "disabled"
|
||||||
|
|
||||||
|
|
||||||
|
class TodoStatus(str, Enum):
|
||||||
|
PENDING = "pending"
|
||||||
|
DONE = "done"
|
||||||
|
CANCELED = "canceled"
|
||||||
|
|
||||||
|
|
||||||
|
class TodoPriority(int, Enum):
|
||||||
|
IMPORTANT_URGENT = 1
|
||||||
|
IMPORTANT_NOT_URGENT = 2
|
||||||
|
NOT_IMPORTANT_URGENT = 3
|
||||||
|
NOT_IMPORTANT_NOT_URGENT = 4
|
||||||
|
|
||||||
|
|
||||||
|
class AgentChatMessageRole(str, Enum):
|
||||||
|
USER = "user"
|
||||||
|
ASSISTANT = "assistant"
|
||||||
|
SYSTEM = "system"
|
||||||
|
TOOL = "tool"
|
||||||
|
|
||||||
|
|
||||||
|
class AgentChatSessionStatus(str, Enum):
|
||||||
|
PENDING = "pending"
|
||||||
|
RUNNING = "running"
|
||||||
|
COMPLETED = "completed"
|
||||||
|
FAILED = "failed"
|
||||||
|
|
||||||
|
|
||||||
|
class SessionType(str, Enum):
|
||||||
|
CHAT = "chat"
|
||||||
|
AUTOMATION = "automation"
|
||||||
|
|
||||||
|
|
||||||
|
class InboxMessageType(str, Enum):
|
||||||
|
FRIEND_REQUEST = "friend_request"
|
||||||
|
CALENDAR = "calendar"
|
||||||
|
SYSTEM = "system"
|
||||||
|
GROUP = "group"
|
||||||
|
|
||||||
|
|
||||||
|
class InboxMessageStatus(str, Enum):
|
||||||
|
PENDING = "pending"
|
||||||
|
ACCEPTED = "accepted"
|
||||||
|
REJECTED = "rejected"
|
||||||
|
DISMISSED = "dismissed"
|
||||||
|
|
||||||
|
|
||||||
|
class SubscriptionStatus(str, Enum):
|
||||||
|
ACTIVE = "active"
|
||||||
|
PENDING = "pending"
|
||||||
|
PAUSED = "paused"
|
||||||
|
UNSUBSCRIBED = "unsubscribed"
|
||||||
|
|
||||||
|
|
||||||
|
class NotifyLevel(str, Enum):
|
||||||
|
ALL = "all"
|
||||||
|
MENTIONS = "mentions"
|
||||||
|
NONE = "none"
|
||||||
|
|
||||||
|
|
||||||
|
class SubscriptionPermission(int, Enum):
|
||||||
|
VIEW = 1
|
||||||
|
INVITE = 2
|
||||||
|
EDIT = 4
|
||||||
|
OWNER = 7
|
||||||
|
|
||||||
|
|
||||||
|
class FriendshipStatus(str, Enum):
|
||||||
|
PENDING = "pending"
|
||||||
|
ACCEPTED = "accepted"
|
||||||
|
BLOCKED = "blocked"
|
||||||
|
DECLINED = "declined"
|
||||||
|
CANCELED = "canceled"
|
||||||
|
|
||||||
|
|
||||||
|
class InviteCodeStatus(str, Enum):
|
||||||
|
ACTIVE = "active"
|
||||||
|
DISABLED = "disabled"
|
||||||
|
EXPIRED = "expired"
|
||||||
|
|
||||||
|
|
||||||
|
class GroupStatus(str, Enum):
|
||||||
|
ACTIVE = "active"
|
||||||
|
ARCHIVED = "archived"
|
||||||
|
|
||||||
|
|
||||||
|
class GroupMemberRole(str, Enum):
|
||||||
|
OWNER = "owner"
|
||||||
|
ADMIN = "admin"
|
||||||
|
MEMBER = "member"
|
||||||
|
|
||||||
|
|
||||||
|
class GroupMemberSource(str, Enum):
|
||||||
|
INVITED = "invited"
|
||||||
|
JOINED = "joined"
|
||||||
|
|
||||||
|
|
||||||
|
class GroupMemberStatus(str, Enum):
|
||||||
|
ACTIVE = "active"
|
||||||
|
MUTED = "muted"
|
||||||
|
REMOVED = "removed"
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
from schemas.inbox.messages import InboxMessageStatus, InboxMessageType
|
|
||||||
|
|
||||||
__all__ = ["InboxMessageStatus", "InboxMessageType"]
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
from schemas.messages.chat_message import AgentChatMessage, AgentChatMessageMetadata
|
|
||||||
|
|
||||||
__all__ = ["AgentChatMessage", "AgentChatMessageMetadata"]
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
from schemas.inbox.messages import (
|
|
||||||
CalendarContent,
|
|
||||||
CalendarDeleteContent,
|
|
||||||
CalendarInviteContent,
|
|
||||||
CalendarUpdateContent,
|
|
||||||
parse_calendar_content,
|
|
||||||
)
|
|
||||||
from schemas.schedule.items import (
|
|
||||||
AttachmentType,
|
|
||||||
ScheduleItemMetadata,
|
|
||||||
ScheduleItemMetadataAttachment,
|
|
||||||
ScheduleItemSourceType,
|
|
||||||
ScheduleItemStatus,
|
|
||||||
)
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"AttachmentType",
|
|
||||||
"CalendarContent",
|
|
||||||
"CalendarDeleteContent",
|
|
||||||
"CalendarInviteContent",
|
|
||||||
"CalendarUpdateContent",
|
|
||||||
"ScheduleItemMetadata",
|
|
||||||
"ScheduleItemMetadataAttachment",
|
|
||||||
"ScheduleItemSourceType",
|
|
||||||
"ScheduleItemStatus",
|
|
||||||
"parse_calendar_content",
|
|
||||||
]
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
from schemas.sessions.chat_session import SessionStateSnapshot
|
|
||||||
|
|
||||||
__all__ = ["SessionStateSnapshot"]
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"""Shared schemas used across multiple domain modules."""
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
from .contracts import TodoOrder
|
|
||||||
|
|
||||||
__all__ = ["TodoOrder"]
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
from schemas.user.context import (
|
|
||||||
PreferenceSettings,
|
|
||||||
ProfileSettingsUnion,
|
|
||||||
ProfileSettingsV1,
|
|
||||||
UserContext,
|
|
||||||
parse_profile_settings,
|
|
||||||
upgrade_to_latest,
|
|
||||||
)
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"PreferenceSettings",
|
|
||||||
"ProfileSettingsUnion",
|
|
||||||
"ProfileSettingsV1",
|
|
||||||
"UserContext",
|
|
||||||
"parse_profile_settings",
|
|
||||||
"upgrade_to_latest",
|
|
||||||
]
|
|
||||||
@@ -9,10 +9,11 @@ from fastapi import HTTPException
|
|||||||
from sqlalchemy import Select, select
|
from sqlalchemy import Select, select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from models.agent_chat_message import AgentChatMessage, AgentChatMessageRole
|
from models.agent_chat_message import AgentChatMessage
|
||||||
from models.agent_chat_session import AgentChatSession
|
from models.agent_chat_session import AgentChatSession
|
||||||
from models.system_agents import SystemAgents
|
from models.system_agents import SystemAgents
|
||||||
from schemas.messages.chat_message import (
|
from schemas.enums import AgentChatMessageRole
|
||||||
|
from schemas.domain.chat_message import (
|
||||||
AgentChatMessage as AgentChatMessageSchema,
|
AgentChatMessage as AgentChatMessageSchema,
|
||||||
AgentChatMessageMetadata,
|
AgentChatMessageMetadata,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ from schemas.agent.forwarded_props import (
|
|||||||
RuntimeMode,
|
RuntimeMode,
|
||||||
)
|
)
|
||||||
from schemas.agent.visibility import SystemVisibilityBit, bit_mask
|
from schemas.agent.visibility import SystemVisibilityBit, bit_mask
|
||||||
from schemas.automation import RuntimeConfig
|
from schemas.domain.automation import RuntimeConfig
|
||||||
from schemas.messages.chat_message import (
|
from schemas.domain.chat_message import (
|
||||||
AgentChatMessageMetadata,
|
AgentChatMessageMetadata,
|
||||||
UserMessageAttachment,
|
UserMessageAttachment,
|
||||||
extract_user_message_attachments,
|
extract_user_message_attachments,
|
||||||
@@ -362,7 +362,7 @@ class AgentService:
|
|||||||
before: date | None,
|
before: date | None,
|
||||||
current_user: CurrentUser,
|
current_user: CurrentUser,
|
||||||
) -> HistorySnapshotResponse:
|
) -> HistorySnapshotResponse:
|
||||||
from schemas.messages.chat_message import AgentChatMessage
|
from schemas.domain.chat_message import AgentChatMessage
|
||||||
from v1.agent.utils import convert_message_to_history
|
from v1.agent.utils import convert_message_to_history
|
||||||
from v1.agent.schemas import HistoryMessage
|
from v1.agent.schemas import HistoryMessage
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from pydantic import ValidationError
|
|||||||
|
|
||||||
from core.agentscope.tools.tool_config import AgentTool
|
from core.agentscope.tools.tool_config import AgentTool
|
||||||
from schemas.agent.system_agent import SystemAgentLLMConfig
|
from schemas.agent.system_agent import SystemAgentLLMConfig
|
||||||
from schemas.automation import (
|
from schemas.domain.automation import (
|
||||||
ContextSource,
|
ContextSource,
|
||||||
ContextWindowMode,
|
ContextWindowMode,
|
||||||
MessageContextConfig,
|
MessageContextConfig,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from collections.abc import Callable
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from core.agentscope.runtime.ui_compiler import compile as compile_ui_hints
|
from core.agentscope.runtime.ui_compiler import compile as compile_ui_hints
|
||||||
from schemas.messages.chat_message import (
|
from schemas.domain.chat_message import (
|
||||||
AgentChatMessage,
|
AgentChatMessage,
|
||||||
AgentChatMessageMetadata,
|
AgentChatMessageMetadata,
|
||||||
extract_user_message_attachments,
|
extract_user_message_attachments,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from typing import Any
|
|||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from schemas.automation import AutomationJobConfig
|
from schemas.domain.automation import AutomationJobConfig
|
||||||
|
|
||||||
_CONFIG_NAME_PATTERN = re.compile(r"^[a-z0-9][a-z0-9_-]{0,63}$")
|
_CONFIG_NAME_PATTERN = re.compile(r"^[a-z0-9][a-z0-9_-]{0,63}$")
|
||||||
|
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ from sqlalchemy.dialects.postgresql import insert
|
|||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from core.logging import get_logger
|
from core.logging import get_logger
|
||||||
from models.automation_jobs import AutomationJob, AutomationJobStatus, ScheduleType
|
from models.automation_jobs import AutomationJob
|
||||||
from models.memories import MemoryType
|
from schemas.enums import AutomationJobStatus, MemoryType, ScheduleType
|
||||||
from models.profile import Profile
|
from models.profile import Profile
|
||||||
from schemas.automation import AutomationJobConfig, ScheduleConfig
|
from schemas.domain.automation import AutomationJobConfig, ScheduleConfig
|
||||||
from schemas.memories.memory_content import UserMemoryContent, WorkProfileContent
|
from schemas.domain.memory_content import UserMemoryContent, WorkProfileContent
|
||||||
from schemas.user.context import parse_profile_settings
|
from schemas.shared.user import parse_profile_settings
|
||||||
from v1.auth.automation_static_config import load_static_automation_job_config
|
from v1.auth.automation_static_config import load_static_automation_job_config
|
||||||
from v1.auth.schemas import RegistrationBootstrapRequest
|
from v1.auth.schemas import RegistrationBootstrapRequest
|
||||||
from v1.memories.repository import SQLAlchemyMemoriesRepository
|
from v1.memories.repository import SQLAlchemyMemoriesRepository
|
||||||
|
|||||||
@@ -9,9 +9,10 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||||||
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||||
|
|
||||||
from core.db.base_repository import BaseRepository
|
from core.db.base_repository import BaseRepository
|
||||||
from models.agent_chat_session import AgentChatSession, SessionType
|
from models.agent_chat_session import AgentChatSession
|
||||||
from models.automation_jobs import AutomationJob, AutomationJobStatus, ScheduleType
|
from models.automation_jobs import AutomationJob
|
||||||
from schemas.automation import AutomationJobConfig, ScheduleConfig
|
from schemas.enums import AutomationJobStatus, ScheduleType, SessionType
|
||||||
|
from schemas.domain.automation import AutomationJobConfig, ScheduleConfig
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from v1.automation_jobs.schemas import (
|
from v1.automation_jobs.schemas import (
|
||||||
|
|||||||
@@ -7,9 +7,8 @@ from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
|||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
||||||
|
|
||||||
from models.automation_jobs import AutomationJob as OrmAutomationJob
|
from schemas.domain.automation import AutomationJobConfig
|
||||||
from models.automation_jobs import AutomationJobStatus
|
from schemas.enums import AutomationJobStatus
|
||||||
from schemas.automation import AutomationJobConfig
|
|
||||||
|
|
||||||
|
|
||||||
class AutomationJobResponse(BaseModel):
|
class AutomationJobResponse(BaseModel):
|
||||||
@@ -29,20 +28,20 @@ class AutomationJobResponse(BaseModel):
|
|||||||
updated_at: datetime
|
updated_at: datetime
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_orm(cls, obj: OrmAutomationJob) -> Self:
|
def from_orm(cls, obj: object) -> Self:
|
||||||
return cls(
|
return cls(
|
||||||
id=obj.id,
|
id=getattr(obj, "id"),
|
||||||
owner_id=obj.owner_id,
|
owner_id=getattr(obj, "owner_id"),
|
||||||
bootstrap_key=obj.bootstrap_key,
|
bootstrap_key=getattr(obj, "bootstrap_key"),
|
||||||
title=obj.title,
|
title=getattr(obj, "title"),
|
||||||
timezone=obj.timezone,
|
timezone=getattr(obj, "timezone"),
|
||||||
status=obj.status,
|
status=getattr(obj, "status"),
|
||||||
is_system=obj.bootstrap_key is not None,
|
is_system=getattr(obj, "bootstrap_key") is not None,
|
||||||
config=AutomationJobConfig.model_validate(obj.config or {}),
|
config=AutomationJobConfig.model_validate(getattr(obj, "config", {}) or {}),
|
||||||
next_run_at=obj.next_run_at,
|
next_run_at=getattr(obj, "next_run_at"),
|
||||||
last_run_at=obj.last_run_at,
|
last_run_at=getattr(obj, "last_run_at"),
|
||||||
created_at=obj.created_at,
|
created_at=getattr(obj, "created_at"),
|
||||||
updated_at=obj.updated_at,
|
updated_at=getattr(obj, "updated_at"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ from uuid import UUID
|
|||||||
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||||
|
|
||||||
from fastapi import HTTPException, status
|
from fastapi import HTTPException, status
|
||||||
from models.automation_jobs import ScheduleType
|
from schemas.enums import ScheduleType
|
||||||
from schemas.automation import (
|
from schemas.domain.automation import (
|
||||||
AutomationJob as AutomationJobSchema,
|
AutomationJob as AutomationJobSchema,
|
||||||
MessageContextConfig,
|
MessageContextConfig,
|
||||||
RuntimeConfig,
|
RuntimeConfig,
|
||||||
|
|||||||
@@ -9,9 +9,10 @@ from sqlalchemy.exc import SQLAlchemyError
|
|||||||
|
|
||||||
from core.db.base_repository import BaseRepository
|
from core.db.base_repository import BaseRepository
|
||||||
from core.logging import get_logger
|
from core.logging import get_logger
|
||||||
from models.friendships import Friendship, FriendshipStatus
|
from models.friendships import Friendship
|
||||||
from models.inbox_messages import InboxMessage, InboxMessageStatus, InboxMessageType
|
from models.inbox_messages import InboxMessage
|
||||||
from schemas.inbox.messages import FriendshipContent
|
from schemas.enums import FriendshipStatus, InboxMessageStatus, InboxMessageType
|
||||||
|
from schemas.domain.inbox import FriendshipContent
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from uuid import UUID
|
|||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict, Field
|
from pydantic import BaseModel, ConfigDict, Field
|
||||||
|
|
||||||
from schemas.user.context import UserContext
|
from schemas.shared.user import UserContext
|
||||||
|
|
||||||
|
|
||||||
class FriendRequestCreate(BaseModel):
|
class FriendRequestCreate(BaseModel):
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ from sqlalchemy.exc import SQLAlchemyError
|
|||||||
from core.auth.models import CurrentUser
|
from core.auth.models import CurrentUser
|
||||||
from core.db.base_service import BaseService
|
from core.db.base_service import BaseService
|
||||||
from core.logging import get_logger
|
from core.logging import get_logger
|
||||||
from models.friendships import Friendship, FriendshipStatus
|
from models.friendships import Friendship
|
||||||
from models.inbox_messages import InboxMessage, InboxMessageStatus, InboxMessageType
|
from models.inbox_messages import InboxMessage
|
||||||
|
from schemas.enums import FriendshipStatus, InboxMessageStatus, InboxMessageType
|
||||||
from v1.friendships.repository import FriendshipRepository
|
from v1.friendships.repository import FriendshipRepository
|
||||||
from v1.friendships.schemas import (
|
from v1.friendships.schemas import (
|
||||||
FriendRequestCreate,
|
FriendRequestCreate,
|
||||||
@@ -22,7 +23,7 @@ from v1.users.repository import UserRepository
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from schemas.user.context import UserContext
|
from schemas.shared.user import UserContext
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger("v1.friendships.service")
|
logger = get_logger("v1.friendships.service")
|
||||||
@@ -593,7 +594,7 @@ class FriendshipService(BaseService):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _build_user_basic_info(self, profile: Any) -> "UserContext":
|
def _build_user_basic_info(self, profile: Any) -> "UserContext":
|
||||||
from schemas.user.context import UserContext
|
from schemas.shared.user import UserContext
|
||||||
|
|
||||||
if profile is None:
|
if profile is None:
|
||||||
return UserContext(id="", username="")
|
return UserContext(id="", username="")
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ from sqlalchemy import select, update
|
|||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
|
||||||
from core.logging import get_logger
|
from core.logging import get_logger
|
||||||
from models.inbox_messages import InboxMessage, InboxMessageType, InboxMessageStatus
|
from models.inbox_messages import InboxMessage
|
||||||
|
from schemas.enums import InboxMessageStatus, InboxMessageType
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from uuid import UUID
|
|||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict
|
from pydantic import BaseModel, ConfigDict
|
||||||
|
|
||||||
from schemas.inbox.messages import InboxMessageStatus, InboxMessageType
|
from schemas.domain.inbox import InboxMessageStatus, InboxMessageType
|
||||||
|
|
||||||
|
|
||||||
class InboxMessageResponse(BaseModel):
|
class InboxMessageResponse(BaseModel):
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ from sqlalchemy.exc import SQLAlchemyError
|
|||||||
|
|
||||||
from core.db.base_repository import BaseRepository
|
from core.db.base_repository import BaseRepository
|
||||||
from core.logging import get_logger
|
from core.logging import get_logger
|
||||||
from models.memories import Memory, MemoryStatus, MemoryType
|
from models.memories import Memory
|
||||||
|
from schemas.enums import MemoryStatus, MemoryType
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from typing import Annotated
|
|||||||
|
|
||||||
from fastapi import APIRouter, Depends, status
|
from fastapi import APIRouter, Depends, status
|
||||||
|
|
||||||
from schemas.memories.memory_content import UserMemoryContent, WorkProfileContent
|
from schemas.domain.memory_content import UserMemoryContent, WorkProfileContent
|
||||||
from v1.memories.dependencies import get_memories_service
|
from v1.memories.dependencies import get_memories_service
|
||||||
from v1.memories.schemas import (
|
from v1.memories.schemas import (
|
||||||
MemoryListResponse,
|
MemoryListResponse,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from typing import ClassVar
|
|||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict
|
from pydantic import BaseModel, ConfigDict
|
||||||
|
|
||||||
from schemas.memories.memory_content import UserMemoryContent, WorkProfileContent
|
from schemas.domain.memory_content import UserMemoryContent, WorkProfileContent
|
||||||
|
|
||||||
|
|
||||||
class UserMemoryUpdate(BaseModel):
|
class UserMemoryUpdate(BaseModel):
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ from sqlalchemy.exc import SQLAlchemyError
|
|||||||
from core.auth.models import CurrentUser
|
from core.auth.models import CurrentUser
|
||||||
from core.db.base_service import BaseService
|
from core.db.base_service import BaseService
|
||||||
from core.logging import get_logger
|
from core.logging import get_logger
|
||||||
from models.memories import Memory, MemoryType
|
from models.memories import Memory
|
||||||
from schemas.memories.memory_content import UserMemoryContent, WorkProfileContent
|
from schemas.enums import MemoryType
|
||||||
|
from schemas.domain.memory_content import UserMemoryContent, WorkProfileContent
|
||||||
from v1.memories.repository import MemoriesRepositoryLike
|
from v1.memories.repository import MemoriesRepositoryLike
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ from sqlalchemy.exc import SQLAlchemyError
|
|||||||
|
|
||||||
from core.db.base_repository import BaseRepository
|
from core.db.base_repository import BaseRepository
|
||||||
from core.logging import get_logger
|
from core.logging import get_logger
|
||||||
from models.schedule_items import ScheduleItem, ScheduleItemStatus
|
from models.schedule_items import ScheduleItem
|
||||||
from models.schedule_subscriptions import ScheduleSubscription, SubscriptionStatus
|
from models.schedule_subscriptions import ScheduleSubscription
|
||||||
|
from schemas.enums import ScheduleItemStatus, SubscriptionStatus
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
|||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||||
|
|
||||||
from schemas.inbox.messages import (
|
from schemas.domain.inbox import (
|
||||||
CalendarContent,
|
CalendarContent,
|
||||||
CalendarDeleteContent,
|
CalendarDeleteContent,
|
||||||
CalendarInviteContent,
|
CalendarInviteContent,
|
||||||
CalendarUpdateContent,
|
CalendarUpdateContent,
|
||||||
parse_calendar_content,
|
parse_calendar_content,
|
||||||
)
|
)
|
||||||
from schemas.schedule.items import (
|
from schemas.domain.schedule import (
|
||||||
AttachmentType,
|
AttachmentType,
|
||||||
ScheduleItemMetadata,
|
ScheduleItemMetadata,
|
||||||
ScheduleItemMetadataAttachment,
|
ScheduleItemMetadataAttachment,
|
||||||
|
|||||||
@@ -10,9 +10,14 @@ from sqlalchemy.exc import SQLAlchemyError
|
|||||||
from core.auth.models import CurrentUser
|
from core.auth.models import CurrentUser
|
||||||
from core.db.base_service import BaseService
|
from core.db.base_service import BaseService
|
||||||
from core.logging import get_logger
|
from core.logging import get_logger
|
||||||
from models.inbox_messages import InboxMessage, InboxMessageType, InboxMessageStatus
|
from models.inbox_messages import InboxMessage
|
||||||
from models.schedule_items import ScheduleItem
|
from models.schedule_items import ScheduleItem
|
||||||
from models.schedule_subscriptions import SubscriptionPermission, SubscriptionStatus
|
from schemas.enums import (
|
||||||
|
InboxMessageStatus,
|
||||||
|
InboxMessageType,
|
||||||
|
SubscriptionPermission,
|
||||||
|
SubscriptionStatus,
|
||||||
|
)
|
||||||
from v1.auth.gateway import SupabaseAuthGateway
|
from v1.auth.gateway import SupabaseAuthGateway
|
||||||
from v1.inbox_messages.repository import InboxMessageRepository
|
from v1.inbox_messages.repository import InboxMessageRepository
|
||||||
from v1.schedule_items.repository import ScheduleItemRepository
|
from v1.schedule_items.repository import ScheduleItemRepository
|
||||||
@@ -35,6 +40,8 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
logger = get_logger("v1.schedule_items.service")
|
logger = get_logger("v1.schedule_items.service")
|
||||||
|
|
||||||
|
_LEGACY_ARCHIVED_STATUSES = {"completed", "canceled"}
|
||||||
|
|
||||||
|
|
||||||
class AuthByPhoneGateway(Protocol):
|
class AuthByPhoneGateway(Protocol):
|
||||||
async def get_user_by_phone(self, phone: str) -> "UserByPhoneResponse": ...
|
async def get_user_by_phone(self, phone: str) -> "UserByPhoneResponse": ...
|
||||||
@@ -417,8 +424,10 @@ class ScheduleItemService(BaseService):
|
|||||||
permission: int = 1,
|
permission: int = 1,
|
||||||
) -> ScheduleItemResponse:
|
) -> ScheduleItemResponse:
|
||||||
status_value = (
|
status_value = (
|
||||||
item.status.value if hasattr(item.status, "value") else item.status
|
item.status.value if hasattr(item.status, "value") else str(item.status)
|
||||||
)
|
)
|
||||||
|
if status_value in _LEGACY_ARCHIVED_STATUSES:
|
||||||
|
status_value = ScheduleItemStatus.ARCHIVED.value
|
||||||
source_type_value = (
|
source_type_value = (
|
||||||
item.source_type.value
|
item.source_type.value
|
||||||
if hasattr(item.source_type, "value")
|
if hasattr(item.source_type, "value")
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ from sqlalchemy.exc import SQLAlchemyError
|
|||||||
from core.db.base_repository import BaseRepository
|
from core.db.base_repository import BaseRepository
|
||||||
from core.logging import get_logger
|
from core.logging import get_logger
|
||||||
from models.todo_sources import TodoSource
|
from models.todo_sources import TodoSource
|
||||||
from models.todos import Todo, TodoPriority, TodoStatus
|
from models.todos import Todo
|
||||||
|
from schemas.enums import TodoPriority, TodoStatus
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from uuid import UUID
|
|||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict, Field
|
from pydantic import BaseModel, ConfigDict, Field
|
||||||
|
|
||||||
from schemas.todo import TodoOrder
|
from schemas.domain.todo import TodoOrder
|
||||||
|
|
||||||
|
|
||||||
class TodoCreate(BaseModel):
|
class TodoCreate(BaseModel):
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ from sqlalchemy.exc import SQLAlchemyError
|
|||||||
from core.auth.models import CurrentUser
|
from core.auth.models import CurrentUser
|
||||||
from core.db.base_service import BaseService
|
from core.db.base_service import BaseService
|
||||||
from core.logging import get_logger
|
from core.logging import get_logger
|
||||||
from models.todos import Todo, TodoStatus
|
from models.todos import Todo
|
||||||
|
from schemas.enums import TodoStatus
|
||||||
from v1.schedule_items.repository import SQLAlchemyScheduleItemRepository
|
from v1.schedule_items.repository import SQLAlchemyScheduleItemRepository
|
||||||
from v1.todo.repository import TodoRepository
|
from v1.todo.repository import TodoRepository
|
||||||
from v1.todo.schemas import (
|
from v1.todo.schemas import (
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from uuid import UUID
|
|||||||
|
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends
|
||||||
|
|
||||||
from schemas.user.context import UserContext
|
from schemas.shared.user import UserContext
|
||||||
from v1.users.dependencies import get_user_service
|
from v1.users.dependencies import get_user_service
|
||||||
from v1.users.schemas import UserSearchRequest, UserUpdateRequest
|
from v1.users.schemas import UserSearchRequest, UserUpdateRequest
|
||||||
from v1.users.service import UserService
|
from v1.users.service import UserService
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ from core.agentscope.persistence.user_context_cache import (
|
|||||||
)
|
)
|
||||||
from core.db.base_service import BaseService
|
from core.db.base_service import BaseService
|
||||||
from core.logging import get_logger
|
from core.logging import get_logger
|
||||||
from schemas.user.context import UserContext, parse_profile_settings
|
from schemas.shared.user import UserContext, parse_profile_settings
|
||||||
from v1.users.repository import UserRepository
|
from v1.users.repository import UserRepository
|
||||||
from v1.users.schemas import UserSearchRequest, UserUpdateRequest
|
from v1.users.schemas import UserSearchRequest, UserUpdateRequest
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from schemas.user.context import UserContext
|
from schemas.shared.user import UserContext
|
||||||
|
|
||||||
logger = get_logger("v1.users.service")
|
logger = get_logger("v1.users.service")
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ class UserService(BaseService):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def get_user_by_id(self, user_id: UUID) -> "UserContext":
|
async def get_user_by_id(self, user_id: UUID) -> "UserContext":
|
||||||
from schemas.user.context import UserContext
|
from schemas.shared.user import UserContext
|
||||||
|
|
||||||
try:
|
try:
|
||||||
profile = await self._repository.get_by_user_id(user_id)
|
profile = await self._repository.get_by_user_id(user_id)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import uvicorn
|
|||||||
|
|
||||||
from app import app
|
from app import app
|
||||||
from core.auth.models import CurrentUser
|
from core.auth.models import CurrentUser
|
||||||
from schemas.user.context import UserContext
|
from schemas.shared.user import UserContext
|
||||||
from v1.users.dependencies import get_current_user, get_user_service
|
from v1.users.dependencies import get_current_user, get_user_service
|
||||||
from v1.users.schemas import UserUpdateRequest
|
from v1.users.schemas import UserUpdateRequest
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from fastapi.testclient import TestClient
|
|||||||
|
|
||||||
from app import app
|
from app import app
|
||||||
from core.auth.models import CurrentUser
|
from core.auth.models import CurrentUser
|
||||||
from schemas.user.context import UserContext
|
from schemas.shared.user import UserContext
|
||||||
from v1.friendships.dependencies import get_friendship_service
|
from v1.friendships.dependencies import get_friendship_service
|
||||||
from v1.friendships.schemas import (
|
from v1.friendships.schemas import (
|
||||||
FriendRequestCreate,
|
FriendRequestCreate,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from fastapi.testclient import TestClient
|
|||||||
|
|
||||||
from app import app
|
from app import app
|
||||||
from core.auth.models import CurrentUser
|
from core.auth.models import CurrentUser
|
||||||
from schemas.user.context import UserContext
|
from schemas.shared.user import UserContext
|
||||||
from v1.users.dependencies import get_current_user, get_user_service
|
from v1.users.dependencies import get_current_user, get_user_service
|
||||||
from v1.users.schemas import UserSearchRequest, UserUpdateRequest
|
from v1.users.schemas import UserSearchRequest, UserUpdateRequest
|
||||||
from v1.users.service import UserService
|
from v1.users.service import UserService
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from uuid import uuid4
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from core.agentscope.persistence.user_context_cache import UserContextCache
|
from core.agentscope.persistence.user_context_cache import UserContextCache
|
||||||
from schemas.user.context import (
|
from schemas.shared.user import (
|
||||||
UserContext,
|
UserContext,
|
||||||
parse_profile_settings,
|
parse_profile_settings,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from core.agentscope.runtime.registry_builder import build_consumer_registry
|
|
||||||
|
|
||||||
|
|
||||||
def test_build_consumer_registry_from_system_agent_configs() -> None:
|
|
||||||
registry = build_consumer_registry(
|
|
||||||
system_agent_configs={
|
|
||||||
"router": {"config": {"visibility_consumer_bit": 16}},
|
|
||||||
"worker": {"config": {"visibility_consumer_bit": 17}},
|
|
||||||
"memory": {"config": {"visibility_consumer_bit": 18}},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
assert registry.resolve_agent_bit(agent_type="router") == 16
|
|
||||||
assert registry.resolve_agent_bit(agent_type="worker") == 17
|
|
||||||
|
|
||||||
|
|
||||||
def test_build_consumer_registry_rejects_duplicate_bit() -> None:
|
|
||||||
with pytest.raises(ValueError, match="duplicate visibility bit"):
|
|
||||||
build_consumer_registry(
|
|
||||||
system_agent_configs={
|
|
||||||
"router": {"config": {"visibility_consumer_bit": 16}},
|
|
||||||
"worker": {"config": {"visibility_consumer_bit": 16}},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@@ -6,8 +6,8 @@ import pytest
|
|||||||
from ag_ui.core import RunAgentInput
|
from ag_ui.core import RunAgentInput
|
||||||
|
|
||||||
from core.agentscope.runtime.orchestrator import AgentScopeRuntimeOrchestrator
|
from core.agentscope.runtime.orchestrator import AgentScopeRuntimeOrchestrator
|
||||||
from schemas.automation import MessageContextConfig, RuntimeConfig
|
from schemas.domain.automation import MessageContextConfig, RuntimeConfig
|
||||||
from schemas.user import UserContext, parse_profile_settings
|
from schemas.shared.user import UserContext, parse_profile_settings
|
||||||
|
|
||||||
|
|
||||||
class _FakePipeline:
|
class _FakePipeline:
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from core.agentscope.runtime.pipeline_registry import build_default_pipeline_spec
|
|
||||||
|
|
||||||
|
|
||||||
def test_build_default_pipeline_spec_worker_has_two_stages() -> None:
|
|
||||||
spec = build_default_pipeline_spec(mode="worker")
|
|
||||||
|
|
||||||
assert spec.mode == "worker"
|
|
||||||
assert [item.stage_name for item in spec.stages] == ["router", "worker"]
|
|
||||||
|
|
||||||
|
|
||||||
def test_build_default_pipeline_spec_memory_has_single_stage() -> None:
|
|
||||||
spec = build_default_pipeline_spec(mode="memory")
|
|
||||||
|
|
||||||
assert spec.mode == "memory"
|
|
||||||
assert [item.stage_name for item in spec.stages] == ["memory"]
|
|
||||||
|
|
||||||
|
|
||||||
def test_build_default_pipeline_spec_rejects_unknown_mode() -> None:
|
|
||||||
with pytest.raises(ValueError, match="unsupported pipeline mode"):
|
|
||||||
build_default_pipeline_spec(mode="planner")
|
|
||||||
@@ -18,8 +18,8 @@ from schemas.agent.runtime_models import (
|
|||||||
WorkerAgentOutputLite,
|
WorkerAgentOutputLite,
|
||||||
)
|
)
|
||||||
from schemas.agent.system_agent import AgentType
|
from schemas.agent.system_agent import AgentType
|
||||||
from schemas.automation import MessageContextConfig, RuntimeConfig
|
from schemas.domain.automation import MessageContextConfig, RuntimeConfig
|
||||||
from schemas.user import UserContext, parse_profile_settings
|
from schemas.shared.user import UserContext, parse_profile_settings
|
||||||
|
|
||||||
|
|
||||||
def _run_input() -> RunAgentInput:
|
def _run_input() -> RunAgentInput:
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import pytest
|
|||||||
|
|
||||||
import core.agentscope.runtime.tasks as tasks_module
|
import core.agentscope.runtime.tasks as tasks_module
|
||||||
from schemas.agent import ToolStatus
|
from schemas.agent import ToolStatus
|
||||||
from schemas.automation import ContextWindowMode, MessageContextConfig
|
from schemas.domain.automation import ContextWindowMode, MessageContextConfig
|
||||||
from schemas.user import UserContext, parse_profile_settings
|
from schemas.shared.user import UserContext, parse_profile_settings
|
||||||
|
|
||||||
|
|
||||||
def _run_input_payload() -> dict[str, Any]:
|
def _run_input_payload() -> dict[str, Any]:
|
||||||
|
|||||||
@@ -1,250 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from core.agentscope.schemas.agui_input import (
|
|
||||||
MAX_MESSAGES,
|
|
||||||
MAX_RUN_ID_LENGTH,
|
|
||||||
MAX_RUN_INPUT_BYTES,
|
|
||||||
MAX_TEXT_CHARS,
|
|
||||||
parse_run_input,
|
|
||||||
validate_run_request_messages_contract,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _base_payload() -> dict[str, object]:
|
|
||||||
return {
|
|
||||||
"threadId": "00000000-0000-0000-0000-000000000001",
|
|
||||||
"runId": "run-1",
|
|
||||||
"state": {},
|
|
||||||
"messages": [{"id": "u1", "role": "user", "content": "hello"}],
|
|
||||||
"tools": [],
|
|
||||||
"context": [],
|
|
||||||
"forwardedProps": {"agent_type": "worker"},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_run_input_rejects_invalid_uuid() -> None:
|
|
||||||
payload = _base_payload()
|
|
||||||
payload["threadId"] = "bad-uuid"
|
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="threadId must be a valid UUID"):
|
|
||||||
parse_run_input(payload)
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_run_input_rejects_message_count_over_limit() -> None:
|
|
||||||
payload = _base_payload()
|
|
||||||
payload["messages"] = [
|
|
||||||
{"id": f"u{i}", "role": "user", "content": "x"} for i in range(MAX_MESSAGES + 1)
|
|
||||||
]
|
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="RunAgentInput.messages exceeds limit"):
|
|
||||||
parse_run_input(payload)
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_run_input_rejects_user_text_over_limit() -> None:
|
|
||||||
payload = _base_payload()
|
|
||||||
payload["messages"] = [
|
|
||||||
{"id": "u1", "role": "user", "content": "x" * (MAX_TEXT_CHARS + 1)}
|
|
||||||
]
|
|
||||||
|
|
||||||
with pytest.raises(
|
|
||||||
ValueError, match="RunAgentInput user message text exceeds limit"
|
|
||||||
):
|
|
||||||
parse_run_input(payload)
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_run_input_rejects_payload_over_limit() -> None:
|
|
||||||
payload = _base_payload()
|
|
||||||
payload["forwardedProps"] = {"blob": "x" * MAX_RUN_INPUT_BYTES}
|
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="RunAgentInput payload exceeds size limit"):
|
|
||||||
parse_run_input(payload)
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_run_input_rejects_run_id_over_limit() -> None:
|
|
||||||
payload = _base_payload()
|
|
||||||
payload["runId"] = "r" * (MAX_RUN_ID_LENGTH + 1)
|
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="runId exceeds length limit"):
|
|
||||||
parse_run_input(payload)
|
|
||||||
|
|
||||||
|
|
||||||
def test_validate_run_request_messages_contract_requires_single_user_message() -> None:
|
|
||||||
payload = _base_payload()
|
|
||||||
payload["messages"] = [
|
|
||||||
{"id": "u1", "role": "user", "content": "hello"},
|
|
||||||
{"id": "u2", "role": "user", "content": "again"},
|
|
||||||
]
|
|
||||||
run_input = parse_run_input(payload)
|
|
||||||
|
|
||||||
with pytest.raises(
|
|
||||||
ValueError,
|
|
||||||
match="RunAgentInput.messages must contain exactly one user message",
|
|
||||||
):
|
|
||||||
validate_run_request_messages_contract(run_input)
|
|
||||||
|
|
||||||
|
|
||||||
def test_validate_run_request_messages_contract_accepts_binary_url_blocks() -> None:
|
|
||||||
payload = _base_payload()
|
|
||||||
payload["messages"] = [
|
|
||||||
{
|
|
||||||
"id": "u1",
|
|
||||||
"role": "user",
|
|
||||||
"content": [
|
|
||||||
{"type": "text", "text": "请分析"},
|
|
||||||
{
|
|
||||||
"type": "binary",
|
|
||||||
"mimeType": "image/png",
|
|
||||||
"url": "https://signed.example/a.png",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
]
|
|
||||||
run_input = parse_run_input(payload)
|
|
||||||
|
|
||||||
validate_run_request_messages_contract(run_input)
|
|
||||||
|
|
||||||
|
|
||||||
def test_validate_run_request_messages_contract_rejects_binary_data_block() -> None:
|
|
||||||
payload = _base_payload()
|
|
||||||
payload["messages"] = [
|
|
||||||
{
|
|
||||||
"id": "u1",
|
|
||||||
"role": "user",
|
|
||||||
"content": [
|
|
||||||
{"type": "text", "text": "请分析"},
|
|
||||||
{
|
|
||||||
"type": "binary",
|
|
||||||
"mimeType": "image/png",
|
|
||||||
"data": "aGVsbG8=",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
]
|
|
||||||
run_input = parse_run_input(payload)
|
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="binary content requires url"):
|
|
||||||
validate_run_request_messages_contract(run_input)
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_run_input_accepts_snake_case_aliases() -> None:
|
|
||||||
payload = {
|
|
||||||
"thread_id": "00000000-0000-0000-0000-000000000001",
|
|
||||||
"run_id": "run-1",
|
|
||||||
"state": {},
|
|
||||||
"messages": [
|
|
||||||
{
|
|
||||||
"id": "u1",
|
|
||||||
"role": "user",
|
|
||||||
"content": [
|
|
||||||
{"type": "text", "text": "hello"},
|
|
||||||
{
|
|
||||||
"type": "binary",
|
|
||||||
"mime_type": "image/png",
|
|
||||||
"url": "https://signed.example/a.png",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tools": [],
|
|
||||||
"context": [],
|
|
||||||
"forwarded_props": {"agent_type": "worker"},
|
|
||||||
}
|
|
||||||
|
|
||||||
run_input = parse_run_input(payload)
|
|
||||||
|
|
||||||
assert run_input.thread_id == "00000000-0000-0000-0000-000000000001"
|
|
||||||
assert run_input.run_id == "run-1"
|
|
||||||
validate_run_request_messages_contract(run_input)
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_run_input_accepts_client_time_forwarded_props() -> None:
|
|
||||||
payload = _base_payload()
|
|
||||||
payload["forwardedProps"] = {
|
|
||||||
"agent_type": "worker",
|
|
||||||
"client_time": {
|
|
||||||
"device_timezone": "America/Los_Angeles",
|
|
||||||
"client_now_iso": "2026-03-16T09:12:33-07:00",
|
|
||||||
"client_epoch_ms": 1773658353000,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
run_input = parse_run_input(payload)
|
|
||||||
|
|
||||||
assert run_input.forwarded_props is not None
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_run_input_rejects_invalid_client_time_timezone() -> None:
|
|
||||||
payload = _base_payload()
|
|
||||||
payload["forwardedProps"] = {
|
|
||||||
"agent_type": "worker",
|
|
||||||
"client_time": {
|
|
||||||
"device_timezone": "Mars/OlympusMons",
|
|
||||||
"client_now_iso": "2026-03-16T09:12:33-07:00",
|
|
||||||
"client_epoch_ms": 1773658353000,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="invalid RunAgentInput.forwardedProps"):
|
|
||||||
parse_run_input(payload)
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_run_input_rejects_invalid_client_time_now_iso() -> None:
|
|
||||||
payload = _base_payload()
|
|
||||||
payload["forwardedProps"] = {
|
|
||||||
"agent_type": "worker",
|
|
||||||
"client_time": {
|
|
||||||
"device_timezone": "America/Los_Angeles",
|
|
||||||
"client_now_iso": "2026-03-16 09:12:33",
|
|
||||||
"client_epoch_ms": 1773658353000,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="invalid RunAgentInput.forwardedProps"):
|
|
||||||
parse_run_input(payload)
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_run_input_rejects_invalid_client_time_epoch_type() -> None:
|
|
||||||
payload = _base_payload()
|
|
||||||
payload["forwardedProps"] = {
|
|
||||||
"agent_type": "worker",
|
|
||||||
"client_time": {
|
|
||||||
"device_timezone": "America/Los_Angeles",
|
|
||||||
"client_now_iso": "2026-03-16T09:12:33-07:00",
|
|
||||||
"client_epoch_ms": "1773658353000",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="invalid RunAgentInput.forwardedProps"):
|
|
||||||
parse_run_input(payload)
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_run_input_rejects_unknown_forwarded_props_key() -> None:
|
|
||||||
payload = _base_payload()
|
|
||||||
payload["forwardedProps"] = {
|
|
||||||
"agent_type": "worker",
|
|
||||||
"client_time": {
|
|
||||||
"device_timezone": "America/Los_Angeles",
|
|
||||||
"client_now_iso": "2026-03-16T09:12:33-07:00",
|
|
||||||
"client_epoch_ms": 1773658353000,
|
|
||||||
},
|
|
||||||
"unexpected": {"foo": "bar"},
|
|
||||||
}
|
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="invalid RunAgentInput.forwardedProps"):
|
|
||||||
parse_run_input(payload)
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_run_input_rejects_missing_forwarded_props_agent_type() -> None:
|
|
||||||
payload = _base_payload()
|
|
||||||
payload["forwardedProps"] = {
|
|
||||||
"client_time": {
|
|
||||||
"device_timezone": "America/Los_Angeles",
|
|
||||||
"client_now_iso": "2026-03-16T09:12:33-07:00",
|
|
||||||
"client_epoch_ms": 1773658353000,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="invalid RunAgentInput.forwardedProps"):
|
|
||||||
parse_run_input(payload)
|
|
||||||
@@ -40,6 +40,7 @@ class _FakeService:
|
|||||||
start_at=datetime(2026, 3, 17, 9, 0, tzinfo=timezone.utc),
|
start_at=datetime(2026, 3, 17, 9, 0, tzinfo=timezone.utc),
|
||||||
end_at=datetime(2026, 3, 17, 9, 30, tzinfo=timezone.utc),
|
end_at=datetime(2026, 3, 17, 9, 30, tzinfo=timezone.utc),
|
||||||
timezone="Asia/Shanghai",
|
timezone="Asia/Shanghai",
|
||||||
|
status="active",
|
||||||
metadata=SimpleNamespace(
|
metadata=SimpleNamespace(
|
||||||
location=None, color="#4F46E5", reminder_minutes=15
|
location=None, color="#4F46E5", reminder_minutes=15
|
||||||
),
|
),
|
||||||
@@ -247,7 +248,7 @@ async def test_calendar_read_returns_structured_result_with_ids(
|
|||||||
assert "total=1" in payload["result"]
|
assert "total=1" in payload["result"]
|
||||||
assert "timezone=Asia/Shanghai" in payload["result"]
|
assert "timezone=Asia/Shanghai" in payload["result"]
|
||||||
assert "description=今天下午五点的会议" in payload["result"]
|
assert "description=今天下午五点的会议" in payload["result"]
|
||||||
assert "status=" in payload["result"]
|
assert "status=active" in payload["result"]
|
||||||
assert fake_service.created_id in payload["result"]
|
assert fake_service.created_id in payload["result"]
|
||||||
assert fake_service.list_calls == [{"page": 1, "page_size": 20, "query": "会议"}]
|
assert fake_service.list_calls == [{"page": 1, "page_size": 20, "query": "会议"}]
|
||||||
|
|
||||||
|
|||||||
@@ -1,132 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import json
|
|
||||||
from typing import Any, AsyncGenerator
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from core.agentscope.tools.tool_config import ToolApprovalConfig, ToolConfig, ToolGroup
|
|
||||||
from core.agentscope.tools.tool_middleware import create_approval_middleware
|
|
||||||
|
|
||||||
|
|
||||||
async def _next_handler(**kwargs: Any) -> AsyncGenerator[dict[str, object], None]:
|
|
||||||
async def _generator() -> AsyncGenerator[dict[str, object], None]:
|
|
||||||
yield {"ok": True, "tool_call": kwargs.get("tool_call")}
|
|
||||||
|
|
||||||
return _generator()
|
|
||||||
|
|
||||||
|
|
||||||
def _extract_error_payload(chunk: object) -> dict[str, Any]:
|
|
||||||
content = getattr(chunk, "content", None)
|
|
||||||
if not isinstance(content, list) or not content:
|
|
||||||
return {}
|
|
||||||
first_block = content[0]
|
|
||||||
text = getattr(first_block, "text", None)
|
|
||||||
if not isinstance(text, str) and isinstance(first_block, dict):
|
|
||||||
raw_text = first_block.get("text")
|
|
||||||
text = raw_text if isinstance(raw_text, str) else None
|
|
||||||
if not isinstance(text, str):
|
|
||||||
return {}
|
|
||||||
return json.loads(text)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_hitl_middleware_default_write_does_not_require_approval() -> None:
|
|
||||||
middleware = create_approval_middleware(
|
|
||||||
config_by_name={
|
|
||||||
"calendar_write": ToolConfig(
|
|
||||||
name="calendar_write",
|
|
||||||
group=ToolGroup.EXECUTE,
|
|
||||||
approval=ToolApprovalConfig(required=False),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
responses = []
|
|
||||||
async for chunk in middleware(
|
|
||||||
{"tool_call": {"name": "calendar.write", "input": {"operation": "create"}}},
|
|
||||||
_next_handler,
|
|
||||||
):
|
|
||||||
responses.append(chunk)
|
|
||||||
|
|
||||||
assert responses[0]["ok"] is True
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_hitl_middleware_pending_when_tool_requires_approval() -> None:
|
|
||||||
middleware = create_approval_middleware(
|
|
||||||
config_by_name={
|
|
||||||
"calendar_write": ToolConfig(
|
|
||||||
name="calendar_write",
|
|
||||||
group=ToolGroup.EXECUTE,
|
|
||||||
approval=ToolApprovalConfig(required=True),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
responses = []
|
|
||||||
async for chunk in middleware(
|
|
||||||
{"tool_call": {"name": "calendar_write", "input": {"operation": "create"}}},
|
|
||||||
_next_handler,
|
|
||||||
):
|
|
||||||
responses.append(chunk)
|
|
||||||
|
|
||||||
payload = _extract_error_payload(responses[0])
|
|
||||||
assert payload["error"]["code"] == "TOOL_PENDING_APPROVAL"
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_hitl_middleware_passes_when_write_approved() -> None:
|
|
||||||
middleware = create_approval_middleware(
|
|
||||||
config_by_name={
|
|
||||||
"calendar_write": ToolConfig(
|
|
||||||
name="calendar_write",
|
|
||||||
group=ToolGroup.EXECUTE,
|
|
||||||
approval=ToolApprovalConfig(required=True),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
approval_resolver=lambda _name, _args, _config: "approved",
|
|
||||||
)
|
|
||||||
|
|
||||||
responses = []
|
|
||||||
async for chunk in middleware(
|
|
||||||
{
|
|
||||||
"tool_call": {
|
|
||||||
"name": "calendar.write",
|
|
||||||
"input": {
|
|
||||||
"operation": "create",
|
|
||||||
"_hitl": {"approval": "required"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_next_handler,
|
|
||||||
):
|
|
||||||
responses.append(chunk)
|
|
||||||
|
|
||||||
assert responses[0]["ok"] is True
|
|
||||||
sanitized_input = responses[0]["tool_call"]["input"]
|
|
||||||
assert "_hitl" not in sanitized_input
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_hitl_middleware_rejected_short_circuits() -> None:
|
|
||||||
middleware = create_approval_middleware(
|
|
||||||
config_by_name={
|
|
||||||
"calendar_write": ToolConfig(
|
|
||||||
name="calendar_write",
|
|
||||||
group=ToolGroup.EXECUTE,
|
|
||||||
approval=ToolApprovalConfig(required=True),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
approval_resolver=lambda _name, _args, _config: "rejected",
|
|
||||||
)
|
|
||||||
|
|
||||||
responses = []
|
|
||||||
async for chunk in middleware(
|
|
||||||
{"tool_call": {"name": "calendar_write", "input": {"operation": "create"}}},
|
|
||||||
_next_handler,
|
|
||||||
):
|
|
||||||
responses.append(chunk)
|
|
||||||
|
|
||||||
payload = _extract_error_payload(responses[0])
|
|
||||||
assert payload["error"]["code"] == "TOOL_REJECTED"
|
|
||||||
@@ -9,7 +9,7 @@ from agentscope.tool import ToolResponse
|
|||||||
|
|
||||||
from core.agentscope.tools.custom import memory as memory_module
|
from core.agentscope.tools.custom import memory as memory_module
|
||||||
from models.memories import MemoryType
|
from models.memories import MemoryType
|
||||||
from schemas.memories.memory_content import UserMemoryContent
|
from schemas.domain.memory_content import UserMemoryContent
|
||||||
|
|
||||||
|
|
||||||
def _decode_tool_response(response: ToolResponse) -> dict[str, object]:
|
def _decode_tool_response(response: ToolResponse) -> dict[str, object]:
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from core.agentscope.prompts.system_prompt import (
|
|||||||
)
|
)
|
||||||
from schemas.agent.forwarded_props import ClientTimeContext
|
from schemas.agent.forwarded_props import ClientTimeContext
|
||||||
from schemas.agent.system_agent import AgentType
|
from schemas.agent.system_agent import AgentType
|
||||||
from schemas.user.context import UserContext, parse_profile_settings
|
from schemas.shared.user import UserContext, parse_profile_settings
|
||||||
|
|
||||||
|
|
||||||
def _build_user_context(*, timezone_name: str = "Asia/Shanghai") -> UserContext:
|
def _build_user_context(*, timezone_name: str = "Asia/Shanghai") -> UserContext:
|
||||||
@@ -159,7 +159,7 @@ def test_build_system_prompt_keeps_sections_focused_without_language_duplication
|
|||||||
|
|
||||||
|
|
||||||
def test_build_system_prompt_includes_user_memory_section_for_router() -> None:
|
def test_build_system_prompt_includes_user_memory_section_for_router() -> None:
|
||||||
from schemas.memories.memory_content import UserMemoryContent
|
from schemas.domain.memory_content import UserMemoryContent
|
||||||
|
|
||||||
user_memory = UserMemoryContent()
|
user_memory = UserMemoryContent()
|
||||||
|
|
||||||
@@ -175,7 +175,7 @@ def test_build_system_prompt_includes_user_memory_section_for_router() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_build_system_prompt_includes_work_memory_section_for_worker() -> None:
|
def test_build_system_prompt_includes_work_memory_section_for_worker() -> None:
|
||||||
from schemas.memories.memory_content import WorkProfileContent
|
from schemas.domain.memory_content import WorkProfileContent
|
||||||
|
|
||||||
work_memory = WorkProfileContent()
|
work_memory = WorkProfileContent()
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from uuid import UUID, uuid4
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from models.automation_jobs import AutomationJob as OrmAutomationJob, ScheduleType
|
from models.automation_jobs import AutomationJob as OrmAutomationJob, ScheduleType
|
||||||
from schemas.automation import (
|
from schemas.domain.automation import (
|
||||||
RuntimeConfig,
|
RuntimeConfig,
|
||||||
ScheduleConfig,
|
ScheduleConfig,
|
||||||
ScheduleRunAt,
|
ScheduleRunAt,
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from datetime import datetime, timezone
|
|
||||||
from uuid import UUID, uuid4
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from sqlalchemy import Column, String, Table
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
|
||||||
|
|
||||||
from core.db.base import Base, SoftDeleteMixin
|
|
||||||
from core.db.base_repository import BaseRepository
|
|
||||||
|
|
||||||
|
|
||||||
class Widget(SoftDeleteMixin, Base):
|
|
||||||
__tablename__ = "widgets"
|
|
||||||
|
|
||||||
id: Mapped[UUID] = mapped_column(primary_key=True)
|
|
||||||
name: Mapped[str] = mapped_column(String(50), nullable=False)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
async def db_engine():
|
|
||||||
auth_users = Table(
|
|
||||||
"users",
|
|
||||||
Base.metadata,
|
|
||||||
Column("id", String, primary_key=True),
|
|
||||||
schema="auth",
|
|
||||||
extend_existing=True,
|
|
||||||
)
|
|
||||||
engine = create_async_engine("sqlite+aiosqlite:///:memory:", echo=False)
|
|
||||||
async with engine.begin() as conn:
|
|
||||||
await conn.exec_driver_sql("ATTACH DATABASE ':memory:' AS auth")
|
|
||||||
await conn.run_sync(Base.metadata.create_all)
|
|
||||||
yield engine
|
|
||||||
Base.metadata.remove(auth_users)
|
|
||||||
await engine.dispose()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
async def db_session(db_engine):
|
|
||||||
async_session = async_sessionmaker(
|
|
||||||
bind=db_engine,
|
|
||||||
class_=AsyncSession,
|
|
||||||
expire_on_commit=False,
|
|
||||||
)
|
|
||||||
async with async_session() as session:
|
|
||||||
yield session
|
|
||||||
await session.rollback()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_get_by_id_filters_soft_deleted(db_session: AsyncSession) -> None:
|
|
||||||
repository = BaseRepository(db_session, Widget)
|
|
||||||
widget_id = uuid4()
|
|
||||||
|
|
||||||
widget = Widget(id=widget_id, name="widget")
|
|
||||||
db_session.add(widget)
|
|
||||||
await db_session.commit()
|
|
||||||
|
|
||||||
found = await repository.get_by_id(widget_id)
|
|
||||||
assert found is not None
|
|
||||||
|
|
||||||
deleted = await repository.soft_delete_by_id(widget_id)
|
|
||||||
assert deleted is not None
|
|
||||||
assert deleted.deleted_at is not None
|
|
||||||
|
|
||||||
missing = await repository.get_by_id(widget_id)
|
|
||||||
assert missing is None
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_soft_delete_sets_timestamp(db_session: AsyncSession) -> None:
|
|
||||||
repository = BaseRepository(db_session, Widget)
|
|
||||||
widget_id = uuid4()
|
|
||||||
|
|
||||||
widget = Widget(id=widget_id, name="widget")
|
|
||||||
db_session.add(widget)
|
|
||||||
await db_session.commit()
|
|
||||||
|
|
||||||
deleted = await repository.soft_delete_by_id(widget_id)
|
|
||||||
assert deleted is not None
|
|
||||||
assert isinstance(deleted.deleted_at, datetime)
|
|
||||||
deleted_at = deleted.deleted_at
|
|
||||||
if deleted_at.tzinfo is None:
|
|
||||||
deleted_at = deleted_at.replace(tzinfo=timezone.utc)
|
|
||||||
assert deleted_at <= datetime.now(timezone.utc)
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from uuid import uuid4
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from sqlalchemy import Column, String, Table, select
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
|
||||||
|
|
||||||
from core.db.base import Base
|
|
||||||
from models.profile import Profile
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
async def db_engine():
|
|
||||||
"""Create in-memory SQLite engine for testing."""
|
|
||||||
users_table = Table(
|
|
||||||
"users",
|
|
||||||
Base.metadata,
|
|
||||||
Column("id", String, primary_key=True),
|
|
||||||
schema="auth",
|
|
||||||
extend_existing=True,
|
|
||||||
)
|
|
||||||
engine = create_async_engine(
|
|
||||||
"sqlite+aiosqlite:///:memory:",
|
|
||||||
echo=False,
|
|
||||||
)
|
|
||||||
async with engine.begin() as conn:
|
|
||||||
await conn.exec_driver_sql("ATTACH DATABASE ':memory:' AS auth")
|
|
||||||
await conn.run_sync(Base.metadata.create_all)
|
|
||||||
yield engine
|
|
||||||
Base.metadata.remove(users_table)
|
|
||||||
await engine.dispose()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
async def db_session(db_engine):
|
|
||||||
"""Create a database session for testing."""
|
|
||||||
async_session = async_sessionmaker(
|
|
||||||
bind=db_engine,
|
|
||||||
class_=AsyncSession,
|
|
||||||
expire_on_commit=False,
|
|
||||||
)
|
|
||||||
async with async_session() as session:
|
|
||||||
yield session
|
|
||||||
await session.rollback()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_profile_model_create(db_session: AsyncSession) -> None:
|
|
||||||
"""Test creating a Profile model."""
|
|
||||||
profile_id = uuid4()
|
|
||||||
profile = Profile(
|
|
||||||
id=profile_id,
|
|
||||||
username="testuser",
|
|
||||||
)
|
|
||||||
db_session.add(profile)
|
|
||||||
await db_session.commit()
|
|
||||||
await db_session.refresh(profile)
|
|
||||||
|
|
||||||
assert profile.id == profile_id
|
|
||||||
assert profile.username == "testuser"
|
|
||||||
assert profile.created_at is not None
|
|
||||||
assert profile.updated_at is not None
|
|
||||||
assert profile.deleted_at is None
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_profile_model_get_by_id(db_session: AsyncSession) -> None:
|
|
||||||
"""Test retrieving a Profile by ID."""
|
|
||||||
profile_id = uuid4()
|
|
||||||
profile = Profile(
|
|
||||||
id=profile_id,
|
|
||||||
username="testuser",
|
|
||||||
)
|
|
||||||
db_session.add(profile)
|
|
||||||
await db_session.commit()
|
|
||||||
|
|
||||||
result = await db_session.get(Profile, profile_id)
|
|
||||||
assert result is not None
|
|
||||||
assert result.username == "testuser"
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_profile_model_get_by_username(db_session: AsyncSession) -> None:
|
|
||||||
"""Test retrieving a Profile by username."""
|
|
||||||
profile = Profile(
|
|
||||||
id=uuid4(),
|
|
||||||
username="testuser",
|
|
||||||
)
|
|
||||||
db_session.add(profile)
|
|
||||||
await db_session.commit()
|
|
||||||
|
|
||||||
result = await db_session.execute(
|
|
||||||
select(Profile).where(Profile.username == "testuser")
|
|
||||||
)
|
|
||||||
found = result.scalar_one()
|
|
||||||
assert found is not None
|
|
||||||
assert found.username == "testuser"
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_profile_model_update(db_session: AsyncSession) -> None:
|
|
||||||
"""Test updating a Profile."""
|
|
||||||
profile = Profile(
|
|
||||||
id=uuid4(),
|
|
||||||
username="testuser",
|
|
||||||
bio="Old bio",
|
|
||||||
)
|
|
||||||
db_session.add(profile)
|
|
||||||
await db_session.commit()
|
|
||||||
|
|
||||||
profile.bio = "New bio"
|
|
||||||
await db_session.commit()
|
|
||||||
await db_session.refresh(profile)
|
|
||||||
|
|
||||||
assert profile.bio == "New bio"
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_profile_model_allows_duplicate_usernames(
|
|
||||||
db_session: AsyncSession,
|
|
||||||
) -> None:
|
|
||||||
first = Profile(id=uuid4(), username="same_name")
|
|
||||||
second = Profile(id=uuid4(), username="same_name")
|
|
||||||
|
|
||||||
db_session.add(first)
|
|
||||||
db_session.add(second)
|
|
||||||
await db_session.commit()
|
|
||||||
|
|
||||||
result = await db_session.execute(
|
|
||||||
select(Profile).where(Profile.username == "same_name")
|
|
||||||
)
|
|
||||||
found = result.scalars().all()
|
|
||||||
assert len(found) == 2
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
ROOT_DIR = Path(__file__).resolve().parents[4]
|
|
||||||
APP_SCRIPT = ROOT_DIR / "infra" / "scripts" / "app.sh"
|
|
||||||
|
|
||||||
|
|
||||||
def test_worker_commands_use_taskiq() -> None:
|
|
||||||
content = APP_SCRIPT.read_text(encoding="utf-8")
|
|
||||||
removed_runner = "uv run celery"
|
|
||||||
|
|
||||||
assert "uv run taskiq worker" in content
|
|
||||||
assert "core.taskiq.app:critical_broker" in content
|
|
||||||
assert "core.taskiq.app:default_broker" in content
|
|
||||||
assert "core.taskiq.app:bulk_broker" in content
|
|
||||||
assert 'pgrep -f "uv run taskiq worker core.taskiq.app:"' in content
|
|
||||||
assert 'kill_pids_gracefully "taskiq workers"' in content
|
|
||||||
assert "gunicorn" not in content
|
|
||||||
assert removed_runner not in content
|
|
||||||
|
|
||||||
|
|
||||||
def test_web_command_uses_uvicorn_only() -> None:
|
|
||||||
content = APP_SCRIPT.read_text(encoding="utf-8")
|
|
||||||
|
|
||||||
assert "uv run uvicorn app:app" in content
|
|
||||||
assert 'WEB_PORT="${SOCIAL_WEB__PORT:-5775}"' in content
|
|
||||||
assert "SOCIAL_WEB__WORKERS" in content
|
|
||||||
assert 'UVICORN_LOG_LEVEL="${UVICORN_LOG_LEVEL,,}"' in content
|
|
||||||
assert "SOCIAL_WEB__GUNICORN__" not in content
|
|
||||||
assert "uv run gunicorn" not in content
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user