fix(agent): preserve collection totals in truncated results

This commit is contained in:
jxxghp
2026-09-01 10:49:22 +08:00
parent 1850c91b00
commit 2f7f2da300
12 changed files with 196 additions and 94 deletions
+5 -2
View File
@@ -140,8 +140,11 @@ class MoviePilotApiExecutor:
continue
if not collection:
return payload
result = dict(payload)
result["collection"] = collection
# 集合数据可能触发通用工具结果截断;把元数据放在 data 前面,确保预览仍保留精确总数。
result = {"collection": collection}
for key, value in payload.items():
if key != "collection":
result[key] = value
return result
async def execute(
+42 -42
View File
@@ -5206,7 +5206,7 @@
},
{
"additionalProperties": false,
"description": "Read the current host network receive and transmit counters. Method: GET. Path: /api/v1/dashboard/network. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "Read the current host network receive and transmit counters. Method: GET. Path: /api/v1/dashboard/network. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "dashboard.network",
@@ -5262,7 +5262,7 @@
},
{
"additionalProperties": false,
"description": "List host processes visible to the MoviePilot runtime. Method: GET. Path: /api/v1/dashboard/processes. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "List host processes visible to the MoviePilot runtime. Method: GET. Path: /api/v1/dashboard/processes. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "dashboard.processes",
@@ -5350,7 +5350,7 @@
},
{
"additionalProperties": false,
"description": "Read aggregate file-transfer counts grouped by time period. Method: GET. Path: /api/v1/dashboard/transfer. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "Read aggregate file-transfer counts grouped by time period. Method: GET. Path: /api/v1/dashboard/transfer. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "dashboard.transfer.statistics",
@@ -5467,7 +5467,7 @@
},
{
"additionalProperties": false,
"description": "List managed database backup artifacts without exposing host paths. Method: GET. Path: /api/v1/system/database/backups. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "List managed database backup artifacts without exposing host paths. Method: GET. Path: /api/v1/system/database/backups. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "database.backups.list",
@@ -5576,7 +5576,7 @@
},
{
"additionalProperties": false,
"description": "List enabled downloader instance names and provider types without credentials. Method: GET. Path: /api/v1/download/clients. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "List enabled downloader instance names and provider types without credentials. Method: GET. Path: /api/v1/download/clients. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "download.clients",
@@ -5653,7 +5653,7 @@
},
{
"additionalProperties": false,
"description": "Page MoviePilot download-history records in reverse chronological order. Method: GET. Path: /api/v1/history/download. Effect: safe_read. Collection response: data remains a list and the endpoint's documented pagination or limit defaults remain in effect. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "Page MoviePilot download-history records in reverse chronological order. Method: GET. Path: /api/v1/history/download. Effect: safe_read. Collection response: data remains a list and the endpoint's documented pagination or limit defaults remain in effect. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count-only request, use the smallest valid page and read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "download.history.list",
@@ -5708,7 +5708,7 @@
},
{
"additionalProperties": false,
"description": "List configured downloader save-path URIs that may be passed to download.add. Method: GET. Path: /api/v1/download/paths. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "List configured downloader save-path URIs that may be passed to download.add. Method: GET. Path: /api/v1/download/paths. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "download.paths",
@@ -5764,7 +5764,7 @@
},
{
"additionalProperties": false,
"description": "List currently downloading MoviePilot tasks with their canonical media context. Method: GET. Path: /api/v1/download/. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "List currently downloading MoviePilot tasks with their canonical media context. Method: GET. Path: /api/v1/download/. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "download.tasks.active",
@@ -6440,7 +6440,7 @@
},
{
"additionalProperties": false,
"description": "List seasons defined by one exact TMDB episode-group identity. Method: GET. Path: /api/v1/media/group/seasons/{episode_group}. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "List seasons defined by one exact TMDB episode-group identity. Method: GET. Path: /api/v1/media/group/seasons/{episode_group}. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "media.episode_group.seasons",
@@ -6512,7 +6512,7 @@
},
{
"additionalProperties": false,
"description": "List alternate TMDB episode groups available for one TV media identity. Method: GET. Path: /api/v1/media/groups/{tmdbid}. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "List alternate TMDB episode groups available for one TV media identity. Method: GET. Path: /api/v1/media/groups/{tmdbid}. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "media.episode_groups",
@@ -6584,7 +6584,7 @@
},
{
"additionalProperties": false,
"description": "Read TMDB episode release information for one season. Method: GET. Path: /api/v1/tmdb/{tmdbid}/{season}. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "Read TMDB episode release information for one season. Method: GET. Path: /api/v1/tmdb/{tmdbid}/{season}. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "media.episode_schedule",
@@ -7090,7 +7090,7 @@
},
{
"additionalProperties": false,
"description": "List seasons for one exact media identity or a title-and-year fallback. Method: GET. Path: /api/v1/media/seasons. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "List seasons for one exact media identity or a title-and-year fallback. Method: GET. Path: /api/v1/media/seasons. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "media.seasons",
@@ -7192,7 +7192,7 @@
},
{
"additionalProperties": false,
"description": "List metadata sources currently registered for MoviePilot media operations. Method: GET. Path: /api/v1/media/source. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "List metadata sources currently registered for MoviePilot media operations. Method: GET. Path: /api/v1/media/source. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "media.sources",
@@ -8210,7 +8210,7 @@
},
{
"additionalProperties": false,
"description": "List installed plugins and their runtime status. Method: GET. Path: /api/v1/plugin/. Effect: safe_read. Collection response: data remains a list and the endpoint's documented pagination or limit defaults remain in effect. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "List installed plugins and their runtime status. Method: GET. Path: /api/v1/plugin/. Effect: safe_read. Collection response: data remains a list and the endpoint's documented pagination or limit defaults remain in effect. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count-only request, use the smallest valid page and read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "plugin.installed",
@@ -8301,7 +8301,7 @@
},
{
"additionalProperties": false,
"description": "List plugins available from configured marketplaces. Method: GET. Path: /api/v1/plugin/. Effect: safe_read. Collection response: data remains a list and the endpoint's documented pagination or limit defaults remain in effect. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "List plugins available from configured marketplaces. Method: GET. Path: /api/v1/plugin/. Effect: safe_read. Collection response: data remains a list and the endpoint's documented pagination or limit defaults remain in effect. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count-only request, use the smallest valid page and read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "plugin.market",
@@ -8923,7 +8923,7 @@
},
{
"additionalProperties": false,
"description": "List registered scheduler jobs and their current state. Method: GET. Path: /api/v1/dashboard/schedule. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "List registered scheduler jobs and their current state. Method: GET. Path: /api/v1/dashboard/schedule. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "scheduler.list",
@@ -9157,7 +9157,7 @@
},
{
"additionalProperties": false,
"description": "Search torrent sites for one canonical media identity. Method: GET. Path: /api/v1/search/media/{media_id}. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "Search torrent sites for one canonical media identity. Method: GET. Path: /api/v1/search/media/{media_id}. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "search.torrents",
@@ -9356,7 +9356,7 @@
},
{
"additionalProperties": false,
"description": "List torrent categories supported by one configured site. Method: GET. Path: /api/v1/site/category/{site_id}. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "List torrent categories supported by one configured site. Method: GET. Path: /api/v1/site/category/{site_id}. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "site.category",
@@ -9513,7 +9513,7 @@
},
{
"additionalProperties": false,
"description": "List configured sites with status/name filters; authentication fields are returned only to a superuser. Method: GET. Path: /api/v1/site/agent. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "List configured sites with status/name filters; authentication fields are returned only to a superuser. Method: GET. Path: /api/v1/site/agent. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "site.list",
@@ -9743,7 +9743,7 @@
},
{
"additionalProperties": false,
"description": "List configured sites selected for RSS subscription processing. Method: GET. Path: /api/v1/site/rss. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "List configured sites selected for RSS subscription processing. Method: GET. Path: /api/v1/site/rss. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "site.rss",
@@ -9799,7 +9799,7 @@
},
{
"additionalProperties": false,
"description": "List active configured sites supporting one exact media type. Method: GET. Path: /api/v1/site/media/{media_type}. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "List active configured sites supporting one exact media type. Method: GET. Path: /api/v1/site/media/{media_type}. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "site.searchable",
@@ -9903,7 +9903,7 @@
},
{
"additionalProperties": false,
"description": "Read the latest account and traffic statistics for all configured sites. Method: GET. Path: /api/v1/site/statistic. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "Read the latest account and traffic statistics for all configured sites. Method: GET. Path: /api/v1/site/statistic. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "site.statistics",
@@ -10028,7 +10028,7 @@
},
{
"additionalProperties": false,
"description": "Read the latest account statistics collected from one site. Method: GET. Path: /api/v1/site/userdata/{site_id}. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "Read the latest account statistics collected from one site. Method: GET. Path: /api/v1/site/userdata/{site_id}. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "site.userdata",
@@ -10112,7 +10112,7 @@
},
{
"additionalProperties": false,
"description": "Read the latest collected account statistics for every configured site. Method: GET. Path: /api/v1/site/userdata/latest. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "Read the latest collected account statistics for every configured site. Method: GET. Path: /api/v1/site/userdata/latest. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "site.userdata.latest",
@@ -10200,7 +10200,7 @@
},
{
"additionalProperties": false,
"description": "List slash commands that the Agent may dispatch. Method: GET. Path: /api/v1/message/agent/commands. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "List slash commands that the Agent may dispatch. Method: GET. Path: /api/v1/message/agent/commands. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "slash.list",
@@ -10298,7 +10298,7 @@
},
{
"additionalProperties": false,
"description": "List files or directories from one configured storage location. Method: POST. Path: /api/v1/storage/agent/list. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "List files or directories from one configured storage location. Method: POST. Path: /api/v1/storage/agent/list. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"body": {
"$ref": "#/$defs/FileItem-Input",
@@ -10492,7 +10492,7 @@
},
{
"additionalProperties": false,
"description": "Read configured directory or storage settings. Method: GET. Path: /api/v1/storage/directories. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "Read configured directory or storage settings. Method: GET. Path: /api/v1/storage/directories. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "storage.settings",
@@ -10881,7 +10881,7 @@
},
{
"additionalProperties": false,
"description": "List subscription-sharing user IDs followed by the current user. Method: GET. Path: /api/v1/subscribe/follow. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "List subscription-sharing user IDs followed by the current user. Method: GET. Path: /api/v1/subscribe/follow. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "subscription.follow.list",
@@ -10990,7 +10990,7 @@
},
{
"additionalProperties": false,
"description": "List completed or archived subscription records. Method: GET. Path: /api/v1/subscribe/history/{mtype}. Effect: safe_read. Collection response: data remains a list and the endpoint's documented pagination or limit defaults remain in effect. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "List completed or archived subscription records. Method: GET. Path: /api/v1/subscribe/history/{mtype}. Effect: safe_read. Collection response: data remains a list and the endpoint's documented pagination or limit defaults remain in effect. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count-only request, use the smallest valid page and read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "subscription.history",
@@ -11093,7 +11093,7 @@
},
{
"additionalProperties": false,
"description": "List active subscriptions. Method: GET. Path: /api/v1/subscribe/. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "List active subscriptions. Method: GET. Path: /api/v1/subscribe/. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "subscription.list",
@@ -11438,7 +11438,7 @@
},
{
"additionalProperties": false,
"description": "Read aggregate contribution and reuse counts for subscription sharers. Method: GET. Path: /api/v1/subscribe/share/statistics. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "Read aggregate contribution and reuse counts for subscription sharers. Method: GET. Path: /api/v1/subscribe/share/statistics. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "subscription.share.statistics",
@@ -11678,7 +11678,7 @@
},
{
"additionalProperties": false,
"description": "List public subscriptions owned by one accessible MoviePilot username. Method: GET. Path: /api/v1/subscribe/user/{username}. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "List public subscriptions owned by one accessible MoviePilot username. Method: GET. Path: /api/v1/subscribe/user/{username}. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "subscription.user.list",
@@ -11750,7 +11750,7 @@
},
{
"additionalProperties": false,
"description": "Search subtitle providers for one canonical media identity and optional season or episode. Method: GET. Path: /api/v1/search/subtitle/media/{media_id}. Effect: external_side_effect. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "Search subtitle providers for one canonical media identity and optional season or episode. Method: GET. Path: /api/v1/search/subtitle/media/{media_id}. Effect: external_side_effect. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "subtitle.search.media",
@@ -11992,7 +11992,7 @@
},
{
"additionalProperties": false,
"description": "List approved built-in network-test targets without exposing their request URLs. Method: GET. Path: /api/v1/system/nettest/targets. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "List approved built-in network-test targets without exposing their request URLs. Method: GET. Path: /api/v1/system/nettest/targets. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "system.network.targets",
@@ -12225,7 +12225,7 @@
},
{
"additionalProperties": false,
"description": "List available MoviePilot GitHub releases. Method: GET. Path: /api/v1/system/versions. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "List available MoviePilot GitHub releases. Method: GET. Path: /api/v1/system/versions. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "system.versions",
@@ -12514,7 +12514,7 @@
},
{
"additionalProperties": false,
"description": "List file-transfer history with filters and pagination. Method: GET. Path: /api/v1/history/transfer. Effect: safe_read. Collection response: items stay in data.list and the exact total stays in data.total.",
"description": "List file-transfer history with filters and pagination. Method: GET. Path: /api/v1/history/transfer. Effect: safe_read. Collection response: items stay in data.list and the exact total stays in data.total. For a count-only request, use the smallest documented page and read that total; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "transfer.history",
@@ -12806,7 +12806,7 @@
},
{
"additionalProperties": false,
"description": "Page durable transfer tasks awaiting manual review or retry recovery. Method: GET. Path: /api/v1/transfer/tasks/manual-reviews. Effect: safe_read. Collection response: items stay in data.items and the exact total stays in data.total.",
"description": "Page durable transfer tasks awaiting manual review or retry recovery. Method: GET. Path: /api/v1/transfer/tasks/manual-reviews. Effect: safe_read. Collection response: items stay in data.items and the exact total stays in data.total. For a count-only request, use the smallest documented page and read that total; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "transfer.manual_reviews",
@@ -12898,7 +12898,7 @@
},
{
"additionalProperties": false,
"description": "List items waiting in the file-transfer queue. Method: GET. Path: /api/v1/transfer/queue. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "List items waiting in the file-transfer queue. Method: GET. Path: /api/v1/transfer/queue. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "transfer.queue",
@@ -12996,7 +12996,7 @@
},
{
"additionalProperties": false,
"description": "List built-in workflow action definitions and their parameter contracts. Method: GET. Path: /api/v1/workflow/actions. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "List built-in workflow action definitions and their parameter contracts. Method: GET. Path: /api/v1/workflow/actions. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "workflow.actions",
@@ -13105,7 +13105,7 @@
},
{
"additionalProperties": false,
"description": "List event types that can trigger workflows. Method: GET. Path: /api/v1/workflow/event_types. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "List event types that can trigger workflows. Method: GET. Path: /api/v1/workflow/event_types. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "workflow.event_types",
@@ -13214,7 +13214,7 @@
},
{
"additionalProperties": false,
"description": "List configured workflows and their execution state. Method: GET. Path: /api/v1/workflow/agent. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "List configured workflows and their execution state. Method: GET. Path: /api/v1/workflow/agent. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "workflow.list",
@@ -13340,7 +13340,7 @@
},
{
"additionalProperties": false,
"description": "List workflow actions contributed by installed plugins, optionally filtered by plugin ID. Method: GET. Path: /api/v1/workflow/plugin/actions. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count.",
"description": "List workflow actions contributed by installed plugins, optionally filtered by plugin ID. Method: GET. Path: /api/v1/workflow/plugin/actions. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.",
"properties": {
"operation_id": {
"const": "workflow.plugin.actions",
+9 -3
View File
@@ -1003,19 +1003,25 @@ def _collection_response_guidance(contract: Mapping[str, Any]) -> str:
if contract.get("body_shape") == "page_object":
return (
f" Collection response: items stay in {contract['items_field']} and the exact total "
f"stays in {contract['total_count_field']}."
f"stays in {contract['total_count_field']}. For a count-only request, use the smallest "
"documented page and read that total; do not query the database merely because item "
"data is truncated."
)
if contract.get("total_count_field"):
if contract.get("default_pagination") != "unpaginated":
return (
" Collection response: data remains a list and the endpoint's documented "
"pagination or limit defaults remain in effect. Successful gateway output adds "
"collection.result_count and the exact collection.total_count."
"collection.result_count and the exact collection.total_count. For a count-only "
"request, use the smallest valid page and read collection.total_count; do not "
"query the database merely because item data is truncated."
)
return (
" Collection response: data remains a list; omit both page and count to preserve the "
"legacy complete result. Successful gateway output adds collection.result_count and "
"the exact collection.total_count."
"the exact collection.total_count. For a count or summary, send page=1 and count=1, "
"then read collection.total_count; do not query the database merely because item data "
"is truncated."
)
return (
" Collection response: data remains a list and successful gateway output adds "
+1
View File
@@ -66,6 +66,7 @@ You act as a proactive agent. Your goal is to fully resolve the user's media-rel
- If `media.search` fails, fall back to `search_web` or `media.recognize`. Only ask the user when automated paths are exhausted.
- If torrent search yields no useful result, check site scope, site health, and recognition quality before concluding that the resource is unavailable.
- Reuse the latest torrent search cache through `search.results` before `download.add` instead of repeating the same search unnecessarily.
- For a MoviePilot collection count or summary, use the corresponding `moviepilot_api` list operation with the smallest documented page; optional legacy pagination should use `page=1,count=1`. Read the exact `collection.total_count` or existing `data.total`. Do not query the database merely because list items or a tool preview were truncated. If the operation does not expose a total, continue its native pagination or report that the upstream total is unavailable instead of guessing.
- For administrator code discovery across local files, use `execute_command(action="run")` with `rg` and narrow globs or paths; large searches may be split with narrower globs, paths, or `rg --files` filters. Use `moviepilot_api` operation `storage.list` for one known local or remote storage directory, with its paging fields when more than the first page is needed. Always use `read_skill`, never `read_file`, to load a skill's SKILL.md; `read_skill` returns up to 512 KiB of the skill body and the relative paths of its supporting files in one call. If the body is truncated, do not use `read_file` to bypass the limit. Use `read_file` when an exact non-SKILL.md local file is known, including a supporting file listed by `read_skill`. If `read_file` reports truncation, continue with smaller `start_line` and `end_line` ranges instead of assuming the file ended.
- Read the relevant file before changing it, then pick the editing tool by scope. Use `apply_patch` when one logical change spans multiple files, adds new files, or deletes files: submit a single patch wrapped in `*** Begin Patch` / `*** End Patch` with `*** Add File:`, `*** Update File:`, and `*** Delete File:` sections; every context and removed line must match the current content exactly, and the whole patch is validated before any file is written. Use `edit_file` for a single localized exact replacement within one already-read file; make `old_text` unique with enough surrounding context, and use `replace_all=true` only when every match must change. Use `write_file` for one standalone new file; set `overwrite=true` only for an intentional full rewrite, and use `read_file(include_metadata=true)` plus `expected_sha256` when preserving the previously read version matters.
- When implementation depends on a Python or Node.js API, first identify the installed or locked dependency version from environment metadata, requirements, package manifests, lockfiles, local source, and type declarations. Use `rg` against the relevant package directory, `.venv`, or `node_modules` instead of scanning the entire project without bounds. If local evidence is insufficient, use `search_web` and then `browse_webpage` to read the matching version of the official documentation. Do not guess signatures from memory, mix examples from incompatible versions, or install a package only to inspect its API.
+4 -2
View File
@@ -72,8 +72,10 @@ class MoviePilotApiTool(MoviePilotTool):
]
description: str = (
"Call allowlisted MoviePilot business APIs. Use the domain Skill to select operation_id, "
"parameters, and failure handling. External MCP tools/list exposes one complete oneOf "
"branch per operation. Arbitrary URLs, commands, and authentication endpoints are forbidden."
"parameters, and failure handling. For collection counts, use the smallest documented "
"page and read collection.total_count instead of querying the database after item "
"truncation. External MCP tools/list exposes one complete oneOf branch per operation. "
"Arbitrary URLs, commands, and authentication endpoints are forbidden."
)
require_admin: bool = False
args_schema: Type[BaseModel] = MoviePilotApiInput
+10 -1
View File
@@ -41,7 +41,7 @@
| MCP/HTTP 工具管理 | 存在旧业务工具与同名 first-wins 选择空间 | 与主 Agent 共用严格唯一新目录;重名直接以 TOOL_IDENTITY_AMBIGUOUS 失败 |
| 退役代码 | 旧实现仍位于 app/agent/tools/impl | 77 个退役文件已直接删除,其中 72 个工具模块、5 个辅助模块 |
| 架构图 | 982 个宿主模块、8,430 条内部依赖边 | 919 个宿主模块、7,688 条内部依赖边,Application/Chain 具体 Adapter 直连仍为 0 |
| 工作区状态 | 基线提交 871632af,与 origin/v3 对齐,初始工作区干净 | L1-L9 已完成;L10 正在把上一轮响应层兼容分页改为端点显式输入和数据库查询下推,当前基线 `91e33277f``origin/v3` 对齐 |
| 工作区状态 | 基线提交 871632af,与 origin/v3 对齐,初始工作区干净 | L1-L10 已完成并通过远端 CI;2026-09-01 继续修复集合总数在大结果截断预览中不可见的问题 |
## 3. 目标工具分层
@@ -357,4 +357,13 @@ action,并使用 MoviePilot 已配置的具体服务实例访问其自身 API
- L10 正式完成:集合端点显式声明 `page/count`,数据库查询在 SQL 层完成筛选、稳定排序、分页与同条件精确计数;不传新增参数继续返回旧全量列表,外部来源没有总数时不伪造
- MoviePilot Agent 工具体系重构父目标至此全部完成,后续查询端点必须继续维护 REST、OpenAPI、MCP、Skill 和数据库查询合同的一致性
### 2026-09-01:集合总数截断可见性修复
- Web Agent 实际调用 `subscription.list` 时未传分页,完整订阅列表超过通用工具结果 64KB 上限;此前网关把 `collection` 追加在 `data` 之后,截断预览只保留开头条目,导致 Agent 看不到已经由 API 返回的精确 `collection.total_count`,继而错误回退到数据库统计
- 网关现将 `collection` 放在 `data` 前面,使 `X-Total-Count` 投影的精确总数在大结果预览中仍优先可见;列表响应和 REST `data` 结构均未改变
- 核心提示、MCP oneOf、`moviepilot-api` Skill 和文档统一要求:数量或摘要查询使用最小分页窗口;兼容分页传 `page=1,count=1` 并读取 `collection.total_count`,不得因为条目或工具预览截断而改查数据库
- `moviepilot-api` Skill 版本提升到 24;新增超大订阅结果回归,验证截断预览中总数早于 `data` 出现
- 完整锁定测试已通过:4 个分片分别为 `1620 passed, 3 skipped``1860 passed, 4 skipped``1917 passed``2250 passed, 2 skipped`,合计 `7647 passed, 9 skipped`;变更文件 Pylint 为 `10.00/10`Ruff、生成物幂等与 `git diff --check` 均通过
- 当前未托管后端使用的 API 身份与工作区 CLI 配置不一致,真实 CLI 调用被既有 API key 校验拒绝;本轮未擅自重启正在运行的服务,运行进程需在代码部署并重载后再做在线会话复验
本文件作为本次重构的持续记录,保留阶段状态、实际变更、验证结果、提交状态与已知基线边界。
+1
View File
@@ -99,6 +99,7 @@ operation ID、权限、副作用、确认、恢复、结果敏感性及精确
- 数据库列表在查询层先应用授权范围和业务筛选,再执行稳定排序、`LIMIT/OFFSET` 和同条件精确 `COUNT`;不得先全表加载、响应后切片。纯内存、配置、缓存、文件系统或运行时列表可以在序列化边界切片。已有 `max_results` 等原生限量参数的接口继续保留其旧默认值,显式 `page/count` 的优先级由端点合同说明。
- REST 响应的 `data` 保持原列表结构,不改成 `{items,total}``X-Result-Count` 报告本次实际返回数量;仅当 MoviePilot 已经取得完整筛选结果时,才增加精确的 `X-Total-Count`。原有结构化分页接口继续在既有 `data.total``data.items` / `data.list` 中返回总数。
- `moviepilot_api` 把这些响应头投影为响应中的附加 `collection` 对象:`result_count` 为本次返回数量,`total_count` 仅在精确可知时出现,`page` / `count` 在可用时出现。`collection` 是附加元数据,不替换或改写 `data`
- Agent 仅查询数量或摘要时,应对支持精确总数的列表发送最小窗口;兼容分页接口使用 `page=1,count=1`,然后直接读取 `collection.total_count`。即使列表内容触发 64KB 工具预览截断,网关也会把 `collection` 放在 `data` 前面,确保总数仍可见;不得因为条目被截断就回退到数据库统计。
- 已经由第三方接口原生分页或限量、但上游没有返回总数的查询不会伪造 `total_count`Agent 应以 `result_count` 判断当前页是否为空,并按原接口的分页参数继续读取。
### `downloader_operation` / `mediaserver_operation` 调用形状
+8 -2
View File
@@ -266,6 +266,7 @@ def _render_api_docs() -> str:
"The operations, HTTP methods, routes, and path/query/body fields below exactly match external MCP `tools/list`.",
"A field name ending in `*` is required. Omit an empty bucket or send `{}`. Referenced body models are expanded below.",
"For collection operations, `data` keeps its existing list or page-object shape. The gateway may add a sibling `collection` object with `result_count`, optional exact `total_count`, `page`, and `count`; it never replaces the list body with a new wrapper.",
"When a collection contract exposes an exact total, answer count or summary requests from that API metadata. For optional legacy pagination, send `page=1,count=1` and read `collection.total_count`; never query the database merely because item data or a tool preview was truncated.",
"If an endpoint or external source does not expose a total, `collection.total_count` is omitted instead of being guessed from the current page.",
"",
]
@@ -298,13 +299,18 @@ def _render_api_docs() -> str:
lines.append(
"- `response`: `data` remains a list; omitting both `page` and `count` "
"keeps the complete legacy result. `collection.result_count` reports the "
"returned items and `collection.total_count` reports the exact pre-pagination total."
"returned items and `collection.total_count` reports the exact pre-pagination "
"total. For counts or summaries, send `page=1,count=1`, read "
"`collection.total_count`, and do not fall back to a database query because "
"the item preview was truncated."
)
else:
lines.append(
"- `response`: `data` remains a list and the endpoint's documented pagination "
"or limit defaults remain in effect. `collection.result_count` reports the "
"returned items and `collection.total_count` reports the exact total."
"returned items and `collection.total_count` reports the exact total. For a "
"count-only request, use the smallest valid page and read that metadata instead "
"of querying the database after item truncation."
)
else:
lines.append(
+60 -41
View File
@@ -1,6 +1,6 @@
---
name: moviepilot-api
version: 23
version: 24
description: >-
Use this skill for MoviePilot product operations such as media search, torrent
search, downloads, subscriptions, library checks, sites, storage, workflows,
@@ -125,6 +125,24 @@ Call the gateway with this shape:
- Treat `success=false`, HTTP error data, empty results, and validation errors as
real outcomes. Do not claim success without checking the response.
## Collection Counts And Pagination
- For list inspection, explicitly send the operation's documented pagination
fields instead of requesting an unbounded legacy result. For optional legacy
pagination, start with `query={"page":1,"count":20}`.
- For a count or summary request when the operation documents an exact total,
send `query={"page":1,"count":1}` and read `collection.total_count`. This is
the authoritative count after the endpoint's authorization scope and filters.
- A large item list or `tool_result_truncated=true` does not make the total
unavailable. The gateway places `collection` before `data`, so its exact
metadata remains visible in the bounded preview. Never query the MoviePilot
database merely to recover a total already declared by the API contract.
- Use `database-operation` only for administrator diagnostics or aggregations
that the business API cannot express. Do not use it as a fallback for an API
list count. If an operation explicitly omits `collection.total_count`, do not
infer a total from one page; continue its native pagination or state that the
upstream total is unavailable.
## Music Navigation
- Search titles, albums, or artists with `media.search` using `type=music`. Preserve
@@ -150,6 +168,7 @@ Call the gateway with this shape:
The operations, HTTP methods, routes, and path/query/body fields below exactly match external MCP `tools/list`.
A field name ending in `*` is required. Omit an empty bucket or send `{}`. Referenced body models are expanded below.
For collection operations, `data` keeps its existing list or page-object shape. The gateway may add a sibling `collection` object with `result_count`, optional exact `total_count`, `page`, and `count`; it never replaces the list body with a new wrapper.
When a collection contract exposes an exact total, answer count or summary requests from that API metadata. For optional legacy pagination, send `page=1,count=1` and read `collection.total_count`; never query the database merely because item data or a tool preview was truncated.
If an endpoint or external source does not expose a total, `collection.total_count` is omitted instead of being guessed from the current page.
### `config.identifiers.get`
@@ -225,7 +244,7 @@ Purpose: Read current MoviePilot process and host memory utilization.
### `dashboard.network`
`GET /api/v1/dashboard/network`; policy effect: `safe_read`.
Purpose: Read the current host network receive and transmit counters.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: none
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.
- `body`: none
@@ -233,7 +252,7 @@ Purpose: Read the current host network receive and transmit counters.
### `dashboard.processes`
`GET /api/v1/dashboard/processes`; policy effect: `safe_read`.
Purpose: List host processes visible to the MoviePilot runtime.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: none
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.
- `body`: none
@@ -255,7 +274,7 @@ Purpose: Read MoviePilot host, runtime, platform, and uptime summary information
### `dashboard.transfer.statistics`
`GET /api/v1/dashboard/transfer`; policy effect: `safe_read`.
Purpose: Read aggregate file-transfer counts grouped by time period.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: none
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `days` (integer|null; default `7`): Recommendation time window in days.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.
- `body`: none
@@ -277,7 +296,7 @@ Purpose: Delete one exact managed database backup artifact.
### `database.backups.list`
`GET /api/v1/system/database/backups`; policy effect: `safe_read`.
Purpose: List managed database backup artifacts without exposing host paths.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: none
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.
- `body`: none
@@ -299,7 +318,7 @@ Purpose: Submit one torrent to MoviePilot's normal download workflow.
### `download.clients`
`GET /api/v1/download/clients`; policy effect: `safe_read`.
Purpose: List enabled downloader instance names and provider types without credentials.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: none
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.
- `body`: none
@@ -314,7 +333,7 @@ Purpose: Delete one MoviePilot download-history record.
### `download.history.list`
`GET /api/v1/history/download`; policy effect: `safe_read`.
Purpose: Page MoviePilot download-history records in reverse chronological order.
- `response`: `data` remains a list and the endpoint's documented pagination or limit defaults remain in effect. `collection.result_count` reports the returned items and `collection.total_count` reports the exact total.
- `response`: `data` remains a list and the endpoint's documented pagination or limit defaults remain in effect. `collection.result_count` reports the returned items and `collection.total_count` reports the exact total. For a count-only request, use the smallest valid page and read that metadata instead of querying the database after item truncation.
- `path_params`: none
- `query`: `count` (integer|null; default `30`): Maximum number of records to return on the requested page.; `page` (integer|null; default `1`): One-based result page number.
- `body`: none
@@ -322,7 +341,7 @@ Purpose: Page MoviePilot download-history records in reverse chronological order
### `download.paths`
`GET /api/v1/download/paths`; policy effect: `safe_read`.
Purpose: List configured downloader save-path URIs that may be passed to download.add.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: none
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.
- `body`: none
@@ -330,7 +349,7 @@ Purpose: List configured downloader save-path URIs that may be passed to downloa
### `download.tasks.active`
`GET /api/v1/download/`; policy effect: `safe_read`.
Purpose: List currently downloading MoviePilot tasks with their canonical media context.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: none
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `name` (string|null): Human-readable name of the site, storage item, subscription, or rule group.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.
- `body`: none
@@ -451,7 +470,7 @@ Purpose: Read canonical media details from one selected metadata source.
### `media.episode_group.seasons`
`GET /api/v1/media/group/seasons/{episode_group}`; policy effect: `safe_read`.
Purpose: List seasons defined by one exact TMDB episode-group identity.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: `episode_group*` (string): TMDB episode-group identifier used for alternate episode ordering.
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.
- `body`: none
@@ -459,7 +478,7 @@ Purpose: List seasons defined by one exact TMDB episode-group identity.
### `media.episode_groups`
`GET /api/v1/media/groups/{tmdbid}`; policy effect: `safe_read`.
Purpose: List alternate TMDB episode groups available for one TV media identity.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: `tmdbid*` (integer): TMDB media ID returned by media search or detail.
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.
- `body`: none
@@ -467,7 +486,7 @@ Purpose: List alternate TMDB episode groups available for one TV media identity.
### `media.episode_schedule`
`GET /api/v1/tmdb/{tmdbid}/{season}`; policy effect: `safe_read`.
Purpose: Read TMDB episode release information for one season.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: `season*` (integer): Season number used by the media, search, subscription, or transfer operation.; `tmdbid*` (integer): TMDB media ID returned by media search or detail.
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `episode_group` (string|null): TMDB episode-group identifier used for alternate episode ordering.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.
- `body`: none
@@ -520,7 +539,7 @@ Purpose: Search canonical media across selected metadata sources.
### `media.seasons`
`GET /api/v1/media/seasons`; policy effect: `safe_read`.
Purpose: List seasons for one exact media identity or a title-and-year fallback.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: none
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `media_id` (string|null): Source-native media ID. Always pair it with the exact media_source returned by search.; `media_source` (MediaSource|null): Metadata source identifier. Preserve the exact value returned with media_id.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.; `season` (integer): Season number used by the media, search, subscription, or transfer operation.; `title` (string|null): Media, torrent, subscription, or history title used by the operation.; `year` (string): Release or premiere year used to disambiguate the media title.
- `body`: none
@@ -528,7 +547,7 @@ Purpose: List seasons for one exact media identity or a title-and-year fallback.
### `media.sources`
`GET /api/v1/media/source`; policy effect: `safe_read`.
Purpose: List metadata sources currently registered for MoviePilot media operations.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: none
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.
- `body`: none
@@ -694,7 +713,7 @@ Purpose: Install or update one plugin from an approved source.
### `plugin.installed`
`GET /api/v1/plugin/`; policy effect: `safe_read`.
Purpose: List installed plugins and their runtime status.
- `response`: `data` remains a list and the endpoint's documented pagination or limit defaults remain in effect. `collection.result_count` reports the returned items and `collection.total_count` reports the exact total.
- `response`: `data` remains a list and the endpoint's documented pagination or limit defaults remain in effect. `collection.result_count` reports the returned items and `collection.total_count` reports the exact total. For a count-only request, use the smallest valid page and read that metadata instead of querying the database after item truncation.
- `path_params`: none
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `force` (boolean; default `False`): Force a marketplace refresh or plugin installation when true.; `max_results` (integer; default `50`; minimum `1`; maximum `200`): Maximum number of plugin catalog results to return, from 1 to 200.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.; `query` (string|null): Optional case-insensitive keyword matched against plugin ID, name, description, and author.; `state*` (string=installed): Literal installed, selecting only installed plugin catalog entries.
- `body`: none
@@ -702,7 +721,7 @@ Purpose: List installed plugins and their runtime status.
### `plugin.market`
`GET /api/v1/plugin/`; policy effect: `safe_read`.
Purpose: List plugins available from configured marketplaces.
- `response`: `data` remains a list and the endpoint's documented pagination or limit defaults remain in effect. `collection.result_count` reports the returned items and `collection.total_count` reports the exact total.
- `response`: `data` remains a list and the endpoint's documented pagination or limit defaults remain in effect. `collection.result_count` reports the returned items and `collection.total_count` reports the exact total. For a count-only request, use the smallest valid page and read that metadata instead of querying the database after item truncation.
- `path_params`: none
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `force` (boolean; default `False`): Force a marketplace refresh or plugin installation when true.; `max_results` (integer; default `50`; minimum `1`; maximum `200`): Maximum number of plugin catalog results to return, from 1 to 200.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.; `query` (string|null): Optional case-insensitive keyword matched against plugin ID, name, description, and author.; `state*` (string=market): Literal market, selecting only market plugin catalog entries.
- `body`: none
@@ -809,7 +828,7 @@ Purpose: Read personalized media or music recommendations.
### `scheduler.list`
`GET /api/v1/dashboard/schedule`; policy effect: `safe_read`.
Purpose: List registered scheduler jobs and their current state.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: none
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.
- `body`: none
@@ -853,7 +872,7 @@ Purpose: Search torrent sites directly from a free-form title and optional media
### `search.torrents`
`GET /api/v1/search/media/{media_id}`; policy effect: `safe_read`.
Purpose: Search torrent sites for one canonical media identity.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: `media_id*` (string): Source-native media ID. Always pair it with the exact media_source returned by search.
- `query`: `area` (string|null; default `title`): Optional region filter applied by the torrent search workflow.; `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `media_source*` (MediaSource): Metadata source identifier. Preserve the exact value returned with media_id.; `mtype` (string|null): MoviePilot media type or subscription-history category required by the operation.; `music_type` (string|null): Music identity level: recording, album, or artist where supported.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.; `season` (string|null): Season number used by the media, search, subscription, or transfer operation.; `sites` (string|null): Exact site IDs included in the search or subscription scope.
- `body`: none
@@ -882,7 +901,7 @@ Purpose: Authenticate a supported site account and persist the resulting site au
### `site.category`
`GET /api/v1/site/category/{site_id}`; policy effect: `safe_read`.
Purpose: List torrent categories supported by one configured site.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: `site_id*` (integer): Persistent site ID returned by site.list.
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.
- `body`: none
@@ -911,7 +930,7 @@ Purpose: Delete one configured site by persistent site ID.
### `site.list`
`GET /api/v1/site/agent`; policy effect: `safe_read`.
Purpose: List configured sites with status/name filters; authentication fields are returned only to a superuser.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: none
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `name` (string|null): Human-readable name of the site, storage item, subscription, or rule group.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.; `status` (string(active,inactive,all); default `all`): Transfer success status used to filter history or describe a record.
- `body`: none
@@ -948,7 +967,7 @@ Purpose: Browse torrent resources from one configured site with category and key
### `site.rss`
`GET /api/v1/site/rss`; policy effect: `safe_read`.
Purpose: List configured sites selected for RSS subscription processing.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: none
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.
- `body`: none
@@ -956,7 +975,7 @@ Purpose: List configured sites selected for RSS subscription processing.
### `site.searchable`
`GET /api/v1/site/media/{media_type}`; policy effect: `safe_read`.
Purpose: List active configured sites supporting one exact media type.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: `media_type*` (string): MoviePilot media type used to filter recommendations or rule groups.
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.
- `body`: none
@@ -971,7 +990,7 @@ Purpose: Read account and traffic statistics for one exact configured site domai
### `site.statistics`
`GET /api/v1/site/statistic`; policy effect: `safe_read`.
Purpose: Read the latest account and traffic statistics for all configured sites.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: none
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.
- `body`: none
@@ -1000,7 +1019,7 @@ Purpose: Update one configured site's complete settings.
### `site.userdata`
`GET /api/v1/site/userdata/{site_id}`; policy effect: `safe_read`.
Purpose: Read the latest account statistics collected from one site.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: `site_id*` (integer): Persistent site ID returned by site.list.
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.; `workdate` (string|null): Date used when retrieving one site's historical user statistics.
- `body`: none
@@ -1008,7 +1027,7 @@ Purpose: Read the latest account statistics collected from one site.
### `site.userdata.latest`
`GET /api/v1/site/userdata/latest`; policy effect: `safe_read`.
Purpose: Read the latest collected account statistics for every configured site.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: none
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.
- `body`: none
@@ -1023,7 +1042,7 @@ Purpose: Refresh and return account statistics for one configured site.
### `slash.list`
`GET /api/v1/message/agent/commands`; policy effect: `safe_read`.
Purpose: List slash commands that the Agent may dispatch.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: none
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.
- `body`: none
@@ -1045,7 +1064,7 @@ Purpose: Delete one exact file or directory from a configured storage provider.
### `storage.list`
`POST /api/v1/storage/agent/list`; policy effect: `safe_read`.
Purpose: List files or directories from one configured storage location.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: none
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `keyword` (string|null): Case-insensitive substring used to discover settings or filter storage entries.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.; `sort` (string|null; default `updated_at`): Storage-list sort field or ordering expression.
- `body`: `basename` (string|null): Base filename without its parent path.; `children` (array<FileItem-Input>|null): Child storage items nested below this item.; `drive_id` (string|null): Provider-native storage drive identifier.; `extension` (string|null): Filename extension, including or excluding the leading dot as returned by storage.; `fileid` (string|null): Provider-native storage item identifier.; `modify_time` (number|null): Storage item modification timestamp.; `name` (string|null): Human-readable name of the site, storage item, subscription, or rule group.; `parent_fileid` (string|null): Provider-native identifier of the parent storage directory.; `path` (string|null; default `/`): Storage or history path represented by this record.; `pickcode` (string|null): 115 storage pickcode associated with the item.; `size` (integer|null): File or torrent size in bytes.; `storage` (string|null; default `local`): Configured storage name or storage type used by the operation.; `thumbnail` (string|null): Thumbnail URL returned by the storage provider.; `type` (string|null): MoviePilot media or storage item type required by the selected operation.; `url` (string|null): Site, storage, or torrent URL represented by this field.
@@ -1074,7 +1093,7 @@ Purpose: Rename one exact storage item, optionally applying media-aware recursiv
### `storage.settings`
`GET /api/v1/storage/directories`; policy effect: `safe_read`.
Purpose: Read configured directory or storage settings.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: none
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `directory_type` (string; default `all`): Directory configuration subtype to return.; `name` (string|null): Human-readable name of the site, storage item, subscription, or rule group.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.; `storage_type` (string; default `all`): Configured storage provider type to return.
- `body`: none
@@ -1131,7 +1150,7 @@ Purpose: Stop following one subscription-sharing user by exact share user ID.
### `subscription.follow.list`
`GET /api/v1/subscribe/follow`; policy effect: `safe_read`.
Purpose: List subscription-sharing user IDs followed by the current user.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: none
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.
- `body`: none
@@ -1153,7 +1172,7 @@ Purpose: Read one accessible subscription by persistent subscription ID.
### `subscription.history`
`GET /api/v1/subscribe/history/{mtype}`; policy effect: `safe_read`.
Purpose: List completed or archived subscription records.
- `response`: `data` remains a list and the endpoint's documented pagination or limit defaults remain in effect. `collection.result_count` reports the returned items and `collection.total_count` reports the exact total.
- `response`: `data` remains a list and the endpoint's documented pagination or limit defaults remain in effect. `collection.result_count` reports the returned items and `collection.total_count` reports the exact total. For a count-only request, use the smallest valid page and read that metadata instead of querying the database after item truncation.
- `path_params`: `mtype*` (string): MoviePilot media type or subscription-history category required by the operation.
- `query`: `count` (integer|null; default `30`): Maximum number of records to return on the requested page.; `page` (integer|null; default `1`): One-based result page number.
- `body`: none
@@ -1168,7 +1187,7 @@ Purpose: Delete one accessible subscription-history record.
### `subscription.list`
`GET /api/v1/subscribe/`; policy effect: `safe_read`.
Purpose: List active subscriptions.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: none
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.
- `body`: none
@@ -1233,7 +1252,7 @@ Purpose: Delete one shared-subscription publication by share ID.
### `subscription.share.statistics`
`GET /api/v1/subscribe/share/statistics`; policy effect: `safe_read`.
Purpose: Read aggregate contribution and reuse counts for subscription sharers.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: none
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.
- `body`: none
@@ -1263,7 +1282,7 @@ Purpose: Update one existing movie, TV, or music subscription.
### `subscription.user.list`
`GET /api/v1/subscribe/user/{username}`; policy effect: `safe_read`.
Purpose: List public subscriptions owned by one accessible MoviePilot username.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: `username*` (string): MoviePilot or site username required by the selected operation.
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.
- `body`: none
@@ -1271,7 +1290,7 @@ Purpose: List public subscriptions owned by one accessible MoviePilot username.
### `subtitle.search.media`
`GET /api/v1/search/subtitle/media/{media_id}`; policy effect: `external_side_effect`.
Purpose: Search subtitle providers for one canonical media identity and optional season or episode.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: `media_id*` (string): Source-native media ID. Always pair it with the exact media_source returned by search.
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `episode` (string|null): Episode number used to narrow a subtitle or media search.; `media_source*` (MediaSource): Metadata source identifier. Preserve the exact value returned with media_id.; `mtype` (string|null): MoviePilot media type or subscription-history category required by the operation.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.; `season` (string|null): Season number used by the media, search, subscription, or transfer operation.; `sites` (string|null): Exact site IDs included in the search or subscription scope.
- `body`: none
@@ -1301,7 +1320,7 @@ Purpose: Run the built-in availability test for one loaded MoviePilot module.
### `system.network.targets`
`GET /api/v1/system/nettest/targets`; policy effect: `safe_read`.
Purpose: List approved built-in network-test targets without exposing their request URLs.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: none
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.
- `body`: none
@@ -1365,7 +1384,7 @@ Purpose: Read the installation version and runtime usage report available to the
### `system.versions`
`GET /api/v1/system/versions`; policy effect: `safe_read`.
Purpose: List available MoviePilot GitHub releases.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: none
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.
- `body`: none
@@ -1494,7 +1513,7 @@ Purpose: Preview the organized destination name for one source path and media id
### `transfer.queue`
`GET /api/v1/transfer/queue`; policy effect: `safe_read`.
Purpose: List items waiting in the file-transfer queue.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: none
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.
- `body`: none
@@ -1516,7 +1535,7 @@ Purpose: Resolve the configured transfer destination for supplied source storage
### `workflow.actions`
`GET /api/v1/workflow/actions`; policy effect: `safe_read`.
Purpose: List built-in workflow action definitions and their parameter contracts.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: none
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.
- `body`: none
@@ -1538,7 +1557,7 @@ Purpose: Delete one configured workflow by persistent workflow ID.
### `workflow.event_types`
`GET /api/v1/workflow/event_types`; policy effect: `safe_read`.
Purpose: List event types that can trigger workflows.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: none
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.
- `body`: none
@@ -1560,7 +1579,7 @@ Purpose: Read one complete configured workflow definition.
### `workflow.list`
`GET /api/v1/workflow/agent`; policy effect: `safe_read`.
Purpose: List configured workflows and their execution state.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: none
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `name` (string|null): Human-readable name of the site, storage item, subscription, or rule group.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.; `state` (string(W,R,P,S,F,all); default `all`): Current site, subscription, marketplace, or transfer state filter.; `trigger_type` (string(timer,event,manual,all); default `all`): Workflow trigger filter: timer, event, manual, or all.
- `body`: none
@@ -1575,7 +1594,7 @@ Purpose: Disable automatic execution of one configured workflow.
### `workflow.plugin.actions`
`GET /api/v1/workflow/plugin/actions`; policy effect: `safe_read`.
Purpose: List workflow actions contributed by installed plugins, optionally filtered by plugin ID.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total.
- `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated.
- `path_params`: none
- `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.; `plugin_id` (string): Exact installed or marketplace plugin ID.
- `body`: none
+41
View File
@@ -4,6 +4,7 @@ from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock, patch
from app.agent.api.executor import ApiExecutionContext, MoviePilotApiExecutor
from app.agent.tools.base import format_tool_result_for_agent
def _execute_with_headers(headers: dict[str, str]) -> tuple[dict, AsyncMock]:
@@ -77,3 +78,43 @@ def test_executor_keeps_non_collection_payload_unchanged_without_headers() -> No
assert result == {"success": True, "message": "", "data": [{"id": 1}]}
close.assert_awaited_once()
def test_executor_keeps_collection_total_visible_in_truncated_tool_preview() -> None:
"""列表内容过大时,精确总数必须位于截断预览开头供 Agent 直接使用。"""
response = SimpleNamespace(
status_code=200,
headers={"X-Result-Count": "200", "X-Total-Count": "357"},
json=lambda: {
"success": True,
"message": "",
"data": [{"title": "x" * 1024} for _ in range(100)],
},
aclose=AsyncMock(),
)
request = AsyncMock(return_value=response)
request_factory = MagicMock(return_value=SimpleNamespace(request=request))
executor = MoviePilotApiExecutor(
context=ApiExecutionContext(
user_id="1",
username="admin",
is_admin=True,
),
request_factory=request_factory,
)
with patch("app.agent.api.executor.create_access_token", return_value="token"):
raw_result = asyncio.run(executor.execute("subscription.list"))
result = json.loads(
format_tool_result_for_agent(raw_result, tool_name="moviepilot_api")
)
assert result["tool_result_truncated"] is True
assert result["content_preview"].startswith(
'{"collection": {"result_count": 200, "total_count": 357}'
)
assert result["content_preview"].index('"total_count": 357') < result[
"content_preview"
].index('"data"')
response.aclose.assert_awaited_once()
+2
View File
@@ -137,6 +137,8 @@ def test_mcp_collection_contract_distinguishes_exact_and_unavailable_totals() ->
"total_count_field": "collection.total_count",
"default_pagination": "unpaginated",
}
assert "page=1 and count=1" in subscription["description"]
assert "do not query the database" in subscription["description"]
storage = branches["storage.list"]
assert {"page", "count"}.issubset(storage["properties"]["query"]["properties"])
+13 -1
View File
@@ -110,7 +110,7 @@ def test_modified_builtin_skills_have_incremented_versions() -> None:
"command-dispatch": "2",
"database-operation": "6",
"feedback-issue": "9",
"moviepilot-api": "23",
"moviepilot-api": "24",
"moviepilot-update": "5",
"organize-files": "5",
"transfer-failed-retry": "5",
@@ -144,6 +144,18 @@ def test_core_prompt_requires_read_skill_for_skill_documents() -> None:
assert "do not use `read_file` to bypass the limit" in core_prompt
def test_api_collection_counts_must_use_gateway_metadata_before_database() -> None:
"""核心提示与 API Skill 必须阻止列表截断后错误回退数据库统计。"""
core_prompt = CORE_PROMPT_PATH.read_text(encoding="utf-8")
api_skill = _read_skill("moviepilot-api")
assert "optional legacy pagination should use `page=1,count=1`" in core_prompt
assert "Do not query the database merely because" in core_prompt
assert 'send `query={"page":1,"count":1}`' in api_skill
assert "Never query the MoviePilot" in api_skill
assert "database merely to recover a total" in api_skill
def test_every_retired_business_tool_has_a_live_precise_owner() -> None:
"""全部 72 个退出业务工具必须由 API、provider Skill 或统一原生工具承接。"""
from app.agent.policy.api import API_OPERATION_ROUTES