Files
ttstd-infinite-canvas/canvas-agent/src/dreamina-cli-contract.ts

166 lines
11 KiB
TypeScript

import { z } from "zod";
export const dreaminaGenerationOperations = [
"text2image",
"image2image",
"image_upscale",
"text2video",
"image2video",
"frames2video",
"multiframe2video",
"multimodal2video",
] as const;
const safeId = z.string().min(8).max(160).regex(/^[A-Za-z0-9._:-]+$/);
export const dreaminaSubmitIdSchema = safeId;
const prompt = z.string().min(1).max(20_000);
const referencePath = z.string().min(1).max(2_048);
const imageReferences = z.array(referencePath);
const imageRatio = z.enum(["21:9", "16:9", "4:3", "3:2", "1:1", "2:3", "3:4", "9:16"]);
const videoRatio = z.enum(["21:9", "16:9", "4:3", "1:1", "3:4", "9:16"]);
const imageResolution = z.enum(["1k", "2k", "4k"]);
const videoResolution = z.enum(["480p", "720p", "1080p", "4k"]);
export const dreaminaImageModelSchema = z.enum(["3.0", "3.1", "4.0", "4.1", "4.5", "4.6", "4.7", "5.0", "5.0Pro"]);
const imageEditModel = z.enum(["4.0", "4.1", "4.5", "4.6", "4.7", "5.0", "5.0Pro"]);
const seedanceModel = z.enum(["seedance2.0", "seedance2.0fast", "seedance2.0_vip", "seedance2.0fast_vip", "seedance2.0mini", "seedance2.5"]);
const imageVideoModel = z.enum(["seedance1.0fast", "seedance1.5pro", "seedance2.0", "seedance2.0fast", "seedance2.0_vip", "seedance2.0fast_vip", "seedance2.0mini", "seedance2.5"]);
type DreaminaVideoModel = z.infer<typeof seedanceModel> | z.infer<typeof imageVideoModel>;
type DreaminaVideoModelCapability = {
videoResolutions: readonly z.infer<typeof videoResolution>[];
minDuration: number;
maxDuration: number;
};
const defaultVideoModelCapability: DreaminaVideoModelCapability = {
videoResolutions: ["720p"],
minDuration: 4,
maxDuration: 15,
};
export const dreaminaVideoModelCapabilityOverrides = {
"seedance1.0fast": { videoResolutions: ["720p"], minDuration: 5, maxDuration: 10 },
"seedance1.5pro": { videoResolutions: ["720p"], minDuration: 5, maxDuration: 12 },
"seedance2.0_vip": { videoResolutions: ["720p", "1080p", "4k"], minDuration: 4, maxDuration: 15 },
"seedance2.5": { videoResolutions: ["480p", "720p"], minDuration: 4, maxDuration: 30 },
} as const satisfies Partial<Record<DreaminaVideoModel, DreaminaVideoModelCapability>>;
export const dreaminaGenerationCapabilityMetadata = {
image2image: { maxReferenceImages: 10 },
image2video: { maxReferenceImages: 1 },
frames2video: { maxReferenceImages: 2 },
multiframe2video: { minReferenceImages: 2, maxReferenceImages: 20, minDuration: 1, maxDuration: 8 },
multimodal2video: {
default: { maxReferenceImages: 9, maxReferenceVideos: 3, maxReferenceAudios: 3, maxReferences: 12, requiresVisualReference: true },
"seedance2.5": { maxReferenceImages: 30, maxReferenceVideos: 10, maxReferenceAudios: 10, maxReferences: 50, requiresVisualReference: false },
},
} as const;
const videoDuration = z.number().int().min(defaultVideoModelCapability.minDuration).max(
dreaminaVideoModelCapabilityOverrides["seedance2.5"].maxDuration,
);
export function dreaminaVideoModelCapability(model: string): DreaminaVideoModelCapability {
return dreaminaVideoModelCapabilityOverrides[model as keyof typeof dreaminaVideoModelCapabilityOverrides]
?? defaultVideoModelCapability;
}
export function dreaminaMaxReferenceImages(operation: string, model: string) {
switch (operation) {
case "image2image": return dreaminaGenerationCapabilityMetadata.image2image.maxReferenceImages;
case "image2video": return dreaminaGenerationCapabilityMetadata.image2video.maxReferenceImages;
case "frames2video": return dreaminaGenerationCapabilityMetadata.frames2video.maxReferenceImages;
case "multimodal2video": return dreaminaMultimodalCapability(model).maxReferenceImages;
default: return 0;
}
}
export const dreaminaGenerationSchemas = [
z.object({ operation: z.literal("text2image"), idempotencyKey: safeId, prompt, modelVersion: dreaminaImageModelSchema.optional(), ratio: imageRatio.optional(), resolutionType: imageResolution.optional(), generateNum: z.number().int().min(1).max(10).optional() }).strict(),
z.object({ operation: z.literal("image2image"), idempotencyKey: safeId, prompt, modelVersion: imageEditModel.optional(), ratio: imageRatio.optional(), resolutionType: imageResolution.optional(), generateNum: z.number().int().min(1).max(10).optional(), referenceImages: imageReferences.min(1).max(10) }).strict(),
z.object({ operation: z.literal("image_upscale"), idempotencyKey: safeId, resolutionType: z.enum(["2k", "4k", "8k"]), referenceImages: imageReferences.length(1) }).strict(),
z.object({ operation: z.literal("text2video"), idempotencyKey: safeId, prompt, modelVersion: seedanceModel.optional(), ratio: videoRatio.optional(), videoResolution, duration: videoDuration.optional() }).strict(),
z.object({ operation: z.literal("image2video"), idempotencyKey: safeId, prompt, modelVersion: imageVideoModel.optional(), videoResolution, duration: videoDuration.optional(), referenceImages: imageReferences.length(1) }).strict(),
z.object({ operation: z.literal("frames2video"), idempotencyKey: safeId, prompt, modelVersion: z.enum(["seedance1.5pro", "seedance2.0", "seedance2.0fast", "seedance2.0_vip", "seedance2.0fast_vip", "seedance2.0mini", "seedance2.5"]).optional(), videoResolution, duration: videoDuration.optional(), referenceImages: imageReferences.length(2) }).strict(),
z.object({ operation: z.literal("multiframe2video"), idempotencyKey: safeId, videoResolution: z.enum(["720p", "1080p"]), referenceImages: imageReferences.min(2).max(20), prompt: prompt.optional(), duration: z.number().min(1).max(8).optional(), transitionPrompts: z.array(prompt).min(1).max(19).optional(), transitionDurations: z.array(z.number().min(1).max(8)).min(1).max(19).optional() }).strict(),
z.object({ operation: z.literal("multimodal2video"), idempotencyKey: safeId, prompt: prompt.optional(), modelVersion: seedanceModel.optional(), ratio: videoRatio.optional(), videoResolution, duration: videoDuration.optional(), referenceImages: imageReferences.max(30).optional(), referenceVideos: z.array(referencePath).max(10).optional(), referenceAudios: z.array(referencePath).max(10).optional() }).strict(),
] as const;
const generationDiscriminatedSchema = z.discriminatedUnion("operation", dreaminaGenerationSchemas);
const schemas = [
...dreaminaGenerationSchemas,
z.object({ operation: z.literal("query_result"), submitId: safeId }).strict(),
] as const;
const discriminatedSchema = z.discriminatedUnion("operation", schemas);
export type DreaminaCliInput = z.infer<typeof discriminatedSchema>;
export type DreaminaGenerationInput = z.infer<typeof generationDiscriminatedSchema>;
export const dreaminaGenerationInputSchema = generationDiscriminatedSchema.superRefine(validateOperationCombination);
export const dreaminaCliInputSchema = discriminatedSchema.superRefine(validateOperationCombination);
// Public MCP exposes generation only; the internal Runtime schema retains query_result for scheduler use.
export const dreaminaMcpToolShape = z.object({
operation: z.enum(dreaminaGenerationOperations),
idempotencyKey: safeId,
prompt: prompt.optional(),
modelVersion: z.union([dreaminaImageModelSchema, imageVideoModel]).optional(),
ratio: imageRatio.optional(),
resolutionType: z.enum(["1k", "2k", "4k", "8k"]).optional().describe("For automatic image resolution, omit resolutionType. image_upscale requires an explicit tier."),
videoResolution: videoResolution.optional(),
duration: z.number().min(1).max(30).optional(),
generateNum: z.number().int().min(1).max(10).optional(),
referenceImages: imageReferences.max(30).optional(),
referenceVideos: z.array(referencePath).max(10).optional(),
referenceAudios: z.array(referencePath).max(10).optional(),
transitionPrompts: z.array(prompt).max(19).optional(),
transitionDurations: z.array(z.number().min(1).max(8)).max(19).optional(),
}).strict();
function validateOperationCombination(value: DreaminaCliInput, context: z.RefinementCtx) {
if (value.operation === "text2image" || value.operation === "image2image") {
const model = value.modelVersion ?? "5.0";
if (["3.0", "3.1"].includes(model) && value.resolutionType === "4k") invalid(context, "resolutionType");
if (model !== "5.0Pro" && !["3.0", "3.1"].includes(model) && value.resolutionType === "1k") invalid(context, "resolutionType");
return;
}
if (value.operation === "multiframe2video") {
const transitions = value.referenceImages.length - 1;
if (value.referenceImages.length === 2) {
if (!value.prompt || value.transitionPrompts || value.transitionDurations) invalid(context, "prompt");
} else if (value.prompt || value.duration !== undefined || value.transitionPrompts?.length !== transitions || (value.transitionDurations && value.transitionDurations.length !== transitions)) {
invalid(context, "transitionPrompts");
}
return;
}
if (value.operation === "multimodal2video") {
const images = value.referenceImages?.length ?? 0;
const videos = value.referenceVideos?.length ?? 0;
const audios = value.referenceAudios?.length ?? 0;
const model = value.modelVersion ?? "seedance2.0_vip";
if (images + videos + audios === 0 || (model !== "seedance2.5" && images + videos === 0)) invalid(context, "referenceImages");
if (model === "seedance2.5" ? images + videos + audios > 50 : images > 9 || videos > 3 || audios > 3 || images + videos + audios > 12) invalid(context, "referenceImages");
validateVideoCombination(model, value.videoResolution, value.duration, context);
return;
}
if (value.operation === "text2video" || value.operation === "image2video" || value.operation === "frames2video") {
const fallback = value.operation === "text2video" ? "seedance2.0fast" : "seedance2.0_vip";
validateVideoCombination(value.modelVersion ?? fallback, value.videoResolution, value.duration, context);
}
}
function validateVideoCombination(model: string, resolution: string, duration: number | undefined, context: z.RefinementCtx) {
const seconds = duration ?? 5;
const capability = dreaminaVideoModelCapability(model);
if (!capability.videoResolutions.includes(resolution as z.infer<typeof videoResolution>) || seconds < capability.minDuration || seconds > capability.maxDuration) invalid(context, "videoResolution");
}
function dreaminaMultimodalCapability(model: string) {
return model === "seedance2.5"
? dreaminaGenerationCapabilityMetadata.multimodal2video["seedance2.5"]
: dreaminaGenerationCapabilityMetadata.multimodal2video.default;
}
function invalid(context: z.RefinementCtx, field: string) {
context.addIssue({ code: "custom", path: [field], message: "unsupported Dreamina operation combination" });
}