import { z } from "zod"; import { MediaAsset } from "./assets.ts"; import { CAPABILITY_CATALOG, type CapabilitySpec, type KnownCapabilityId } from "./capability.ts"; import { Id, JsonObject, NonNegInt } from "./common.ts"; import { MediaEntity, MediaKind } from "./content.ts"; import { AcquisitionPlan, DownloadStatus, DownloadTask, Task, TaskStatus } from "./execution.ts"; import { ExternalEntityType, ExternalIdentity } from "./identity.ts"; import { ServerMapping } from "./ops.ts"; import { OrganizationAction, OrganizationRecord } from "./organization.ts"; import { AcquisitionSlot, PolicyRevision } from "./policy.ts"; import { SourceRelease } from "./sources.ts"; // 首批能力的输入/输出 schema;名字与 CAPABILITY_CATALOG 中的 inputSchema/outputSchema 一致。 // 写能力的输入只接受计划/对象 ID,不接受模型自拟的幂等键或任意路径。 const Limit = z.number().int().min(1).max(100).default(20); /** 写能力的通用受理结果:返回 task/plan ID,由任务页追踪(REQ §12.3) */ export const Accepted = z.object({ taskId: Id, planId: Id.nullable() }); export type Accepted = z.infer; export const CatalogLookupInput = z .object({ query: z.string().min(1).max(200).optional(), externalId: z.object({ provider: z.string(), entityType: ExternalEntityType, externalId: z.string() }).optional(), kind: MediaKind.optional(), year: z.number().int().optional(), includeRemote: z.boolean().default(false), limit: Limit, }) .refine((v) => !!v.query || !!v.externalId, { message: "query 与 externalId 至少给一个" }); export const CatalogLookupOutput = z.object({ local: z.array(z.object({ entity: MediaEntity, score: z.number(), matchedAlias: z.string().nullable() })), remote: z.array( z.object({ provider: z.string(), entityType: ExternalEntityType, externalId: z.string(), title: z.string(), year: z.number().int().nullable(), }), ), }); export const ReleaseSearchInput = z.object({ query: z.string().min(1).max(200).optional(), mediaEntityId: Id.optional(), siteIds: z.array(Id).optional(), /** 本地快照不足时是否实时补查 */ allowLiveSearch: z.boolean().default(true), limit: Limit, }); export const ReleaseSearchOutput = z.object({ items: z.array(SourceRelease), /** 各站点快照新鲜度与是否实时补查 */ coverage: z.array( z.object({ siteId: Id, fromSnapshot: z.boolean(), live: z.boolean(), error: z.string().nullable() }), ), }); export const ReleaseInspectInput = z.object({ sourceReleaseId: Id, refreshDetail: z.boolean().default(false) }); export const ReleaseInspectOutput = z.object({ release: SourceRelease, inventory: z.array(MediaAsset) }); export const IdentityResolveInput = z .object({ observationId: Id.optional(), sourceReleaseId: Id.optional(), candidate: z .object({ mediaEntityId: Id.optional(), provider: z.string().optional(), entityType: ExternalEntityType.optional(), externalId: z.string().optional(), }) .optional(), evidenceIds: z.array(Id).default([]), }) .refine((v) => !!v.observationId || !!v.sourceReleaseId, { message: "需要 observationId 或 sourceReleaseId" }); export const IdentityResolveOutput = z.object({ status: z.enum(["resolved", "proposed", "ambiguous", "unidentifiable"]), mediaEntityId: Id.nullable(), identities: z.array(ExternalIdentity), decisionId: Id.nullable(), }); export const DownloadPlanInput = z.object({ slotId: Id, sourceReleaseId: Id }); export const DownloadPlanOutput = z.object({ plan: AcquisitionPlan.nullable(), slot: AcquisitionSlot, /** 硬条件不满足或预算/阈值阻止时的原因 */ rejected: z.array(z.object({ code: z.string(), message: z.string() })), requiresApproval: z.boolean(), }); export const DownloadSubmitInput = z.object({ planId: Id, approvalId: Id.optional() }); export const DownloadSubmitOutput = Accepted.extend({ downloadTask: DownloadTask.nullable() }); export const OrganizePlanInput = z .object({ downloadTaskId: Id.optional(), mediaAssetIds: z.array(Id).optional(), action: OrganizationAction.default("copy"), targetLibraryRootId: Id.optional(), }) .refine((v) => !!v.downloadTaskId || !!v.mediaAssetIds?.length, { message: "需要 downloadTaskId 或 mediaAssetIds" }); export const OrganizePlanOutput = z.object({ planId: Id, payloadHash: z.string(), files: z.array( z.object({ sourcePath: z.string(), targetPath: z.string().nullable(), action: OrganizationAction, skipReason: z.string().nullable(), }), ), conflicts: z.array(z.object({ code: z.string(), message: z.string(), path: z.string().nullable() })), requiresApproval: z.boolean(), }); export const OrganizeExecuteInput = z.object({ planId: Id, payloadHash: z.string().min(1), approvalId: Id.optional() }); export const OrganizeExecuteOutput = Accepted.extend({ record: OrganizationRecord.nullable() }); /** 授权内恢复:强制重新校验(missingFiles / error 之后)或恢复暂停;换源、删除、清理不在此能力内 */ export const DownloadRecheckAction = z.enum(["recheck", "resume"]); export const DownloadRecheckInput = z.object({ downloadTaskId: Id, action: DownloadRecheckAction.default("recheck"), reason: z.string().max(500).optional(), }); export const DownloadRecheckOutput = z.object({ downloadTaskId: Id, action: DownloadRecheckAction, statusBefore: DownloadStatus, rawStateBefore: z.string().nullable(), /** 已向下载器发出请求;是否恢复以后续同步到的 download_task 状态为准 */ requested: z.boolean(), }); export const TaskInspectInput = z.object({ taskId: Id }); export const TaskInspectOutput = z.object({ task: Task, children: z.array(Task), timeline: z.array(z.object({ at: z.string(), type: z.string(), summary: z.string(), data: JsonObject.optional() })), }); export const TaskRetryInput = z.object({ taskId: Id, reason: z.string().max(500).optional() }); export const TaskRetryOutput = z.object({ taskId: Id, status: TaskStatus }); export const TaskCancelInput = z.object({ taskId: Id, reason: z.string().min(1).max(500) }); export const TaskCancelOutput = z.object({ taskId: Id, status: TaskStatus, cancelRequested: z.boolean() }); export const PolicyPreviewInput = z.object({ policyRevisionId: Id, sampleLimit: z.number().int().min(1).max(1000).default(200), }); export const PolicyPreviewOutput = z.object({ newlyMatched: NonNegInt, newlyExcluded: NonNegInt, samples: z.array(z.object({ sourceReleaseId: Id, before: z.string(), after: z.string(), reason: z.string() })), }); export const PolicyApplyInput = z.object({ policyRevisionId: Id, expectedRevision: z.number().int().min(1), approvalId: Id.optional(), }); export const PolicyApplyOutput = z.object({ policy: PolicyRevision }); export const InventoryQueryInput = z.object({ mediaEntityId: Id.optional(), query: z.string().max(200).optional(), includeServerInventory: z.boolean().default(false), limit: Limit, }); export const InventoryQueryOutput = z.object({ items: z.array( z.object({ entity: MediaEntity, assets: z.array(MediaAsset), slots: z.array(AcquisitionSlot), identities: z.array(ExternalIdentity.pick({ provider: true, entityType: true, externalId: true, status: true })), /** 外部库存证据;"未知 ≠ 缺失" */ server: z.array(ServerMapping), }), ), }); /** 能力 ID → 输入/输出 schema(server 注册处理器、agent 生成工具定义时共用) */ export const CAPABILITY_IO = { "catalog.lookup": { input: CatalogLookupInput, output: CatalogLookupOutput }, "release.search": { input: ReleaseSearchInput, output: ReleaseSearchOutput }, "release.inspect": { input: ReleaseInspectInput, output: ReleaseInspectOutput }, "identity.resolve": { input: IdentityResolveInput, output: IdentityResolveOutput }, "download.plan": { input: DownloadPlanInput, output: DownloadPlanOutput }, "download.submit": { input: DownloadSubmitInput, output: DownloadSubmitOutput }, "download.recheck": { input: DownloadRecheckInput, output: DownloadRecheckOutput }, "organize.plan": { input: OrganizePlanInput, output: OrganizePlanOutput }, "organize.execute": { input: OrganizeExecuteInput, output: OrganizeExecuteOutput }, "task.inspect": { input: TaskInspectInput, output: TaskInspectOutput }, "task.retry": { input: TaskRetryInput, output: TaskRetryOutput }, "task.cancel": { input: TaskCancelInput, output: TaskCancelOutput }, "policy.preview": { input: PolicyPreviewInput, output: PolicyPreviewOutput }, "policy.apply": { input: PolicyApplyInput, output: PolicyApplyOutput }, "inventory.query": { input: InventoryQueryInput, output: InventoryQueryOutput }, } as const satisfies Record; type CapabilityIo = typeof CAPABILITY_IO; /** 首批能力 id 对应的运行时规格类型(input/output 为该能力的具体 schema) */ export type KnownCapabilitySpec = CapabilitySpec< CapabilityIo[K]["input"], CapabilityIo[K]["output"] >; /** * 首批能力的运行时规格:CAPABILITY_CATALOG 的元数据 + CAPABILITY_IO 的 schema。 * server 各模块注册处理器时一律用它,不要手写描述: * `registry.register(capabilitySpec("release.search"), handler)` */ export function capabilitySpec(id: K): KnownCapabilitySpec { const { inputSchema: _i, outputSchema: _o, ...meta } = CAPABILITY_CATALOG[id]; return { ...meta, inputSchema: CAPABILITY_IO[id].input, outputSchema: CAPABILITY_IO[id].output }; } export type CatalogLookupInput = z.infer; export type CatalogLookupOutput = z.infer; export type ReleaseSearchInput = z.infer; export type ReleaseSearchOutput = z.infer; export type ReleaseInspectInput = z.infer; export type ReleaseInspectOutput = z.infer; export type IdentityResolveInput = z.infer; export type IdentityResolveOutput = z.infer; export type DownloadPlanInput = z.infer; export type DownloadPlanOutput = z.infer; export type DownloadSubmitInput = z.infer; export type DownloadSubmitOutput = z.infer; export type OrganizePlanInput = z.infer; export type OrganizePlanOutput = z.infer; export type OrganizeExecuteInput = z.infer; export type OrganizeExecuteOutput = z.infer; export type TaskInspectInput = z.infer; export type TaskInspectOutput = z.infer; export type DownloadRecheckInput = z.infer; export type DownloadRecheckOutput = z.infer; export type TaskRetryInput = z.infer; export type TaskRetryOutput = z.infer; export type TaskCancelInput = z.infer; export type TaskCancelOutput = z.infer; export type PolicyPreviewInput = z.infer; export type PolicyPreviewOutput = z.infer; export type PolicyApplyInput = z.infer; export type PolicyApplyOutput = z.infer; export type InventoryQueryInput = z.infer; export type InventoryQueryOutput = z.infer;