"""Kernel Run API(rewrite/1.0 新契约首个端点)。 POST /api/v1/runs:创建占位前台 Run(M0 骨架),事件流经既有 GET /api/v1/runs/{run_id}/stream 订阅(SSE + Last-Event-ID 断线续传)。 与旧 runs 路由共存:本模块只新增 POST,不改动旧读端点(M2 重建时收敛)。 """ from __future__ import annotations from fastapi import APIRouter, Depends, Request from pydantic import BaseModel, Field from sqlalchemy.ext.asyncio import AsyncSession from app.api.v1.deps import get_current_user from app.db.session import get_session from app.kernel import Kernel from app.schemas.auth import CurrentUser from app.schemas.runtime import RunResponse from app.services.characters import resolve_active_character from app.services.runtime import get_run_response router = APIRouter(prefix="/runs", tags=["kernel"]) class KernelRunCreate(BaseModel): """占位 Run 创建请求:M0 无镜树挂载,prompt 直接作为用户输入。""" prompt: str = Field(default="你好,请介绍一下你自己。", min_length=1, max_length=8000) character_id: str | None = Field(default=None, description="缺省用当前激活人物") def _get_kernel(request: Request) -> Kernel: kernel: Kernel | None = getattr(request.app.state, "kernel", None) if kernel is None: from app.core.errors import AppError raise AppError("service_unavailable", "Kernel 运行时未就绪。", status_code=503) return kernel @router.post("", response_model=RunResponse, status_code=200) async def create_kernel_run( payload: KernelRunCreate, request: Request, user: CurrentUser = Depends(get_current_user), db: AsyncSession = Depends(get_session), ) -> RunResponse: kernel = _get_kernel(request) character = await resolve_active_character( db, payload.character_id, owner_scope=user.owner_scope ) run = await kernel.start_foreground_run( character_id=character.id, prompt=payload.prompt, owner_id=user.owner_scope ) return await get_run_response(run.id)