Files
MoviePilot/app/agent/learning/schema.py
jxxghp 6d4ce83837 fix(types): clear host mypy debt and prevent typing regressions
严格检查全部宿主源码,清零类型错误预算并移除按模块类型豁免。
按原因、所属方法、错误码及语法指纹锁定必要兼容例外和站点类型桩,
CI 阻断新增忽略、检查范围退化、本地桩替代真实接口及新的类型错误。

修复空值、异步和供应方类型边界,保持插件与 API 兼容合同,
并补充存储、订阅、通知、Agent 和工作流边界的回归覆盖。
2026-10-21 22:03:18 +08:00

122 lines
6.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
skill_manage 按动作区分参数形态,避免正文写进错误字段。
"""
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field
class SkillOperation(BaseModel):
"""
技能身份由名称选择,个人根与后台身份始终由宿主绑定。
"""
model_config = ConfigDict(extra='forbid')
name: str = Field(pattern=r'^[a-z0-9][a-z0-9_-]{0,63}$', description="技能名称,须以小写字母或数字开头,后续仅允许小写字母、数字、下划线或连字符,最多 64 字符")
class CreateSkill(SkillOperation):
"""
新建完整大类技能,正文必须含可加载的 YAML 前言。
"""
action: Literal['create'] = Field(description="固定为 create,表示新建技能")
content: str = Field(min_length=1, max_length=100_000, description="完整 SKILL.md 正文,须包含可加载的 YAML 前言")
category: str | None = Field(default=None, pattern=r'^[a-z0-9][a-z0-9_-]{0,63}$', description="可选的大类目录名称,遵循技能名称约束;None 表示未指定分类")
class PatchSkill(SkillOperation):
"""
局部修改必须提供旧片段;允许明确替换所有命中。
"""
action: Literal['patch'] = Field(description="固定为 patch,表示局部修改技能文件")
old_string: str = Field(min_length=1, max_length=100_000, description="需要定位并替换的非空旧片段")
new_string: str = Field(max_length=100_000, description="替换后的新片段;空字符串表示移除匹配片段")
file_path: str | None = Field(default=None, max_length=255, description="技能目录内的目标文件相对路径;None 表示主技能文档")
replace_all: bool = Field(default=False, description="是否替换全部命中,默认 False;多处匹配时须明确选择")
class RewriteSkill(SkillOperation):
"""
完整重写形态;不能与局部修改字段混用。
"""
action: Literal['patch'] = Field(description="固定为 patch,结合 content 表示完整重写")
content: str = Field(min_length=1, max_length=100_000, description="替换原技能文档的完整非空正文")
class WriteSkillFile(SkillOperation):
"""
写入技能支持文件;不能借此更新所有权或绕过主文档校验。
"""
action: Literal['write_file'] = Field(description="固定为 write_file,表示写入技能支持文件")
file_path: str = Field(min_length=1, max_length=255, description="技能目录内的目标文件相对路径,不得越过技能边界")
file_content: str = Field(max_length=100_000, description="支持文件的完整文本内容;允许空字符串")
class RemoveSkillFile(SkillOperation):
"""
删除精确支持文件,不能递归删除目录。
"""
action: Literal['remove_file'] = Field(description="固定为 remove_file,表示删除单个支持文件")
file_path: str = Field(min_length=1, max_length=255, description="需要删除的精确支持文件相对路径,不能指向目录")
class DeleteSkill(SkillOperation):
"""
整项删除必须独占批次;合并目标可作为追溯依据。
"""
action: Literal['delete'] = Field(description="固定为 delete,删除整个个人技能;此操作必须独占批次")
absorbed_into: str | None = Field(default=None, pattern=r'^(?:[a-z0-9][a-z0-9_-]{0,63})?$', description="合并后承接内容的技能名称,作为追溯依据;未合并时可为 None 或空字符串")
class ManageSkillsInput(BaseModel):
"""
最多二十个有序操作,任一失败回滚本次全部技能变更。
"""
model_config = ConfigDict(extra='forbid')
operations: list[CreateSkill | PatchSkill | RewriteSkill | WriteSkillFile | RemoveSkillFile | DeleteSkill] = Field(
min_length=1, max_length=20, description="按顺序执行的 1 至 20 个技能操作;任一操作失败时回滚本次全部变更")
class ViewSkillInput(BaseModel):
"""
读取具体技能或支持文件,并登记本轮实际读取的版本。
"""
model_config = ConfigDict(extra='forbid')
name: str = Field(pattern=r'^[a-z0-9][a-z0-9_-]{0,63}$', description="当前宿主技能范围内的技能名称")
file_path: str = Field(default='SKILL.md', max_length=255, description="待读取文件在技能目录内的相对路径,默认 SKILL.md")
class MemoryOperation(BaseModel):
"""
old_text 只定位整条记忆,replace 的 content 会替换整条而非子串。
"""
model_config = ConfigDict(extra='forbid')
action: Literal['add', 'replace', 'remove'] = Field(description="记忆动作:add 新增、replace 整条替换、remove 删除")
content: str | None = Field(default=None, max_length=100_000, description="新增或整条替换后的记忆内容;不需要内容的操作可省略")
old_text: str | None = Field(default=None, max_length=100_000, description="用于定位整条已有记忆的原文本;不是局部子串替换规则")
new_text: str | None = Field(default=None, max_length=100_000, description="替换操作兼容的新内容字段;未提供时为 None")
class MemoryInput(BaseModel):
"""
支持单操作及原子批次形态,pending 只读列出等待人工处理的提案。
"""
model_config = ConfigDict(extra='forbid')
action: Literal['add', 'replace', 'remove', 'pending'] | None = Field(default=None, description="单操作动作:add、replace、remove 或只读的 pending;使用 operations 批次时可省略")
target: Literal['memory', 'user'] = Field(default='memory', description="目标记忆:memory 表示 MEMORY.md,user 表示当前用户的 USER.md,默认 memory")
content: str | None = Field(default=None, max_length=100_000, description="单操作新增或整条替换后的记忆内容;可省略")
old_text: str | None = Field(default=None, max_length=100_000, description="用于定位要替换或移除的整条记忆;可省略")
new_text: str | None = Field(default=None, max_length=100_000, description="单操作兼容的新内容字段;未提供时为 None")
operations: list[MemoryOperation] | None = Field(default=None, min_length=1, max_length=20, description="可选的 1 至 20 个原子记忆操作;None 表示使用单操作字段")