Your Name 477f215259
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
feat: update video billing and agent workspace improvements
2026-04-10 16:25:35 +08:00

448 lines
13 KiB
TypeScript

import type { AnalysisResult } from "@/lib/ai/smart-analyzer";
import type { AgentThreadState } from "@/lib/ai/runtime/agent-thread-state";
import { refinePlanWithImageEditSubagent } from "@/lib/ai/image-edit-subagent";
import { refinePlanWithImageGenerationSubagent } from "@/lib/ai/image-generation-subagent";
import { refinePlanWithStoryboardSubagent } from "@/lib/video-agent/storyboard-subagent";
import type {
AgentExecutionPlan,
AgentPlanNextAction,
AgentPlanResponseMode,
AgentPlannedToolCall,
AgentPreferredCapability,
AgentTargetResolutionMode,
} from "@/lib/ai/auto-agent-graph";
import type {
AgentProjectToolDefinition,
AgentProjectToolName,
} from "@/lib/ai/tools-registry";
export type AgentSpecialistDomain =
| "conversation"
| "vision"
| "image_edit"
| "image_edit_memory"
| "image_generate"
| "image_generation_memory"
| "video_generate"
| "storyboard"
| "canvas";
export type AgentSpecialistId =
| "conversation-coordinator"
| "vision-analyst"
| "image-edit-specialist"
| "image-edit-subagent"
| "image-generate-specialist"
| "image-generation-subagent"
| "video-specialist"
| "storyboard-subagent"
| "canvas-specialist";
export interface AgentSpecialistRoute {
domain: AgentSpecialistDomain;
specialistId: AgentSpecialistId;
reason: string;
planningHints: string[];
toolAllowlist?: AgentProjectToolName[];
}
export interface AgentSpecialistContext {
text: string;
analysis: AnalysisResult;
hasImage: boolean;
hasCanvasSelection: boolean;
targetResolutionMode: AgentTargetResolutionMode;
mediaSource?: "current_message" | "canvas_selection" | "history" | "none";
selectedImageCount?: number;
threadState?: AgentThreadState | null;
}
export interface AgentPlanRefineInput extends AgentSpecialistContext {
plan: AgentExecutionPlan;
availableTools: AgentProjectToolDefinition[];
}
function hasTool(
availableTools: AgentProjectToolDefinition[],
name: AgentProjectToolName,
) {
return availableTools.some((tool) => tool.name === name);
}
function upsertTool(
toolCalls: AgentPlannedToolCall[],
name: AgentProjectToolName,
reason: string,
options?: {
prepend?: boolean;
args?: Record<string, unknown>;
},
) {
const existing = toolCalls.find((tool) => tool.name === name);
if (existing) {
if (!existing.reason) {
existing.reason = reason;
}
if (!existing.args && options?.args) {
existing.args = options.args;
}
return toolCalls;
}
const nextTool = {
name,
reason,
args: options?.args,
} satisfies AgentPlannedToolCall;
return options?.prepend ? [nextTool, ...toolCalls] : [...toolCalls, nextTool];
}
function forcePlanShape(params: {
plan: AgentExecutionPlan;
nextAction: AgentPlanNextAction;
intent: AnalysisResult["intent"];
responseMode?: AgentPlanResponseMode;
preferredCapability?: AgentPreferredCapability;
subIntent?: string;
toolCalls: AgentPlannedToolCall[];
shouldUseStructuredReply?: boolean;
targetSource?: AgentTargetResolutionMode;
}): AgentExecutionPlan {
return {
...params.plan,
nextAction: params.nextAction,
intent: params.intent,
responseMode:
params.responseMode ?? params.plan.responseMode,
preferredCapability:
params.preferredCapability ?? params.plan.preferredCapability,
subIntent:
params.intent === "image_edit"
? params.subIntent ?? params.plan.subIntent
: undefined,
toolCalls: params.toolCalls,
shouldUseStructuredReply:
params.shouldUseStructuredReply ?? params.plan.shouldUseStructuredReply,
targetSource: params.targetSource ?? params.plan.targetSource,
};
}
function prependTargetResolverTool(params: {
toolCalls: AgentPlannedToolCall[];
availableTools: AgentProjectToolDefinition[];
targetSource: AgentTargetResolutionMode;
hasCanvasSelection?: boolean;
}) {
if (params.targetSource === "none" || params.targetSource === "clarification") {
return params.toolCalls;
}
let source: "selected_canvas" | "latest_result" | "original_upload" | "auto" = "auto";
if (params.targetSource === "explicit_selection" || params.hasCanvasSelection) {
source = "selected_canvas";
} else if (params.targetSource === "current_upload") {
source = "original_upload";
} else if (
params.targetSource === "latest_result" ||
params.targetSource === "working_memory"
) {
source = "latest_result";
}
if (hasTool(params.availableTools, "resolveTargetImage")) {
return upsertTool(
params.toolCalls,
"resolveTargetImage",
"先锁定当前要处理的目标图。",
{
prepend: true,
args: { source },
},
);
}
if (source === "selected_canvas" && hasTool(params.availableTools, "useSelectedCanvasImage")) {
return upsertTool(
params.toolCalls,
"useSelectedCanvasImage",
"先使用当前选中的画布图片作为目标图。",
{ prepend: true },
);
}
if (source === "latest_result" && hasTool(params.availableTools, "useLatestResultImage")) {
return upsertTool(
params.toolCalls,
"useLatestResultImage",
"先使用最近结果图作为目标图。",
{ prepend: true },
);
}
if (source === "original_upload" && hasTool(params.availableTools, "useOriginalUploadImage")) {
return upsertTool(
params.toolCalls,
"useOriginalUploadImage",
"先使用原上传图作为目标图。",
{ prepend: true },
);
}
return params.toolCalls;
}
function appendCanvasPublishTool(
toolCalls: AgentPlannedToolCall[],
availableTools: AgentProjectToolDefinition[],
) {
if (hasTool(availableTools, "publishResultToCanvas")) {
return upsertTool(
toolCalls,
"publishResultToCanvas",
"执行结果需要同步回无限画布。",
);
}
if (hasTool(availableTools, "publishToCanvas")) {
return upsertTool(
toolCalls,
"publishToCanvas",
"执行结果需要同步回无限画布。",
);
}
return toolCalls;
}
function normalizeEditCapability(
analysis: AnalysisResult,
text: string,
): {
capability: AgentPreferredCapability;
toolName: AgentProjectToolName;
subIntent?: string;
} {
const normalizedText = String(text || "").toLowerCase();
const subIntent = String(analysis.subIntent || "").trim().toLowerCase();
if (subIntent === "remove_background" || /抠图|去背|去背景|透明底/.test(normalizedText)) {
return {
capability: "remove_background",
toolName: "removeBackground",
subIntent: "remove_background",
};
}
if (subIntent === "extend" || /扩图|拓图|补边|延展|加宽|加高/.test(normalizedText)) {
return {
capability: "extend",
toolName: "extendImage",
subIntent: "extend",
};
}
if (subIntent === "resize" || /尺寸|分辨率|改成.*x|改为.*x|比例|画布/.test(normalizedText)) {
return {
capability: "resize",
toolName: "resizeImage",
subIntent: "resize",
};
}
if (subIntent === "box_cutout" || /框选抠图|框选/.test(normalizedText)) {
return {
capability: "box_cutout",
toolName: "boxCutout",
subIntent: "box_cutout",
};
}
if (subIntent === "enhance" || /增强|放大|高清|超清|锐化|修复/.test(normalizedText)) {
return {
capability: "enhance",
toolName: "enhanceImage",
subIntent: "enhance",
};
}
return {
capability: "generic_edit",
toolName: "editImage",
subIntent: analysis.subIntent || undefined,
};
}
export function refinePlanWithSpecialist(
specialist: AgentSpecialistRoute,
input: AgentPlanRefineInput,
): AgentExecutionPlan {
const { plan, availableTools, analysis, text } = input;
let toolCalls = [...plan.toolCalls];
if (specialist.domain === "conversation") {
const shouldClarify = plan.nextAction === "clarify";
if (hasTool(availableTools, "respondDirect")) {
toolCalls = [{ name: "respondDirect", reason: "纯对话由对话协调专家直接回复" }];
}
return forcePlanShape({
plan,
nextAction: shouldClarify ? "clarify" : "respond",
intent: "chat",
responseMode: shouldClarify ? "clarify" : "direct_answer",
preferredCapability: "respond",
toolCalls,
shouldUseStructuredReply: false,
targetSource: "none",
});
}
if (specialist.domain === "vision") {
if (hasTool(availableTools, "analyzeTargetImage")) {
toolCalls = [{ name: "analyzeTargetImage", reason: "视觉分析任务由看图专家处理" }];
} else if (hasTool(availableTools, "analyzeImage")) {
toolCalls = [{ name: "analyzeImage", reason: "视觉分析任务由看图专家处理" }];
} else if (hasTool(availableTools, "respondDirect")) {
toolCalls = [{ name: "respondDirect", reason: "缺少看图工具时退回直接回答" }];
}
toolCalls = prependTargetResolverTool({
toolCalls,
availableTools,
targetSource: plan.targetSource,
hasCanvasSelection: input.hasCanvasSelection,
});
return forcePlanShape({
plan,
nextAction: "analyze_image",
intent: "vision",
responseMode: "tool_then_answer",
preferredCapability: "analyze",
toolCalls,
shouldUseStructuredReply: false,
});
}
if (specialist.domain === "image_edit") {
const editTarget = normalizeEditCapability(analysis, text);
if (hasTool(availableTools, editTarget.toolName)) {
toolCalls = [{ name: editTarget.toolName, reason: "修图专家选择最匹配的编辑能力" }];
} else if (hasTool(availableTools, "editTargetImage")) {
toolCalls = [{ name: "editTargetImage", reason: "修图专家回退到目标图编辑能力" }];
} else if (hasTool(availableTools, "editImage")) {
toolCalls = [{ name: "editImage", reason: "修图专家回退到通用编辑能力" }];
}
toolCalls = prependTargetResolverTool({
toolCalls,
availableTools,
targetSource: plan.targetSource,
hasCanvasSelection: input.hasCanvasSelection,
});
return forcePlanShape({
plan,
nextAction: "edit_image",
intent: "image_edit",
subIntent: editTarget.subIntent,
responseMode: "tool_then_execute",
preferredCapability: editTarget.capability,
toolCalls,
shouldUseStructuredReply: true,
});
}
if (specialist.domain === "image_edit_memory") {
return refinePlanWithImageEditSubagent({
text,
plan,
availableTools,
threadState: input.threadState,
hasImage: input.hasImage,
});
}
if (specialist.domain === "image_generate") {
if (hasTool(availableTools, "generateNewImage")) {
toolCalls = [{ name: "generateNewImage", reason: "生图专家执行图片生成" }];
} else if (hasTool(availableTools, "generateImage")) {
toolCalls = [{ name: "generateImage", reason: "生图专家执行图片生成" }];
}
toolCalls = prependTargetResolverTool({
toolCalls,
availableTools,
targetSource: plan.targetSource,
hasCanvasSelection: input.hasCanvasSelection,
});
return forcePlanShape({
plan,
nextAction: "generate_image",
intent: "image_gen",
responseMode: "tool_then_execute",
preferredCapability: "generate_image",
toolCalls,
shouldUseStructuredReply: true,
});
}
if (specialist.domain === "image_generation_memory") {
return refinePlanWithImageGenerationSubagent({
text,
plan,
availableTools,
threadState: input.threadState,
hasImage: input.hasImage,
});
}
if (specialist.domain === "video_generate") {
if (hasTool(availableTools, "generateVideo")) {
toolCalls = [{ name: "generateVideo", reason: "视频专家执行视频生成" }];
}
return forcePlanShape({
plan,
nextAction: "generate_video",
intent: "video_gen",
responseMode: "tool_then_execute",
preferredCapability: "generate_video",
toolCalls,
shouldUseStructuredReply: true,
});
}
if (specialist.domain === "storyboard") {
return refinePlanWithStoryboardSubagent({
text,
plan,
availableTools,
threadState: input.threadState,
});
}
if (specialist.domain === "canvas") {
const isExecutionAction =
plan.nextAction === "edit_image" ||
plan.nextAction === "generate_image";
const normalizedTargetSource =
input.hasCanvasSelection && plan.targetSource === "none"
? "explicit_selection"
: plan.targetSource;
if (
normalizedTargetSource !== "none" &&
normalizedTargetSource !== "clarification"
) {
toolCalls = prependTargetResolverTool({
toolCalls,
availableTools,
targetSource: normalizedTargetSource,
hasCanvasSelection: input.hasCanvasSelection,
});
}
if (
isExecutionAction &&
(hasTool(availableTools, "publishResultToCanvas") ||
hasTool(availableTools, "publishToCanvas"))
) {
toolCalls = appendCanvasPublishTool(toolCalls, availableTools);
}
return {
...plan,
toolCalls,
targetSource: normalizedTargetSource,
};
}
return plan;
}