151 lines
5.1 KiB
TypeScript

import { tool, type ToolSet } from "ai";
import { z } from "zod";
import { createCanvasCapabilityClientCommand } from "@/lib/canvas/capabilities/client-command";
import { CANVAS_CAPABILITY_REGISTRY } from "@/lib/canvas/capabilities/registry";
import {
executeCanvasCapabilityRuntime,
type CanvasCapabilityRuntimeResult,
} from "@/lib/canvas/capabilities/runtime";
import type { CanvasCapabilityDefinition } from "@/lib/canvas/capabilities/types";
type CanvasCapabilityToolCallbacks = {
onClientCommand?: (
command: NonNullable<CanvasCapabilityRuntimeResult["clientCommand"]>,
capability: CanvasCapabilityDefinition
) => void;
onResult?: (
result: CanvasCapabilityRuntimeResult,
capability: CanvasCapabilityDefinition
) => void;
};
function isCapabilityAvailableForSelection(params: {
capability: CanvasCapabilityDefinition;
selectedNodeIds: string[];
}) {
const count = params.selectedNodeIds.length;
return (
params.capability.agent.exposed &&
count >= params.capability.selection.min &&
(!params.capability.selection.max || count <= params.capability.selection.max)
);
}
function buildToolDescription(capability: CanvasCapabilityDefinition) {
const modeDescription =
capability.executionMode === "interactive"
? "该工具会在用户画布中打开交互面板。"
: capability.executionMode === "headless"
? "该工具会在后台执行并把结果写回画布。"
: "参数完整时后台执行,否则在用户画布中打开交互面板。";
const aliases = capability.agent.aliases.length
? `常见表达:${capability.agent.aliases.join("、")}。`
: "";
return `${capability.description}${modeDescription}${aliases}默认作用于当前选中图片;连续操作时可把上一步返回的 createdNodeIds 传入 nodeIds。`;
}
export async function createCanvasCapabilityTools(params: {
callbacks?: CanvasCapabilityToolCallbacks;
defaultModelId?: string;
destructiveConfirmed?: boolean;
projectId: string;
requestHeaders: Headers;
requestUrl: string;
selectedNodeIds: string[];
userId: string;
}) {
const selectedNodeIds = Array.from(
new Set(
params.selectedNodeIds
.map((id) => String(id || "").trim())
.filter(Boolean)
)
);
const tools: ToolSet = {};
const { getEnabledCanvasCapabilityIds } = await import(
"@/lib/canvas/capabilities/availability"
);
const enabledCapabilityIds = await getEnabledCanvasCapabilityIds(
CANVAS_CAPABILITY_REGISTRY
);
for (const capability of CANVAS_CAPABILITY_REGISTRY) {
if (!enabledCapabilityIds.has(capability.id)) continue;
if (!isCapabilityAvailableForSelection({ capability, selectedNodeIds })) continue;
const toolName = capability.agent.toolName;
if (!toolName) continue;
tools[toolName] = tool({
description: buildToolDescription(capability),
inputSchema: z.object({
nodeIds: z
.array(z.string().trim().min(1).max(180))
.min(1)
.max(50)
.optional()
.describe("可选。传入上一个工具返回的 createdNodeIds 可链式处理新结果。"),
args: capability.inputSchema.optional().describe("能力参数。"),
}),
execute: async (input) => {
const rawArgs =
input.args && typeof input.args === "object" && !Array.isArray(input.args)
? input.args
: {};
const args = {
...rawArgs,
...(capability.modelPolicy === "agent-selected" &&
params.defaultModelId &&
!("modelId" in rawArgs)
? { modelId: params.defaultModelId }
: {}),
};
const command = createCanvasCapabilityClientCommand({
capabilityId: capability.id,
nodeIds: input.nodeIds?.length ? input.nodeIds : selectedNodeIds,
projectId: params.projectId,
args,
source: "agent",
confirmed:
capability.risk === "destructive"
? params.destructiveConfirmed === true
: undefined,
});
const result = await executeCanvasCapabilityRuntime({
command,
projectId: params.projectId,
requestHeaders: params.requestHeaders,
requestUrl: params.requestUrl,
userId: params.userId,
});
if (result.clientCommand) {
params.callbacks?.onClientCommand?.(result.clientCommand, capability);
}
params.callbacks?.onResult?.(result, capability);
return {
capabilityId: capability.id,
title: capability.title,
mode: result.mode,
status: result.task.status,
taskId: result.task.id,
requestId: result.task.requestId,
createdNodeIds: result.operation?.createdNodeIds || [],
updatedNodeIds: result.operation?.updatedNodeIds || [],
outputs: result.outputs || [],
clientAction: result.clientCommand
? capability.clientAction
: undefined,
};
},
});
}
return tools;
}
export function getCanvasCapabilityToolNames(tools: ToolSet) {
return Object.keys(tools);
}