You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
122 lines
4.3 KiB
122 lines
4.3 KiB
import { z } from "zod";
|
|
import type { JSONSchema7 } from "json-schema";
|
|
import type { ToolExecutor, ToolResult } from "../../registry";
|
|
import type { AgentInvokeToolConfig } from "./config";
|
|
import { invokeAgentViaA2A } from "../../../a2a/client";
|
|
import { listAgents } from "../../../agent/agent";
|
|
|
|
export const agentInvokeInputSchema = z.object({
|
|
agentSlug: z.string().min(1).max(100).describe("目标 agent 的 slug"),
|
|
input: z.string().min(1).max(50000).describe("传递给目标 agent 的任务描述/输入内容"),
|
|
});
|
|
|
|
type AgentInvokeInput = z.infer<typeof agentInvokeInputSchema>;
|
|
|
|
export const agentInvokeExecutor: ToolExecutor<AgentInvokeToolConfig> = {
|
|
buildInputSchema(_config: AgentInvokeToolConfig): JSONSchema7 {
|
|
return {
|
|
type: "object",
|
|
properties: {
|
|
agentSlug: {
|
|
type: "string",
|
|
description: "目标 agent 的 slug(唯一标识符)",
|
|
},
|
|
input: {
|
|
type: "string",
|
|
description: "传递给目标 agent 的任务描述或输入内容",
|
|
},
|
|
},
|
|
required: ["agentSlug", "input"],
|
|
};
|
|
},
|
|
|
|
buildDescription(_config: AgentInvokeToolConfig): string {
|
|
return "调用另一个 agent 来处理子任务。目标 agent 必须已启用且标记为可调用(isCallable)。传入 agent slug 和任务描述,返回目标 agent 的回复内容。支持多级调用链,但有递归深度限制。";
|
|
},
|
|
|
|
async buildDescriptionAsync(_config: AgentInvokeToolConfig, ctx?: { agentId?: number | null }): Promise<string> {
|
|
const base = this.buildDescription(_config);
|
|
const callableAgents = await listAgents(true);
|
|
const currentAgentId = ctx?.agentId ?? null;
|
|
const available = callableAgents.filter((a) => a.isCallable === 1 && a.id !== currentAgentId);
|
|
if (available.length === 0) {
|
|
return `${base}\n\n当前没有可调用的 Agent。请勿调用此工具。`;
|
|
}
|
|
const agentList = available.map((a) => `- slug: "${a.slug}" — ${a.name}: ${a.description ?? "无描述"}`).join("\n");
|
|
return `${base}\n\n可调用的 Agent 列表(通过 agentSlug 参数指定):\n${agentList}`;
|
|
},
|
|
|
|
formatResult(result: ToolResult, _config: AgentInvokeToolConfig): string {
|
|
if (!result.success) {
|
|
return `Agent 调用失败: ${result.error ?? "未知错误"}`;
|
|
}
|
|
const meta = result.metadata;
|
|
const agentSlug = meta?.agentSlug ?? "unknown";
|
|
const depth = meta?.recursionDepth ?? 0;
|
|
const duration = meta?.durationMs ?? 0;
|
|
const data = typeof result.data === "string" ? result.data : JSON.stringify(result.data);
|
|
return `[Agent "${agentSlug}" 调用完成 | 深度: ${depth} | 耗时: ${duration}ms]\n${data}`;
|
|
},
|
|
|
|
async execute(
|
|
input: unknown,
|
|
config: AgentInvokeToolConfig,
|
|
ctx: { userId: number | null; agentId?: number | null; recursionDepth?: number },
|
|
): Promise<ToolResult> {
|
|
const start = Date.now();
|
|
|
|
const parsed = agentInvokeInputSchema.safeParse(input);
|
|
if (!parsed.success) {
|
|
return {
|
|
success: false,
|
|
data: null,
|
|
error: `参数校验失败: ${parsed.error.message}`,
|
|
metadata: { durationMs: Date.now() - start },
|
|
};
|
|
}
|
|
|
|
const data: AgentInvokeInput = parsed.data;
|
|
const currentDepth = ctx.recursionDepth ?? 0;
|
|
|
|
const result = await invokeAgentViaA2A(
|
|
{
|
|
agentSlug: data.agentSlug,
|
|
input: data.input,
|
|
context: {
|
|
userId: ctx.userId,
|
|
callerAgentId: ctx.agentId ?? null,
|
|
recursionDepth: currentDepth + 1,
|
|
},
|
|
},
|
|
{
|
|
maxRecursionDepth: config.maxRecursionDepth,
|
|
timeoutMs: config.timeoutMs,
|
|
maxOutputTokens: config.maxOutputTokens,
|
|
},
|
|
);
|
|
|
|
if (!result.ok) {
|
|
return {
|
|
success: false,
|
|
data: null,
|
|
error: result.error ?? `调用 agent ${data.agentSlug} 失败`,
|
|
metadata: { durationMs: Date.now() - start },
|
|
};
|
|
}
|
|
|
|
let output = result.output;
|
|
if (output.length > config.resultTruncateLength) {
|
|
output = output.slice(0, config.resultTruncateLength) + `\n...[结果已截断,原始长度 ${output.length} 字符]`;
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
data: output,
|
|
metadata: {
|
|
durationMs: Date.now() - start,
|
|
agentSlug: data.agentSlug,
|
|
recursionDepth: currentDepth + 1,
|
|
},
|
|
};
|
|
},
|
|
};
|
|
|