Просмотр исходного кода

fix(agent): normalize sub-agent platform args

Ma 6 месяцев назад
Родитель
Сommit
50720009eb

+ 47 - 0
packages/core/src/__tests__/agent-tools-params.test.ts

@@ -1,4 +1,5 @@
 import { describe, it, expect, vi, beforeEach } from "vitest";
+import { validateToolArguments } from "@mariozechner/pi-ai";
 import { createSubAgentTool } from "../agent/agent-tools.js";
 
 describe("SubAgentParams schema", () => {
@@ -41,6 +42,38 @@ describe("SubAgentParams schema", () => {
     expect(props.mode.description).toMatch(/reviser/i);
     expect(props.format.description).toMatch(/exporter/i);
   });
+
+  it("normalizes platform aliases before sub_agent schema validation", () => {
+    const prepared = tool.prepareArguments?.({
+      agent: "architect",
+      instruction: "创建一本番茄都市文",
+      title: "夜港账本",
+      genre: "urban",
+      platform: "番茄小说",
+      language: "zh",
+    });
+
+    expect(prepared).toMatchObject({ platform: "tomato" });
+    expect(() => validateToolArguments(tool as any, {
+      name: "sub_agent",
+      arguments: prepared,
+    } as any)).not.toThrow();
+
+    const blankPlatform = tool.prepareArguments?.({
+      agent: "architect",
+      instruction: "创建一本都市文",
+      title: "空平台测试",
+      genre: "urban",
+      platform: "",
+      language: "zh",
+    });
+
+    expect(blankPlatform).not.toHaveProperty("platform");
+    expect(() => validateToolArguments(tool as any, {
+      name: "sub_agent",
+      arguments: blankPlatform,
+    } as any)).not.toThrow();
+  });
 });
 
 describe("architect agent — BookConfig construction", () => {
@@ -86,6 +119,20 @@ describe("architect agent — BookConfig construction", () => {
     expect(bookConfig.targetChapters).toBe(200);
     expect(bookConfig.chapterWordCount).toBe(3000);
   });
+
+  it("normalizes unsupported platform names to other during architect creation", async () => {
+    await tool.execute("tc3", {
+      agent: "architect",
+      instruction: "Create a Royal Road fantasy novel",
+      title: "Harbor Oath",
+      genre: "fantasy",
+      platform: "royal-road",
+      language: "en",
+    } as any);
+
+    const [bookConfig] = initBookMock.mock.calls[0];
+    expect(bookConfig.platform).toBe("other");
+  });
 });
 
 describe("writer agent — wordCount passthrough", () => {

+ 52 - 2
packages/core/src/agent/agent-tools.ts

@@ -104,6 +104,55 @@ const SubAgentParams = Type.Object({
   approvedOnly: Type.Optional(Type.Boolean({ description: "exporter only: export only approved chapters. Default: false" })),
 });
 
+type SubAgentParamsType = Static<typeof SubAgentParams>;
+type ArchitectPlatform = NonNullable<SubAgentParamsType["platform"]>;
+
+function normalizeArchitectPlatform(platform: unknown): ArchitectPlatform | undefined {
+  if (typeof platform !== "string") {
+    return undefined;
+  }
+
+  const raw = platform.trim();
+  if (!raw) {
+    return undefined;
+  }
+
+  const lowered = raw.toLowerCase();
+  const compact = lowered.replace(/[\s_-]+/g, "");
+
+  if (compact === "tomato" || compact === "fanqie" || compact === "fanqienovel" || raw.includes("番茄")) {
+    return "tomato";
+  }
+  if (compact === "qidian" || compact === "qidianzhongwenwang" || raw.includes("起点")) {
+    return "qidian";
+  }
+  if (compact === "feilu" || raw.includes("飞卢")) {
+    return "feilu";
+  }
+  if (compact === "other" || compact === "others" || raw.includes("其他") || raw.includes("其它")) {
+    return "other";
+  }
+
+  return "other";
+}
+
+function prepareSubAgentArguments(args: unknown): SubAgentParamsType {
+  if (!args || typeof args !== "object" || Array.isArray(args)) {
+    return args as SubAgentParamsType;
+  }
+
+  const prepared = { ...(args as Record<string, unknown>) };
+  if ("platform" in prepared) {
+    const platform = normalizeArchitectPlatform(prepared.platform);
+    if (platform) {
+      prepared.platform = platform;
+    } else {
+      delete prepared.platform;
+    }
+  }
+  return prepared as SubAgentParamsType;
+}
+
 export function createSubAgentTool(
   pipeline: PipelineRunner,
   activeBookId: string | null,
@@ -117,9 +166,10 @@ export function createSubAgentTool(
       "'auditor' to audit quality, 'reviser' to revise a chapter, 'exporter' to export.",
     label: "Sub-Agent",
     parameters: SubAgentParams,
+    prepareArguments: prepareSubAgentArguments,
     async execute(
       _toolCallId: string,
-      params: Static<typeof SubAgentParams>,
+      params: SubAgentParamsType,
       _signal?: AbortSignal,
       onUpdate?: AgentToolUpdateCallback,
     ): Promise<AgentToolResult<unknown>> {
@@ -165,7 +215,7 @@ export function createSubAgentTool(
                 id,
                 title: resolvedTitle,
                 genre: genre ?? "general",
-                platform: (platform ?? "other") as any,
+                platform: normalizeArchitectPlatform(platform) ?? "other",
                 language: (language ?? "zh") as any,
                 status: "outlining" as any,
                 targetChapters: targetChapters ?? 200,