# Copyright 2026 zhaoxi826 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from pydantic import BaseModel import viceroy from kilostar.utils.ray_hook import ray_actor_hook from fastapi import APIRouter, Depends from kilostar.utils.access import TokenData from kilostar.utils.check_user.role_check import RoleChecker from kilostar.core.postgres_database.model import UserAuthority resource_router = APIRouter(prefix="/api/v1/resource") class Skill(BaseModel): """``POST /skill`` 入参:技能仓库地址及可选子目录路径。""" repo_url: str path: str | None @resource_router.post("/skill") async def install_skill( skill: Skill, _: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER)) ): """通过 viceroy 把 skill 仓库克隆到 ``plugin/skill``,并在状态机中登记。""" global_state_machine = ray_actor_hook("global_state_machine").global_state_machine # noinspection PyUnresolvedReferences import os skill_output_dir = os.path.abspath( os.path.join(os.path.dirname(__file__), "..", "plugin", "skill") ) os.makedirs(skill_output_dir, exist_ok=True) await viceroy.install_skill_async( url=skill.repo_url, path=skill.path, output=skill_output_dir ) if skill.path: skill_name = skill.path.split("/")[-1] else: skill_name = skill.repo_url.split("/")[-1] await global_state_machine.add_skill.remote(skill_name) return {"message": "创建成功"} @resource_router.get("/skill") async def get_skills( _: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER)), ): """返回当前状态机中已登记的所有 skill 名称列表。""" global_state_machine = ray_actor_hook("global_state_machine").global_state_machine skills = await global_state_machine.get_skill_list.remote() return {"skills": skills} @resource_router.delete("/skill/{skill_name}") async def delete_skill( skill_name: str, _: TokenData = Depends( RoleChecker(allowed_roles=UserAuthority.SUPER_ADMINISTRATOR) ), ): """从状态机中移除 skill 注册项;不会删除磁盘上的代码文件。""" global_state_machine = ray_actor_hook("global_state_machine").global_state_machine # Note: this only removes it from the state machine manager. await global_state_machine.remove_skill.remote(skill_name) return {"message": "success"} @resource_router.get("/tool") async def get_tools( _: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER)), ): """汇总各作用域 tool_mapper,返回去重后的工具名称列表。""" global_state_machine = ray_actor_hook("global_state_machine").global_state_machine tool_mapper = await global_state_machine.get_tool_mapper.remote() all_tool_names = set() for scope_tools in tool_mapper.values(): all_tool_names.update(scope_tools.keys()) return {"tools": list(all_tool_names)}