refactor: complete host module and event contracts

This commit is contained in:
jxxghp
2026-08-22 13:21:52 +08:00
parent e61b5168ad
commit 677f6a66c2
9 changed files with 3497 additions and 72 deletions
+30
View File
@@ -9,7 +9,9 @@ from typing import Any
from pydantic import BaseModel, ValidationError from pydantic import BaseModel, ValidationError
import app.schemas.event as event_schemas import app.schemas.event as event_schemas
from app.schemas.mediaserver import WebhookEventInfo
from app.schemas.types import ChainEventType, EventType from app.schemas.types import ChainEventType, EventType
from app.schemas.workflow import ActionContext
class EventDelivery(StrEnum): class EventDelivery(StrEnum):
@@ -53,6 +55,13 @@ class EventContract:
_PAYLOAD_MODELS: dict[EventType | ChainEventType, type[BaseModel]] = { _PAYLOAD_MODELS: dict[EventType | ChainEventType, type[BaseModel]] = {
EventType.ConfigChanged: event_schemas.ConfigChangeEventData, EventType.ConfigChanged: event_schemas.ConfigChangeEventData,
EventType.PluginReload: event_schemas.PluginReloadEventData,
EventType.PluginAction: event_schemas.PluginActionEventData,
EventType.PluginTriggered: event_schemas.PluginTriggeredEventData,
EventType.CommandExcute: event_schemas.CommandExecuteEventData,
EventType.SiteDeleted: event_schemas.SiteEventData,
EventType.SiteUpdated: event_schemas.SiteEventData,
EventType.SiteRefreshed: event_schemas.SiteEventData,
EventType.AgentTokensUsage: event_schemas.AgentTokensUsageEventData, EventType.AgentTokensUsage: event_schemas.AgentTokensUsageEventData,
EventType.SubscribeAdded: event_schemas.SubscribeAddedEventData, EventType.SubscribeAdded: event_schemas.SubscribeAddedEventData,
EventType.SubscribeDeleted: event_schemas.SubscribeDeletedEventData, EventType.SubscribeDeleted: event_schemas.SubscribeDeletedEventData,
@@ -60,6 +69,22 @@ _PAYLOAD_MODELS: dict[EventType | ChainEventType, type[BaseModel]] = {
EventType.DownloadAdded: event_schemas.DownloadAddedEventData, EventType.DownloadAdded: event_schemas.DownloadAddedEventData,
EventType.TransferComplete: event_schemas.TransferResultEventData, EventType.TransferComplete: event_schemas.TransferResultEventData,
EventType.TransferFailed: event_schemas.TransferResultEventData, EventType.TransferFailed: event_schemas.TransferResultEventData,
EventType.SubtitleTransferComplete: event_schemas.TransferResultEventData,
EventType.SubtitleTransferFailed: event_schemas.TransferResultEventData,
EventType.AudioTransferComplete: event_schemas.TransferResultEventData,
EventType.AudioTransferFailed: event_schemas.TransferResultEventData,
EventType.HistoryDeleted: event_schemas.HistoryDeletedEventData,
EventType.DownloadFileDeleted: event_schemas.DownloadFileDeletedEventData,
EventType.DownloadDeleted: event_schemas.DownloadDeletedEventData,
EventType.UserMessage: event_schemas.UserMessageEventData,
EventType.WebhookMessage: WebhookEventInfo,
EventType.NoticeMessage: event_schemas.NoticeMessageEventData,
EventType.SubscribeComplete: event_schemas.SubscribeCompleteEventData,
EventType.SystemError: event_schemas.SystemErrorEventData,
EventType.MetadataScrape: event_schemas.MetadataScrapeEventData,
EventType.ModuleReload: event_schemas.EmptyEventData,
EventType.MessageAction: event_schemas.MessageActionEventData,
EventType.WorkflowExecute: event_schemas.WorkflowExecuteEventData,
ChainEventType.PluginDataReset: event_schemas.PluginDataResetEventData, ChainEventType.PluginDataReset: event_schemas.PluginDataResetEventData,
ChainEventType.AuthVerification: event_schemas.AuthCredentials, ChainEventType.AuthVerification: event_schemas.AuthCredentials,
ChainEventType.AuthIntercept: event_schemas.AuthInterceptCredentials, ChainEventType.AuthIntercept: event_schemas.AuthInterceptCredentials,
@@ -77,6 +102,11 @@ _PAYLOAD_MODELS: dict[EventType | ChainEventType, type[BaseModel]] = {
ChainEventType.AgentLLMProvider: event_schemas.AgentLLMProviderEventData, ChainEventType.AgentLLMProvider: event_schemas.AgentLLMProviderEventData,
ChainEventType.SubscribeEpisodesRefresh: event_schemas.SubscribeEpisodesRefreshEventData, ChainEventType.SubscribeEpisodesRefresh: event_schemas.SubscribeEpisodesRefreshEventData,
ChainEventType.SubscribeCompletionCheck: event_schemas.SubscribeCompletionCheckEventData, ChainEventType.SubscribeCompletionCheck: event_schemas.SubscribeCompletionCheckEventData,
ChainEventType.NameRecognize: event_schemas.NameRecognizeEventData,
ChainEventType.MusicNameRecognize: event_schemas.MusicNameRecognizeEventData,
ChainEventType.MediaRecognize: event_schemas.MediaRecognizeEventData,
ChainEventType.MusicMediaRecognize: event_schemas.MusicMediaRecognizeEventData,
ChainEventType.WorkflowExecution: ActionContext,
} }
_DURABLE_REQUIRED = { _DURABLE_REQUIRED = {
+313 -1
View File
@@ -83,7 +83,7 @@ _METHOD_CONTRACTS = {
"get_folder": ModuleMethodContract(family="storage", input_contract="StorageFolderRequest", result_contract="FileItem | None", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("storage", "path")), "get_folder": ModuleMethodContract(family="storage", input_contract="StorageFolderRequest", result_contract="FileItem | None", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("storage", "path")),
"get_parent_item": ModuleMethodContract(family="storage", input_contract="StorageParentRequest", result_contract="FileItem | None", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("fileitem",)), "get_parent_item": ModuleMethodContract(family="storage", input_contract="StorageParentRequest", result_contract="FileItem | None", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("fileitem",)),
"rename_file": ModuleMethodContract(family="storage", input_contract="StorageRenameRequest", result_contract="bool | FileItem", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("fileitem", "name")), "rename_file": ModuleMethodContract(family="storage", input_contract="StorageRenameRequest", result_contract="bool | FileItem", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("fileitem", "name")),
"storage_manage": ModuleMethodContract(family="storage", input_contract="StorageManageRequest", result_contract="Any", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("storage", "action")), "storage_manage": ModuleMethodContract(family="storage", input_contract="StorageManageRequest", result_contract="StorageProviderResult", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("storage", "action")),
"snapshot_storage": ModuleMethodContract(family="storage", input_contract="StorageSnapshotRequest", result_contract="dict[str, dict] | None", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("storage", "path", "last_snapshot_time", "max_depth", "previous_snapshot")), "snapshot_storage": ModuleMethodContract(family="storage", input_contract="StorageSnapshotRequest", result_contract="dict[str, dict] | None", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("storage", "path", "last_snapshot_time", "max_depth", "previous_snapshot")),
"send_message": ModuleMethodContract(family="messaging", input_contract="MessageSendRequest", result_contract="Message | None", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY), "send_message": ModuleMethodContract(family="messaging", input_contract="MessageSendRequest", result_contract="Message | None", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY),
"finalize_message": ModuleMethodContract(family="messaging", input_contract="MessageFinalizeRequest", result_contract="Message | None", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("response",)), "finalize_message": ModuleMethodContract(family="messaging", input_contract="MessageFinalizeRequest", result_contract="Message | None", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("response",)),
@@ -107,6 +107,318 @@ _PREFIX_CONTRACTS = (
) )
# 宿主静态扫描到的全部字符串能力名。第三方插件仍可声明未在这里出现的自定义方法,
# 自定义方法继续走开放的 legacy contract;宿主新增调用则必须先进入本清单。
_OBSERVED_HOST_METHODS = (
'anilist_credits',
'anilist_discover',
'anilist_info',
'anilist_person_credits',
'anilist_person_detail',
'anilist_popular_this_season',
'anilist_recommendations',
'anilist_trending',
'any_files',
'async_anilist_credits',
'async_anilist_discover',
'async_anilist_info',
'async_anilist_person_credits',
'async_anilist_person_detail',
'async_anilist_popular_this_season',
'async_anilist_recommendations',
'async_anilist_trending',
'async_bangumi_calendar',
'async_bangumi_credits',
'async_bangumi_discover',
'async_bangumi_info',
'async_bangumi_person_credits',
'async_bangumi_person_detail',
'async_bangumi_recommend',
'async_douban_discover',
'async_douban_info',
'async_douban_movie_credits',
'async_douban_movie_recommend',
'async_douban_person_credits',
'async_douban_person_detail',
'async_douban_tv_credits',
'async_douban_tv_recommend',
'async_identify_music_by_fingerprint',
'async_match_doubaninfo',
'async_match_music_album',
'async_match_tmdbinfo',
'async_movie_hot',
'async_movie_showing',
'async_movie_top250',
'async_obtain_images',
'async_recognize_media',
'async_refresh_torrents',
'async_search_collections',
'async_search_medias',
'async_search_persons',
'async_search_subtitles',
'async_search_torrents',
'async_tmdb_collection',
'async_tmdb_discover',
'async_tmdb_episodes',
'async_tmdb_group_seasons',
'async_tmdb_info',
'async_tmdb_movie_credits',
'async_tmdb_movie_recommend',
'async_tmdb_movie_similar',
'async_tmdb_person_credits',
'async_tmdb_person_detail',
'async_tmdb_seasons',
'async_tmdb_trending',
'async_tmdb_tv_credits',
'async_tmdb_tv_recommend',
'async_tmdb_tv_similar',
'async_tv_animation',
'async_tv_hot',
'async_tv_weekly_chinese',
'async_tv_weekly_global',
'async_update_recognize_cache',
'bangumi_calendar',
'bangumi_credits',
'bangumi_discover',
'bangumi_info',
'bangumi_person_credits',
'bangumi_person_detail',
'bangumi_recommend',
'channel_manage',
'clear_cache',
'create_folder',
'delete_file',
'delete_message',
'douban_discover',
'douban_info',
'douban_movie_credits',
'douban_movie_recommend',
'douban_person_credits',
'douban_person_detail',
'douban_tv_credits',
'douban_tv_recommend',
'download',
'download_added',
'download_discord_file_bytes',
'download_feishu_file_bytes',
'download_feishu_image_to_data_url',
'download_file',
'download_qq_file_bytes',
'download_slack_file_bytes',
'download_slack_file_to_data_url',
'download_synologychat_file_bytes',
'download_telegram_file_bytes',
'download_telegram_file_to_base64',
'download_vocechat_file_bytes',
'download_vocechat_image_to_data_url',
'download_wechat_image_to_data_url',
'download_wechat_media_bytes',
'downloader_info',
'edit_message',
'filter_torrents',
'finalize_message',
'get_file_item',
'get_folder',
'get_parent_item',
'get_search_page_size',
'get_torrent_trackers',
'identify_music_by_fingerprint',
'list_files',
'list_torrents',
'load_category_config',
'mark_message_processing_finished',
'mark_message_processing_started',
'match_doubaninfo',
'match_music_album',
'match_tmdbinfo',
'media_category',
'media_exists',
'media_files',
'media_statistic',
'mediaserver_image_cookies',
'mediaserver_iteminfo',
'mediaserver_items',
'mediaserver_items_count',
'mediaserver_latest',
'mediaserver_latest_images',
'mediaserver_librarys',
'mediaserver_play_url',
'mediaserver_playing',
'mediaserver_season_episode_ids',
'mediaserver_tv_episodes',
'message_parser',
'metadata_img',
'metadata_nfo',
'movie_hot',
'movie_showing',
'movie_top250',
'music_album',
'music_album_related',
'music_artist',
'music_artist_albums',
'music_artist_related',
'music_cache_clear',
'music_cache_delete',
'music_cache_items',
'music_chart',
'music_discover',
'music_fresh_releases',
'music_lyrics',
'obtain_images',
'obtain_specific_image',
'recognize_media',
'recommend_name',
'refresh_torrents',
'refresh_userdata',
'register_commands',
'remove_torrents',
'rename_file',
'save_category_config',
'scheduler_job',
'search_collections',
'search_medias',
'search_music',
'search_persons',
'search_subtitles',
'search_torrents',
'search_tvdb',
'send_direct_message',
'set_torrents_tag',
'site_subtitle_links',
'snapshot_storage',
'start_torrents',
'stop_torrents',
'storage_manage',
'tmdb_cache_clear',
'tmdb_cache_delete',
'tmdb_cache_items',
'tmdb_collection',
'tmdb_discover',
'tmdb_episodes',
'tmdb_group_seasons',
'tmdb_info',
'tmdb_movie_credits',
'tmdb_movie_recommend',
'tmdb_movie_similar',
'tmdb_person_credits',
'tmdb_person_detail',
'tmdb_seasons',
'tmdb_trending',
'tmdb_tv_credits',
'tmdb_tv_recommend',
'tmdb_tv_similar',
'torrent_files',
'transfer',
'transfer_completed',
'tv_animation',
'tv_hot',
'tv_weekly_chinese',
'tv_weekly_global',
'tvdb_info',
'tvdb_slug',
'update_recognize_cache',
'update_torrent',
'upload_file',
'user_authenticate',
'webhook_parser',
)
_FAMILY_IO_CONTRACTS = {
"anilist": ("AniListKeywordArguments", "AniListProviderResult"),
"authentication": ("AuthenticationKeywordArguments", "AuthenticationResult"),
"bangumi": ("BangumiKeywordArguments", "BangumiProviderResult"),
"category": ("CategoryKeywordArguments", "CategoryProviderResult"),
"douban": ("DoubanKeywordArguments", "DoubanProviderResult"),
"downloader": ("DownloaderKeywordArguments", "DownloaderProviderResult"),
"integration": ("IntegrationKeywordArguments", "IntegrationProviderResult"),
"media-discovery": ("MediaDiscoveryKeywordArguments", "MediaDiscoveryProviderResult"),
"media-recognition": ("MediaRecognitionKeywordArguments", "MediaRecognitionProviderResult"),
"media-server": ("MediaServerKeywordArguments", "MediaServerProviderResult"),
"messaging": ("MessagingKeywordArguments", "MessagingProviderResult"),
"metadata": ("MetadataKeywordArguments", "MetadataProviderResult"),
"music": ("MusicKeywordArguments", "MusicProviderResult"),
"site": ("SiteKeywordArguments", "SiteProviderResult"),
"storage": ("StorageKeywordArguments", "StorageProviderResult"),
"tmdb": ("TmdbKeywordArguments", "TmdbProviderResult"),
"tvdb": ("TvdbKeywordArguments", "TvdbProviderResult"),
}
def _infer_observed_family(method: str) -> str:
"""按稳定能力前缀把已观察宿主方法归入可审计的输入/结果族。"""
for prefix, contract in _PREFIX_CONTRACTS:
if method.startswith(prefix):
return contract.family
if method.startswith(("mediaserver_", "media_exists", "media_statistic")):
return "media-server"
if method.startswith((
"download", "torrent_", "list_torrents", "refresh_torrents",
"remove_torrents", "start_torrents", "stop_torrents",
"set_torrents_tag", "update_torrent", "get_torrent_trackers",
"downloader_info", "filter_torrents", "transfer_completed",
)):
return "downloader"
if method.startswith((
"channel_", "delete_message", "edit_message", "finalize_message",
"mark_message_", "message_parser", "register_commands",
"send_direct_message", "send_message",
)):
return "messaging"
if method.startswith((
"any_files", "create_folder", "delete_file", "get_file_item",
"get_folder", "get_parent_item", "list_files", "media_files",
"rename_file", "snapshot_storage", "storage_manage", "transfer",
"upload_file",
)):
return "storage"
if method.startswith((
"metadata_", "obtain_specific_image", "recommend_name",
)):
return "metadata"
if method.startswith((
"async_identify_music", "async_match_music", "identify_music",
"match_music", "search_music",
)):
return "music"
if method.startswith((
"async_match_", "async_obtain_images", "async_recognize_media",
"async_update_recognize_cache", "match_", "obtain_images",
"recognize_media", "update_recognize_cache",
)):
return "media-recognition"
if method.startswith((
"async_movie_", "async_search_", "async_tv_", "movie_",
"search_collections", "search_medias", "search_persons",
"search_subtitles", "search_torrents", "tv_",
)):
return "media-discovery"
if method in {"clear_cache", "load_category_config", "save_category_config"}:
return "category"
if method in {"get_search_page_size", "refresh_userdata", "site_subtitle_links"}:
return "site"
if method == "user_authenticate":
return "authentication"
return "integration"
def _register_observed_host_contracts() -> None:
"""为全部宿主字符串调用登记完整 V2 字段,保留未知插件方法的 legacy fallback。"""
for method in _OBSERVED_HOST_METHODS:
if method in _METHOD_CONTRACTS:
continue
family = _infer_observed_family(method)
input_contract, result_contract = _FAMILY_IO_CONTRACTS[family]
_METHOD_CONTRACTS[method] = ModuleMethodContract(
family=family,
input_contract=input_contract,
result_contract=result_contract,
)
_register_observed_host_contracts()
def get_module_method_contract(method: str) -> ModuleMethodContract: def get_module_method_contract(method: str) -> ModuleMethodContract:
"""返回方法的显式能力族契约,未知方法保持既有 legacy 协议。""" """返回方法的显式能力族契约,未知方法保持既有 legacy 协议。"""
if contract := _METHOD_CONTRACTS.get(method): if contract := _METHOD_CONTRACTS.get(method):
+185 -1
View File
@@ -1,7 +1,7 @@
from pathlib import Path from pathlib import Path
from typing import Iterable, Optional, Dict, Any, List, Set, Callable from typing import Iterable, Optional, Dict, Any, List, Set, Callable
from pydantic import BaseModel, Field, field_validator, model_validator from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from app.schemas.common import JsonData from app.schemas.common import JsonData
from app.schemas.types import MediaType, NotificationChannel from app.schemas.types import MediaType, NotificationChannel
@@ -29,6 +29,190 @@ class BaseEventData(BaseModel):
pass pass
class ExtensibleEventData(BaseEventData):
"""允许第三方插件附加字段的类型化事件载荷基类。"""
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
class EmptyEventData(ExtensibleEventData):
"""当前没有固定字段、但仍需进入 typed registry 的事件载荷。"""
class PluginReloadEventData(ExtensibleEventData):
"""插件重载广播事件载荷。"""
plugin_id: str = Field(description="重载的插件 ID")
class PluginActionEventData(ExtensibleEventData):
"""插件命令动作载荷;具体动作参数由目标插件扩展。"""
plugin_id: Optional[str] = Field(default=None, description="目标插件 ID")
action: Optional[str] = Field(default=None, description="插件动作名称")
channel: Optional[str] = Field(default=None, description="消息渠道")
source: Optional[str] = Field(default=None, description="消息来源")
user: Optional[Any] = Field(default=None, description="发起用户")
class PluginTriggeredEventData(ExtensibleEventData):
"""插件主动发布的跨插件事件载荷。"""
plugin_id: str = Field(description="发布事件的插件 ID")
event_name: str = Field(description="插件定义的稳定事件名")
data: Any = Field(default=None, description="插件定义的事件数据")
class CommandExecuteEventData(ExtensibleEventData):
"""斜杠命令执行事件载荷。"""
cmd: str = Field(description="包含参数的完整命令文本")
user: Optional[Any] = Field(default=None, description="发起用户")
channel: Optional[str] = Field(default=None, description="消息渠道")
source: Optional[str] = Field(default=None, description="消息来源")
processing_status: Optional[Any] = Field(default=None, description="交互处理状态")
class SiteEventData(ExtensibleEventData):
"""站点新增、更新、删除或数据刷新事件载荷。"""
site_id: Optional[int | str] = Field(default=None, description="站点 ID 或通配符")
domain: Optional[str] = Field(default=None, description="站点域名")
name: Optional[str] = Field(default=None, description="站点名称")
site_url: Optional[str] = Field(default=None, description="站点地址")
class HistoryDeletedEventData(ExtensibleEventData):
"""历史记录删除事件的兼容载荷。"""
history_id: Optional[int] = Field(default=None, description="历史记录 ID")
src: Optional[str] = Field(default=None, description="关联源路径")
class DownloadFileDeletedEventData(ExtensibleEventData):
"""下载源文件删除事件载荷。"""
src: Optional[str] = Field(default=None, description="已删除的下载源路径")
hash: Optional[str] = Field(default=None, description="下载任务 hash")
class DownloadDeletedEventData(ExtensibleEventData):
"""下载任务删除事件载荷。"""
hash: str = Field(description="下载任务 hash")
torrents: List[Dict[str, Any]] = Field(default_factory=list, description="删除前任务快照")
class UserMessageEventData(ExtensibleEventData):
"""未被宿主命令或交互消费的用户文本消息载荷。"""
text: str = Field(description="用户文本")
userid: Optional[Any] = Field(default=None, description="用户 ID")
channel: Optional[str] = Field(default=None, description="消息渠道")
source: Optional[str] = Field(default=None, description="消息来源")
chat_id: Optional[Any] = Field(default=None, description="会话 ID")
reply_to_message_id: Optional[Any] = Field(default=None, description="回复消息 ID")
class NoticeMessageEventData(ExtensibleEventData):
"""宿主向消息模块发送的通知事件载荷。"""
type: Optional[Any] = Field(default=None, description="兼容消息类型")
title: Optional[str] = Field(default=None, description="消息标题")
text: Optional[str] = Field(default=None, description="消息正文")
userid: Optional[Any] = Field(default=None, description="目标用户 ID")
channel: Optional[Any] = Field(default=None, description="目标消息渠道")
source: Optional[str] = Field(default=None, description="消息来源")
class SubscribeCompleteEventData(ExtensibleEventData):
"""订阅完成广播事件载荷。"""
subscribe_id: int = Field(description="已完成订阅 ID")
subscribe_info: Dict[str, Any] = Field(default_factory=dict, description="订阅快照")
mediainfo: Dict[str, Any] = Field(default_factory=dict, description="媒体信息快照")
class SystemErrorEventData(ExtensibleEventData):
"""事件、模块、插件或调度器错误的宿主诊断载荷。"""
type: str = Field(description="错误来源类别")
error: str = Field(description="错误摘要")
traceback: Optional[str] = Field(default=None, description="错误堆栈")
class MetadataScrapeEventData(ExtensibleEventData):
"""媒体文件元数据刮削事件载荷。"""
fileitem: FileItem = Field(description="待刮削目录或文件项")
file_list: List[str] = Field(default_factory=list, description="待刮削文件清单")
meta: Any = Field(default=None, description="文件名解析对象")
mediainfo: Any = Field(default=None, description="媒体信息对象")
overwrite: bool = Field(default=False, description="是否覆盖已有元数据")
file_contexts: List[Any] = Field(default_factory=list, description="逐文件上下文")
class MessageActionEventData(ExtensibleEventData):
"""定向插件消息交互动作载荷。"""
plugin_id: Optional[str] = Field(default=None, description="目标插件 ID")
text: Optional[str] = Field(default=None, description="兼容动作文本")
input_text: Optional[str] = Field(default=None, description="用户输入文本")
userid: Optional[Any] = Field(default=None, description="用户 ID")
channel: Optional[str] = Field(default=None, description="消息渠道")
source: Optional[str] = Field(default=None, description="消息来源")
input_session_id: Optional[str] = Field(default=None, description="输入会话 ID")
payload: Any = Field(default=None, description="插件自定义交互数据")
class WorkflowExecuteEventData(ExtensibleEventData):
"""请求执行指定工作流的事件载荷。"""
workflow_id: int = Field(description="工作流 ID")
class NameRecognizeEventData(ExtensibleEventData):
"""影视名称辅助识别的输入和插件回写字段。"""
title: str = Field(description="待识别标题")
name: Optional[str] = Field(default=None, description="插件识别后的名称")
year: Optional[Any] = Field(default=None, description="年份")
season: Optional[Any] = Field(default=None, description="季号")
episode: Optional[Any] = Field(default=None, description="集号")
class MusicNameRecognizeEventData(ExtensibleEventData):
"""音乐名称辅助识别的输入和插件回写字段。"""
title: str = Field(description="待识别曲名")
artist: Optional[str] = Field(default=None, description="艺术家")
album: Optional[str] = Field(default=None, description="专辑")
year: Optional[Any] = Field(default=None, description="年份")
duration: Optional[Any] = Field(default=None, description="时长")
name: Optional[str] = Field(default=None, description="插件识别后的曲名")
class MediaRecognizeEventData(ExtensibleEventData):
"""影视媒体身份补充识别的输入和插件回写字段。"""
title: Optional[str] = Field(default=None, description="待识别标题")
year: Optional[Any] = Field(default=None, description="年份")
season: Optional[Any] = Field(default=None, description="季号")
type: Optional[Any] = Field(default=None, description="媒体类型")
media_source: Optional[Any] = Field(default=None, description="媒体数据源")
media_id: Optional[Any] = Field(default=None, description="数据源原生 ID")
mediainfo: Optional[Dict[str, Any]] = Field(default=None, description="插件回写媒体信息")
class MusicMediaRecognizeEventData(MediaRecognizeEventData):
"""音乐媒体身份补充识别的输入和插件回写字段。"""
artists: List[str] = Field(default_factory=list, description="艺术家列表")
album: Optional[str] = Field(default=None, description="专辑")
isrc: Optional[str] = Field(default=None, description="ISRC")
music_type: Optional[str] = Field(default=None, description="音乐实体类型")
class ConfigChangeEventData(BaseEventData): class ConfigChangeEventData(BaseEventData):
""" """
ConfigChange 事件的数据模型 ConfigChange 事件的数据模型
+22 -1
View File
@@ -65,6 +65,7 @@ SCHEMA_EXPORTS = {
'ChannelCapability': ('app.schemas.notification', 'ChannelCapability'), 'ChannelCapability': ('app.schemas.notification', 'ChannelCapability'),
'ChannelCapabilityManager': ('app.schemas.notification', 'ChannelCapabilityManager'), 'ChannelCapabilityManager': ('app.schemas.notification', 'ChannelCapabilityManager'),
'ClassVar': ('app.schemas.subscribe', 'ClassVar'), 'ClassVar': ('app.schemas.subscribe', 'ClassVar'),
'CommandExecuteEventData': ('app.schemas.event', 'CommandExecuteEventData'),
'CommandRegisterEventData': ('app.schemas.event', 'CommandRegisterEventData'), 'CommandRegisterEventData': ('app.schemas.event', 'CommandRegisterEventData'),
'ConfigChangeEventData': ('app.schemas.event', 'ConfigChangeEventData'), 'ConfigChangeEventData': ('app.schemas.event', 'ConfigChangeEventData'),
'ConfigDict': ('app.schemas.workflow', 'ConfigDict'), 'ConfigDict': ('app.schemas.workflow', 'ConfigDict'),
@@ -86,7 +87,9 @@ SCHEMA_EXPORTS = {
'Discriminator': ('app.schemas.context', 'Discriminator'), 'Discriminator': ('app.schemas.context', 'Discriminator'),
'DownloadAddedData': ('app.schemas.download', 'DownloadAddedData'), 'DownloadAddedData': ('app.schemas.download', 'DownloadAddedData'),
'DownloadAddedEventData': ('app.schemas.event', 'DownloadAddedEventData'), 'DownloadAddedEventData': ('app.schemas.event', 'DownloadAddedEventData'),
'DownloadDeletedEventData': ('app.schemas.event', 'DownloadDeletedEventData'),
'DownloadDirectory': ('app.schemas.download', 'DownloadDirectory'), 'DownloadDirectory': ('app.schemas.download', 'DownloadDirectory'),
'DownloadFileDeletedEventData': ('app.schemas.event', 'DownloadFileDeletedEventData'),
'DownloadHistory': ('app.schemas.history', 'DownloadHistory'), 'DownloadHistory': ('app.schemas.history', 'DownloadHistory'),
'DownloadTask': ('app.schemas.workflow', 'DownloadTask'), 'DownloadTask': ('app.schemas.workflow', 'DownloadTask'),
'DownloadTaskMedia': ('app.schemas.transfer', 'DownloadTaskMedia'), 'DownloadTaskMedia': ('app.schemas.transfer', 'DownloadTaskMedia'),
@@ -94,6 +97,7 @@ SCHEMA_EXPORTS = {
'DownloaderInfo': ('app.schemas.dashboard', 'DownloaderInfo'), 'DownloaderInfo': ('app.schemas.dashboard', 'DownloaderInfo'),
'DownloaderTorrent': ('app.schemas.transfer', 'DownloaderTorrent'), 'DownloaderTorrent': ('app.schemas.transfer', 'DownloaderTorrent'),
'DownloadingTorrent': ('app.schemas.transfer', 'DownloadingTorrent'), 'DownloadingTorrent': ('app.schemas.transfer', 'DownloadingTorrent'),
'EmptyEventData': ('app.schemas.event', 'EmptyEventData'),
'EndpointStats': ('app.schemas.monitoring', 'EndpointStats'), 'EndpointStats': ('app.schemas.monitoring', 'EndpointStats'),
'Enum': ('app.schemas.notification', 'Enum'), 'Enum': ('app.schemas.notification', 'Enum'),
'EpisodeFormat': ('app.schemas.transfer', 'EpisodeFormat'), 'EpisodeFormat': ('app.schemas.transfer', 'EpisodeFormat'),
@@ -103,12 +107,14 @@ SCHEMA_EXPORTS = {
'ErrorRequest': ('app.schemas.monitoring', 'ErrorRequest'), 'ErrorRequest': ('app.schemas.monitoring', 'ErrorRequest'),
'Event': ('app.schemas.event', 'Event'), 'Event': ('app.schemas.event', 'Event'),
'ExistMediaInfo': ('app.schemas.mediaserver', 'ExistMediaInfo'), 'ExistMediaInfo': ('app.schemas.mediaserver', 'ExistMediaInfo'),
'ExtensibleEventData': ('app.schemas.event', 'ExtensibleEventData'),
'Field': ('app.schemas.mcp', 'Field'), 'Field': ('app.schemas.mcp', 'Field'),
'FileItem': ('app.schemas.workflow', 'FileItem'), 'FileItem': ('app.schemas.workflow', 'FileItem'),
'FileNameData': ('app.schemas.common', 'FileNameData'), 'FileNameData': ('app.schemas.common', 'FileNameData'),
'FileURI': ('app.schemas.file', 'FileURI'), 'FileURI': ('app.schemas.file', 'FileURI'),
'FilterRuleGroup': ('app.schemas.system', 'FilterRuleGroup'), 'FilterRuleGroup': ('app.schemas.system', 'FilterRuleGroup'),
'Generic': ('app.schemas.response', 'Generic'), 'Generic': ('app.schemas.response', 'Generic'),
'HistoryDeletedEventData': ('app.schemas.event', 'HistoryDeletedEventData'),
'IdData': ('app.schemas.common', 'IdData'), 'IdData': ('app.schemas.common', 'IdData'),
'ImmediateException': ('app.schemas.exception', 'ImmediateException'), 'ImmediateException': ('app.schemas.exception', 'ImmediateException'),
'IncomingMessage': ('app.schemas.message', 'IncomingMessage'), 'IncomingMessage': ('app.schemas.message', 'IncomingMessage'),
@@ -173,6 +179,7 @@ SCHEMA_EXPORTS = {
'MediaLanguage': ('app.schemas.context', 'MediaLanguage'), 'MediaLanguage': ('app.schemas.context', 'MediaLanguage'),
'MediaPerson': ('app.schemas.context', 'MediaPerson'), 'MediaPerson': ('app.schemas.context', 'MediaPerson'),
'MediaRecognizeConvertEventData': ('app.schemas.event', 'MediaRecognizeConvertEventData'), 'MediaRecognizeConvertEventData': ('app.schemas.event', 'MediaRecognizeConvertEventData'),
'MediaRecognizeEventData': ('app.schemas.event', 'MediaRecognizeEventData'),
'MediaReleaseDate': ('app.schemas.context', 'MediaReleaseDate'), 'MediaReleaseDate': ('app.schemas.context', 'MediaReleaseDate'),
'MediaSearchResult': ('app.schemas.context', 'MediaSearchResult'), 'MediaSearchResult': ('app.schemas.context', 'MediaSearchResult'),
'MediaSearchResults': ('app.schemas.context', 'MediaSearchResults'), 'MediaSearchResults': ('app.schemas.context', 'MediaSearchResults'),
@@ -190,6 +197,7 @@ SCHEMA_EXPORTS = {
'MediaSourceInfo': ('app.schemas.event', 'MediaSourceInfo'), 'MediaSourceInfo': ('app.schemas.event', 'MediaSourceInfo'),
'MediaType': ('app.schemas.subscribe', 'MediaType'), 'MediaType': ('app.schemas.subscribe', 'MediaType'),
'Message': ('app.schemas.message', 'Message'), 'Message': ('app.schemas.message', 'Message'),
'MessageActionEventData': ('app.schemas.event', 'MessageActionEventData'),
'MessageClearBefore': ('app.schemas.message', 'MessageClearBefore'), 'MessageClearBefore': ('app.schemas.message', 'MessageClearBefore'),
'MessageClearData': ('app.schemas.message', 'MessageClearData'), 'MessageClearData': ('app.schemas.message', 'MessageClearData'),
'MessageClearScope': ('app.schemas.message', 'MessageClearScope'), 'MessageClearScope': ('app.schemas.message', 'MessageClearScope'),
@@ -197,6 +205,7 @@ SCHEMA_EXPORTS = {
'MessageResponse': ('app.schemas.message', 'MessageResponse'), 'MessageResponse': ('app.schemas.message', 'MessageResponse'),
'MessageType': ('app.schemas.message', 'MessageType'), 'MessageType': ('app.schemas.message', 'MessageType'),
'MetaInfo': ('app.schemas.transfer', 'MetaInfo'), 'MetaInfo': ('app.schemas.transfer', 'MetaInfo'),
'MetadataScrapeEventData': ('app.schemas.event', 'MetadataScrapeEventData'),
'MfaChallenge': ('app.schemas.token', 'MfaChallenge'), 'MfaChallenge': ('app.schemas.token', 'MfaChallenge'),
'MfaStatusData': ('app.schemas.mfa', 'MfaStatusData'), 'MfaStatusData': ('app.schemas.mfa', 'MfaStatusData'),
'MonitoringConfig': ('app.schemas.monitoring', 'MonitoringConfig'), 'MonitoringConfig': ('app.schemas.monitoring', 'MonitoringConfig'),
@@ -205,16 +214,20 @@ SCHEMA_EXPORTS = {
'MusicArtistInfo': ('app.schemas.music', 'MusicArtistInfo'), 'MusicArtistInfo': ('app.schemas.music', 'MusicArtistInfo'),
'MusicEntityType': ('app.schemas.music', 'MusicEntityType'), 'MusicEntityType': ('app.schemas.music', 'MusicEntityType'),
'MusicInfo': ('app.schemas.transfer', 'MusicInfo'), 'MusicInfo': ('app.schemas.transfer', 'MusicInfo'),
'MusicMediaRecognizeEventData': ('app.schemas.event', 'MusicMediaRecognizeEventData'),
'MusicMeta': ('app.schemas.transfer', 'MusicMeta'), 'MusicMeta': ('app.schemas.transfer', 'MusicMeta'),
'MusicNameRecognizeEventData': ('app.schemas.event', 'MusicNameRecognizeEventData'),
'MusicRecognitionCacheData': ('app.schemas.music', 'MusicRecognitionCacheData'), 'MusicRecognitionCacheData': ('app.schemas.music', 'MusicRecognitionCacheData'),
'MusicRecognitionCacheItem': ('app.schemas.music', 'MusicRecognitionCacheItem'), 'MusicRecognitionCacheItem': ('app.schemas.music', 'MusicRecognitionCacheItem'),
'MusicRecognizeRequest': ('app.schemas.music', 'MusicRecognizeRequest'), 'MusicRecognizeRequest': ('app.schemas.music', 'MusicRecognizeRequest'),
'MusicRelease': ('app.schemas.music', 'MusicRelease'), 'MusicRelease': ('app.schemas.music', 'MusicRelease'),
'MusicTargetEntityType': ('app.schemas.transfer', 'MusicTargetEntityType'), 'MusicTargetEntityType': ('app.schemas.transfer', 'MusicTargetEntityType'),
'NameData': ('app.schemas.common', 'NameData'), 'NameData': ('app.schemas.common', 'NameData'),
'NameRecognizeEventData': ('app.schemas.event', 'NameRecognizeEventData'),
'NameValueOption': ('app.schemas.workflow', 'NameValueOption'), 'NameValueOption': ('app.schemas.workflow', 'NameValueOption'),
'NetTestTarget': ('app.schemas.system', 'NetTestTarget'), 'NetTestTarget': ('app.schemas.system', 'NetTestTarget'),
'NotExistMediaInfo': ('app.schemas.mediaserver', 'NotExistMediaInfo'), 'NotExistMediaInfo': ('app.schemas.mediaserver', 'NotExistMediaInfo'),
'NoticeMessageEventData': ('app.schemas.event', 'NoticeMessageEventData'),
'NotificationChannel': ('app.schemas.notification', 'NotificationChannel'), 'NotificationChannel': ('app.schemas.notification', 'NotificationChannel'),
'NotificationConf': ('app.schemas.system', 'NotificationConf'), 'NotificationConf': ('app.schemas.system', 'NotificationConf'),
'NotificationSwitch': ('app.schemas.message', 'NotificationSwitch'), 'NotificationSwitch': ('app.schemas.message', 'NotificationSwitch'),
@@ -246,6 +259,7 @@ SCHEMA_EXPORTS = {
'Path': ('app.schemas.transfer', 'Path'), 'Path': ('app.schemas.transfer', 'Path'),
'PerformanceSnapshot': ('app.schemas.monitoring', 'PerformanceSnapshot'), 'PerformanceSnapshot': ('app.schemas.monitoring', 'PerformanceSnapshot'),
'Plugin': ('app.schemas.plugin', 'Plugin'), 'Plugin': ('app.schemas.plugin', 'Plugin'),
'PluginActionEventData': ('app.schemas.event', 'PluginActionEventData'),
'PluginCloneRequest': ('app.schemas.plugin', 'PluginCloneRequest'), 'PluginCloneRequest': ('app.schemas.plugin', 'PluginCloneRequest'),
'PluginDashboard': ('app.schemas.plugin', 'PluginDashboard'), 'PluginDashboard': ('app.schemas.plugin', 'PluginDashboard'),
'PluginDashboardMetaItem': ('app.schemas.plugin', 'PluginDashboardMetaItem'), 'PluginDashboardMetaItem': ('app.schemas.plugin', 'PluginDashboardMetaItem'),
@@ -261,10 +275,12 @@ SCHEMA_EXPORTS = {
'PluginRatingRequest': ('app.schemas.plugin', 'PluginRatingRequest'), 'PluginRatingRequest': ('app.schemas.plugin', 'PluginRatingRequest'),
'PluginReleaseData': ('app.schemas.plugin', 'PluginReleaseData'), 'PluginReleaseData': ('app.schemas.plugin', 'PluginReleaseData'),
'PluginReleaseItem': ('app.schemas.plugin', 'PluginReleaseItem'), 'PluginReleaseItem': ('app.schemas.plugin', 'PluginReleaseItem'),
'PluginReloadEventData': ('app.schemas.event', 'PluginReloadEventData'),
'PluginRemoteInfo': ('app.schemas.plugin', 'PluginRemoteInfo'), 'PluginRemoteInfo': ('app.schemas.plugin', 'PluginRemoteInfo'),
'PluginRuntimeStatus': ('app.schemas.plugin', 'PluginRuntimeStatus'), 'PluginRuntimeStatus': ('app.schemas.plugin', 'PluginRuntimeStatus'),
'PluginRuntimeSummary': ('app.schemas.plugin', 'PluginRuntimeSummary'), 'PluginRuntimeSummary': ('app.schemas.plugin', 'PluginRuntimeSummary'),
'PluginSidebarNavItem': ('app.schemas.plugin', 'PluginSidebarNavItem'), 'PluginSidebarNavItem': ('app.schemas.plugin', 'PluginSidebarNavItem'),
'PluginTriggeredEventData': ('app.schemas.event', 'PluginTriggeredEventData'),
'PluginWorkflowActionGroup': ('app.schemas.workflow', 'PluginWorkflowActionGroup'), 'PluginWorkflowActionGroup': ('app.schemas.workflow', 'PluginWorkflowActionGroup'),
'ProcessInfo': ('app.schemas.dashboard', 'ProcessInfo'), 'ProcessInfo': ('app.schemas.dashboard', 'ProcessInfo'),
'ProgressKeyData': ('app.schemas.common', 'ProgressKeyData'), 'ProgressKeyData': ('app.schemas.common', 'ProgressKeyData'),
@@ -304,6 +320,7 @@ SCHEMA_EXPORTS = {
'SiteAuth': ('app.schemas.site', 'SiteAuth'), 'SiteAuth': ('app.schemas.site', 'SiteAuth'),
'SiteCategory': ('app.schemas.site', 'SiteCategory'), 'SiteCategory': ('app.schemas.site', 'SiteCategory'),
'SiteCookieUpdate': ('app.schemas.site', 'SiteCookieUpdate'), 'SiteCookieUpdate': ('app.schemas.site', 'SiteCookieUpdate'),
'SiteEventData': ('app.schemas.event', 'SiteEventData'),
'SiteIconData': ('app.schemas.site', 'SiteIconData'), 'SiteIconData': ('app.schemas.site', 'SiteIconData'),
'SiteMappingData': ('app.schemas.site', 'SiteMappingData'), 'SiteMappingData': ('app.schemas.site', 'SiteMappingData'),
'SiteStatistic': ('app.schemas.site', 'SiteStatistic'), 'SiteStatistic': ('app.schemas.site', 'SiteStatistic'),
@@ -327,6 +344,7 @@ SCHEMA_EXPORTS = {
'SubscrbieInfo': ('app.schemas.subscribe', 'SubscrbieInfo'), 'SubscrbieInfo': ('app.schemas.subscribe', 'SubscrbieInfo'),
'Subscribe': ('app.schemas.workflow', 'Subscribe'), 'Subscribe': ('app.schemas.workflow', 'Subscribe'),
'SubscribeAddedEventData': ('app.schemas.event', 'SubscribeAddedEventData'), 'SubscribeAddedEventData': ('app.schemas.event', 'SubscribeAddedEventData'),
'SubscribeCompleteEventData': ('app.schemas.event', 'SubscribeCompleteEventData'),
'SubscribeCompletionCheckEventData': ('app.schemas.event', 'SubscribeCompletionCheckEventData'), 'SubscribeCompletionCheckEventData': ('app.schemas.event', 'SubscribeCompletionCheckEventData'),
'SubscribeDeletedEventData': ('app.schemas.event', 'SubscribeDeletedEventData'), 'SubscribeDeletedEventData': ('app.schemas.event', 'SubscribeDeletedEventData'),
'SubscribeDownloadFileInfo': ('app.schemas.subscribe', 'SubscribeDownloadFileInfo'), 'SubscribeDownloadFileInfo': ('app.schemas.subscribe', 'SubscribeDownloadFileInfo'),
@@ -341,6 +359,7 @@ SCHEMA_EXPORTS = {
'SubtitleDownloadData': ('app.schemas.download', 'SubtitleDownloadData'), 'SubtitleDownloadData': ('app.schemas.download', 'SubtitleDownloadData'),
'SubtitleInfo': ('app.schemas.search', 'SubtitleInfo'), 'SubtitleInfo': ('app.schemas.search', 'SubtitleInfo'),
'SystemEnvironmentUpdateData': ('app.schemas.system', 'SystemEnvironmentUpdateData'), 'SystemEnvironmentUpdateData': ('app.schemas.system', 'SystemEnvironmentUpdateData'),
'SystemErrorEventData': ('app.schemas.event', 'SystemErrorEventData'),
'SystemModuleInfo': ('app.schemas.system', 'SystemModuleInfo'), 'SystemModuleInfo': ('app.schemas.system', 'SystemModuleInfo'),
'SystemModuleListData': ('app.schemas.system', 'SystemModuleListData'), 'SystemModuleListData': ('app.schemas.system', 'SystemModuleListData'),
'TMDbException': ('app.schemas.exception', 'TMDbException'), 'TMDbException': ('app.schemas.exception', 'TMDbException'),
@@ -386,6 +405,7 @@ SCHEMA_EXPORTS = {
'UserInDB': ('app.schemas.user', 'UserInDB'), 'UserInDB': ('app.schemas.user', 'UserInDB'),
'UserInDBBase': ('app.schemas.user', 'UserInDBBase'), 'UserInDBBase': ('app.schemas.user', 'UserInDBBase'),
'UserMessage': ('app.schemas.agent', 'UserMessage'), 'UserMessage': ('app.schemas.agent', 'UserMessage'),
'UserMessageEventData': ('app.schemas.event', 'UserMessageEventData'),
'UserPermissions': ('app.schemas.user', 'UserPermissions'), 'UserPermissions': ('app.schemas.user', 'UserPermissions'),
'UserUpdate': ('app.schemas.user', 'UserUpdate'), 'UserUpdate': ('app.schemas.user', 'UserUpdate'),
'ValidationIssue': ('app.schemas.response', 'ValidationIssue'), 'ValidationIssue': ('app.schemas.response', 'ValidationIssue'),
@@ -397,6 +417,7 @@ SCHEMA_EXPORTS = {
'WechatClawBotKnownTarget': ('app.schemas.notification', 'WechatClawBotKnownTarget'), 'WechatClawBotKnownTarget': ('app.schemas.notification', 'WechatClawBotKnownTarget'),
'Workflow': ('app.schemas.workflow', 'Workflow'), 'Workflow': ('app.schemas.workflow', 'Workflow'),
'WorkflowActionDefinition': ('app.schemas.workflow', 'WorkflowActionDefinition'), 'WorkflowActionDefinition': ('app.schemas.workflow', 'WorkflowActionDefinition'),
'WorkflowExecuteEventData': ('app.schemas.event', 'WorkflowExecuteEventData'),
'WorkflowExecutionConfig': ('app.schemas.workflow', 'WorkflowExecutionConfig'), 'WorkflowExecutionConfig': ('app.schemas.workflow', 'WorkflowExecutionConfig'),
'WorkflowExecutionState': ('app.schemas.workflow', 'WorkflowExecutionState'), 'WorkflowExecutionState': ('app.schemas.workflow', 'WorkflowExecutionState'),
'WorkflowNodeState': ('app.schemas.workflow', 'WorkflowNodeState'), 'WorkflowNodeState': ('app.schemas.workflow', 'WorkflowNodeState'),
@@ -415,7 +436,7 @@ SCHEMA_EXPORTS = {
SCHEMA_CONFLICTS = { SCHEMA_CONFLICTS = {
'Any': ['app.schemas.common', 'app.schemas.context', 'app.schemas.event', 'app.schemas.mediaserver', 'app.schemas.message', 'app.schemas.response', 'app.schemas.subscribe', 'app.schemas.system', 'app.schemas.workflow'], 'Any': ['app.schemas.common', 'app.schemas.context', 'app.schemas.event', 'app.schemas.mediaserver', 'app.schemas.message', 'app.schemas.response', 'app.schemas.subscribe', 'app.schemas.system', 'app.schemas.workflow'],
'BaseModel': ['app.schemas.agent', 'app.schemas.cache', 'app.schemas.category', 'app.schemas.common', 'app.schemas.context', 'app.schemas.dashboard', 'app.schemas.download', 'app.schemas.event', 'app.schemas.file', 'app.schemas.history', 'app.schemas.llm', 'app.schemas.mediaserver', 'app.schemas.message', 'app.schemas.mfa', 'app.schemas.music', 'app.schemas.monitoring', 'app.schemas.notification', 'app.schemas.plugin', 'app.schemas.response', 'app.schemas.rule', 'app.schemas.search', 'app.schemas.storage', 'app.schemas.openai', 'app.schemas.servarr', 'app.schemas.servcookie', 'app.schemas.site', 'app.schemas.subscribe', 'app.schemas.system', 'app.schemas.tmdb', 'app.schemas.token', 'app.schemas.transfer', 'app.schemas.user', 'app.schemas.workflow', 'app.schemas.mcp'], 'BaseModel': ['app.schemas.agent', 'app.schemas.cache', 'app.schemas.category', 'app.schemas.common', 'app.schemas.context', 'app.schemas.dashboard', 'app.schemas.download', 'app.schemas.event', 'app.schemas.file', 'app.schemas.history', 'app.schemas.llm', 'app.schemas.mediaserver', 'app.schemas.message', 'app.schemas.mfa', 'app.schemas.music', 'app.schemas.monitoring', 'app.schemas.notification', 'app.schemas.plugin', 'app.schemas.response', 'app.schemas.rule', 'app.schemas.search', 'app.schemas.storage', 'app.schemas.openai', 'app.schemas.servarr', 'app.schemas.servcookie', 'app.schemas.site', 'app.schemas.subscribe', 'app.schemas.system', 'app.schemas.tmdb', 'app.schemas.token', 'app.schemas.transfer', 'app.schemas.user', 'app.schemas.workflow', 'app.schemas.mcp'],
'ConfigDict': ['app.schemas.agent', 'app.schemas.category', 'app.schemas.history', 'app.schemas.mediaserver', 'app.schemas.response', 'app.schemas.openai', 'app.schemas.servcookie', 'app.schemas.site', 'app.schemas.subscribe', 'app.schemas.user', 'app.schemas.workflow'], 'ConfigDict': ['app.schemas.agent', 'app.schemas.category', 'app.schemas.event', 'app.schemas.history', 'app.schemas.mediaserver', 'app.schemas.response', 'app.schemas.openai', 'app.schemas.servcookie', 'app.schemas.site', 'app.schemas.subscribe', 'app.schemas.user', 'app.schemas.workflow'],
'Context': ['app.schemas.context', 'app.schemas.workflow'], 'Context': ['app.schemas.context', 'app.schemas.workflow'],
'Dict': ['app.schemas.category', 'app.schemas.common', 'app.schemas.context', 'app.schemas.event', 'app.schemas.mediaserver', 'app.schemas.message', 'app.schemas.notification', 'app.schemas.plugin', 'app.schemas.openai', 'app.schemas.site', 'app.schemas.subscribe', 'app.schemas.mcp'], 'Dict': ['app.schemas.category', 'app.schemas.common', 'app.schemas.context', 'app.schemas.event', 'app.schemas.mediaserver', 'app.schemas.message', 'app.schemas.notification', 'app.schemas.plugin', 'app.schemas.openai', 'app.schemas.site', 'app.schemas.subscribe', 'app.schemas.mcp'],
'DownloadTask': ['app.schemas.download', 'app.schemas.workflow'], 'DownloadTask': ['app.schemas.download', 'app.schemas.workflow'],
@@ -621,6 +621,9 @@ ModuleMethodSpec(
`ModuleCapability` Protocol 为宿主和新插件提供静态声明入口,但不替换字符串 dispatcher ABI。 `ModuleCapability` Protocol 为宿主和新插件提供静态声明入口,但不替换字符串 dispatcher ABI。
- 22 个显式方法进一步登记宿主真实传入的 required parameter 名称,覆盖识别、搜索、媒体服务器、存储、 - 22 个显式方法进一步登记宿主真实传入的 required parameter 名称,覆盖识别、搜索、媒体服务器、存储、
消息收尾、命令注册和 webhookdispatcher 仍只输出诊断 warning,不阻断缺少参数的旧插件或未知自定义方法。 消息收尾、命令注册和 webhookdispatcher 仍只输出诊断 warning,不阻断缺少参数的旧插件或未知自定义方法。
- 契约清单现覆盖静态扫描到的 211 个宿主字符串调用,并保留一个暂未被宿主调用的 `send_message` 公开能力,
共 212 个显式 V2 spec。原先仅按 prefix 分类或落入默认 legacy 的宿主方法均获得稳定 family、输入合同、
结果合同、执行、超时和错误语义;未知第三方自定义方法仍走开放 legacy fallback,不拒绝加载或执行。
#### ARCH-241Event Contract Registry #### ARCH-241Event Contract Registry
@@ -658,6 +661,9 @@ ModuleMethodSpec(
- 订阅变更、下载添加、整理成功/失败等用户副作用标记为 `durable_required`,只表达完成语义要求; - 订阅变更、下载添加、整理成功/失败等用户副作用标记为 `durable_required`,只表达完成语义要求;
在 ARCH-251 pilot 完成前不虚构当前已具备持久投递。SystemError 仍沿用既有递归保护和异常通知路径。 在 ARCH-251 pilot 完成前不虚构当前已具备持久投递。SystemError 仍沿用既有递归保护和异常通知路径。
- runtime contract baseline 新增稳定 `event_specs`,后续 enum 新增必须同步登记,且不比较源码行号。 - runtime contract baseline 新增稳定 `event_specs`,后续 enum 新增必须同步登记,且不比较源码行号。
- 53 个事件现已全部绑定 typed payload,原有 28 个 `legacy_dict` 项归零。插件动作/触发等开放事件使用
“公共字段类型化 + `extra=allow`”模型,Webhook 与 Workflow execution 复用既有 DTO;验证仍只诊断并投递
同一个原始 dict/model,因此插件字段、对象引用和链式原地修改语义未改变。
#### ARCH-242Module/Integration 质量清单 #### ARCH-242Module/Integration 质量清单
+4 -2
View File
@@ -13,8 +13,8 @@
"runtime_to_db": [], "runtime_to_db": [],
"workflow_to_db": [] "workflow_to_db": []
}, },
"edge_count": 6367, "edge_count": 6369,
"edge_sha256": "62c413857cd12dbaf4d31e4fad4c6b6c80f7dc747d0d5a74ecb61c44b4996822", "edge_sha256": "62638f6e334e7deb05902ca1e50cdcd03245a85c207dd2b4efaa03306cd28187",
"edges": [ "edges": [
"app -> app.runtime", "app -> app.runtime",
"app -> app.runtime.compat", "app -> app.runtime.compat",
@@ -5436,7 +5436,9 @@
"app.runtime.event.binding -> app.runtime.log", "app.runtime.event.binding -> app.runtime.log",
"app.runtime.event.contracts -> app.schemas", "app.runtime.event.contracts -> app.schemas",
"app.runtime.event.contracts -> app.schemas.event", "app.runtime.event.contracts -> app.schemas.event",
"app.runtime.event.contracts -> app.schemas.mediaserver",
"app.runtime.event.contracts -> app.schemas.types", "app.runtime.event.contracts -> app.schemas.types",
"app.runtime.event.contracts -> app.schemas.workflow",
"app.runtime.event.dispatch -> app.runtime", "app.runtime.event.dispatch -> app.runtime",
"app.runtime.event.dispatch -> app.runtime.correlation", "app.runtime.event.dispatch -> app.runtime.correlation",
"app.runtime.event.dispatch -> app.runtime.event", "app.runtime.event.dispatch -> app.runtime.event",
File diff suppressed because it is too large Load Diff
+19 -3
View File
@@ -18,7 +18,7 @@ from app.schemas.types import ChainEventType, EventType, MediaType
def test_every_event_enum_has_complete_contract() -> None: def test_every_event_enum_has_complete_contract() -> None:
"""53 个广播/链式事件必须全部登记且 legacy 项必须解释原因""" """53 个广播/链式事件必须全部登记并绑定 typed payload"""
expected = {*EventType, *ChainEventType} expected = {*EventType, *ChainEventType}
assert set(EVENT_CONTRACTS) == expected assert set(EVENT_CONTRACTS) == expected
@@ -26,8 +26,24 @@ def test_every_event_enum_has_complete_contract() -> None:
for contract in EVENT_CONTRACTS.values(): for contract in EVENT_CONTRACTS.values():
assert contract.mode in {"broadcast", "chain"} assert contract.mode in {"broadcast", "chain"}
assert contract.payload_contract assert contract.payload_contract
if contract.payload_contract == "legacy_dict": assert contract.payload_model is not None
assert contract.legacy_reason assert contract.payload_contract != "legacy_dict"
assert contract.legacy_reason is None
def test_extensible_plugin_payload_accepts_custom_fields_without_shape_change() -> None:
"""插件动作 contract 只校验公共字段,插件自定义字段和原始 dict 均保持不变。"""
payload = {
"plugin_id": "DemoPlugin",
"action": "refresh",
"plugin_owned_field": {"value": 1},
}
assert validate_event_payload(EventType.PluginAction, payload) == ()
event = Event(EventType.PluginAction, payload)
assert event.event_data is payload
assert event.event_data["plugin_owned_field"] == {"value": 1}
def test_typed_payload_is_validated_without_changing_public_shape() -> None: def test_typed_payload_is_validated_without_changing_public_shape() -> None:
+11 -7
View File
@@ -19,15 +19,19 @@ RUNTIME_BASELINE = (
) )
def test_all_scanned_module_methods_resolve_a_contract() -> None: def test_all_scanned_host_module_methods_have_explicit_v2_contracts() -> None:
"""架构快照中的所有字符串方法都必须能解析到稳定聚合规则""" """架构快照中的全部宿主字符串方法都必须显式登记 V2 契约"""
payload = json.loads(RUNTIME_BASELINE.read_text(encoding="utf-8")) payload = json.loads(RUNTIME_BASELINE.read_text(encoding="utf-8"))
methods = payload["run_module"]["methods"] methods = set(payload["run_module"]["methods"])
contracts = list_explicit_module_contracts()
assert methods assert methods
assert methods <= contracts.keys()
for method in methods: for method in methods:
contract = get_module_method_contract(method) contract = contracts[method]
assert isinstance(contract.aggregation, ModuleResultAggregation) assert isinstance(contract.aggregation, ModuleResultAggregation)
assert contract.input_contract != "legacy_args"
assert contract.result_contract != "Any"
assert contract.plugin_short_circuit is True assert contract.plugin_short_circuit is True
@@ -59,11 +63,11 @@ def test_unknown_plugin_method_keeps_legacy_compatibility() -> None:
assert contract.supports_async is True assert contract.supports_async is True
def test_contract_v2_freezes_at_least_twenty_high_value_methods() -> None: def test_contract_v2_freezes_every_observed_host_method() -> None:
"""首批能力必须具备可生成文档和诊断的完整 V2 字段。""" """全部已观察宿主能力必须具备可生成文档和诊断的完整 V2 字段。"""
contracts = list_explicit_module_contracts() contracts = list_explicit_module_contracts()
assert len(contracts) >= 20 assert len(contracts) >= 211
for contract in contracts.values(): for contract in contracts.values():
assert contract.version == 1 assert contract.version == 1
assert contract.input_contract != "legacy_args" assert contract.input_contract != "legacy_args"