feat: 增强日历功能并集成 AgentScope 代理服务

This commit is contained in:
qzl
2026-03-11 17:16:11 +08:00
parent e20e7d2a02
commit 85b314cf64
53 changed files with 3642 additions and 297 deletions
+13 -17
View File
@@ -2,21 +2,20 @@ from __future__ import annotations
import asyncio
from typing import Any
from uuid import UUID
from fastapi import Depends
from redis.asyncio import Redis
from sqlalchemy.ext.asyncio import AsyncSession
from core.agent.infrastructure.events.redis_stream import RedisStreamEventStore
from core.agent.infrastructure.storage.tool_result_storage import (
create_tool_result_storage,
)
from core.agent.infrastructure.queue.tasks import (
from core.agentscope.events import RedisStreamBus
from core.agentscope.runtime.tasks import (
run_command_task,
run_command_task_bulk,
run_command_task_critical,
)
from core.agent.infrastructure.storage.tool_result_storage import (
create_tool_result_storage,
)
from core.config.settings import config
from core.db import get_db
from services.base.redis import get_or_init_redis_client
@@ -84,18 +83,18 @@ class TaskiqQueueClient:
class RedisEventStream:
def __init__(self) -> None:
self._store: RedisStreamEventStore | None = None
self._bus: RedisStreamBus | None = None
async def _get_store(self) -> RedisStreamEventStore:
if self._store is None:
async def _get_bus(self) -> RedisStreamBus:
if self._bus is None:
client = await get_or_init_redis_client()
self._store = RedisStreamEventStore(
self._bus = RedisStreamBus(
client=client,
stream_prefix=config.agent_runtime.redis_stream_prefix,
read_count=config.agent_runtime.redis_stream_read_count,
block_ms=config.agent_runtime.redis_stream_block_ms,
)
return self._store
return self._bus
async def read(
self,
@@ -103,12 +102,9 @@ class RedisEventStream:
session_id: str,
last_event_id: str | None,
) -> list[dict[str, Any]]:
store = await self._get_store()
rows = await store.read_events(
session_id=UUID(session_id),
last_event_id=last_event_id,
)
return [{**row, "cursor": last_event_id} for row in rows]
bus = await self._get_bus()
rows = await bus.read(session_id=session_id, last_event_id=last_event_id)
return [{**row, "cursor": row.get("id")} for row in rows]
def get_agent_service(session: AsyncSession = Depends(get_db)) -> AgentService:
+1 -1
View File
@@ -14,7 +14,7 @@ from fastapi import APIRouter, Depends, Header, Query, Request, status, UploadFi
from fastapi import HTTPException
from fastapi.responses import JSONResponse, StreamingResponse
from core.agent.infrastructure.agui.stream import to_sse_event
from core.agentscope.events import to_sse_event
from core.agent.domain.agui_input import (
parse_run_input,
validate_run_request_messages_contract,
+19
View File
@@ -18,6 +18,17 @@ from core.logging import get_logger
logger = get_logger(__name__)
def _extract_user_token_from_run_input(run_input: RunAgentInput) -> str | None:
forwarded = run_input.forwarded_props
if not isinstance(forwarded, dict):
return None
for key in ("accessToken", "userToken", "token"):
value = forwarded.get(key)
if isinstance(value, str) and value.strip():
return value.strip()
return None
@dataclass(frozen=True)
class TaskAccepted:
task_id: str
@@ -65,6 +76,10 @@ def ensure_session_owner(*, owner_id: str, current_user: CurrentUser) -> None:
class AgentService:
_repository: AgentRepositoryLike
_queue: QueueClientLike
_stream: EventStreamLike
def __init__(
self,
*,
@@ -107,6 +122,8 @@ class AgentService:
task_id = await self._queue.enqueue(
command={
"command": "run",
"owner_id": str(current_user.id),
"user_token": _extract_user_token_from_run_input(run_input),
"run_input": run_input.model_dump(mode="json", by_alias=True),
},
dedup_key=None,
@@ -132,6 +149,8 @@ class AgentService:
task_id = await self._queue.enqueue(
command={
"command": "resume",
"owner_id": str(current_user.id),
"user_token": _extract_user_token_from_run_input(run_input),
"run_input": run_input.model_dump(mode="json", by_alias=True),
},
dedup_key=dedup_key,
+1
View File
@@ -32,6 +32,7 @@ class ScheduleItemMetadata(BaseModel):
location: str | None = None
notes: str | None = None
attachments: list[ScheduleItemMetadataAttachment] = Field(default_factory=list)
reminder_minutes: int | None = Field(default=None, ge=0, le=10080)
version: Literal[1] = 1
+2 -3
View File
@@ -135,14 +135,13 @@ class ScheduleItemService(BaseService):
update_data = request.model_dump(exclude_unset=True)
# Handle metadata separately (model_dump returns dict)
if "metadata" in update_data and update_data["metadata"] is not None:
metadata_value = update_data["metadata"]
if "metadata" in update_data:
metadata_value = update_data.pop("metadata")
update_data["extra_metadata"] = (
metadata_value.model_dump()
if hasattr(metadata_value, "model_dump")
else metadata_value
)
del update_data["metadata"]
# Validate time range
next_start = update_data.get("start_at", existing.start_at)