# -*- coding: utf-8 -*- """智能体引导(bootstrap)路由。 返回"所有用户首次启动 / 每次登录都必须初始化的官方预置智能体"定义 —— 由运营端管理后台配置(official_agents 表),不再硬编码。 这是全局定义(对所有用户相同),因此**公开**(无需登录),本地据此在首次启动 和每次登录时初始化本地 workspace,避免本地硬编码默认智能体。 支持 ``?scope=`` 过滤身份级别:opc(桌面端)/park(园区端)/operator(运营端)/all。 默认 opc(桌面端是主要消费方;旧客户端无参请求仍返回桌面端集合)。 """ from __future__ import annotations import json as _json from fastapi import APIRouter, Depends from starlette.requests import Request from ..dependencies import get_db from ...infrastructure.repositories import Database router = APIRouter(tags=["bootstrap"]) _DOC_KEYS = ("soul", "profile", "heartbeat", "memory") def _parse_docs(raw: str) -> dict: """解析文档 JSON 字段;失败时返回空对象(不影响下发)。""" try: data = _json.loads(raw or "{}") except (ValueError, TypeError): return {} if not isinstance(data, dict): return {} return { "user": str(data.get("user", "") or ""), "server": str(data.get("server", "") or ""), } def _match_scope(agent_scope: str, requested: str) -> bool: """agent.scope 是否覆盖请求 scope:all 覆盖一切;逗号分隔列表含请求值。""" agent_scope = (agent_scope or "opc").strip() if not agent_scope or agent_scope == "all": return True return requested in [s.strip() for s in agent_scope.split(",")] @router.get("/agent-bootstrap", summary="必初始化官方智能体定义") async def agent_bootstrap( request: Request, db: Database = Depends(get_db), ): """返回服务端配置的官方预置智能体(启用中),供桌面端登录/启动同步。""" scope = (request.query_params.get("scope") or "opc").strip() or "opc" if scope == "all": scope = "opc" # all 请求场景按全量:下放时以列表值过滤 seeds = await db.official_agents.active() out = [] for seed in seeds: if not _match_scope(seed.get("scope", "opc"), scope): continue docs = {k: _parse_docs(seed.get(k) or "{}") for k in _DOC_KEYS} out.append( { "id": seed["id"], "name": seed["name"], "description": seed.get("description", ""), "language": seed.get("language", "zh"), "model_name": seed.get("model_name", ""), "template_type": seed.get("template_type", "common"), "deletable": seed.get("deletable", True), "use_fixed_soul": seed.get("use_fixed_soul", False), "scope": seed.get("scope", "opc"), "documents": docs, } ) return out