From a03cc82dd079e1e300b186fbf2c04d8aa7e7b292 Mon Sep 17 00:00:00 2001 From: sp mac bookpro 2605 Date: Wed, 22 Jul 2026 21:37:36 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=8F=92=E4=BB=B6=20API=20ro?= =?UTF-8?q?uting=20=E7=BB=93=E6=9E=84=E5=8C=96=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/agents.ts | 8 +- src/components/ExternalToolEditor.tsx | 144 +++++++++++++++++++++++++- 2 files changed, 147 insertions(+), 5 deletions(-) diff --git a/src/api/agents.ts b/src/api/agents.ts index fdc29bf..0a6a328 100644 --- a/src/api/agents.ts +++ b/src/api/agents.ts @@ -33,6 +33,12 @@ export interface SkillDetail extends SkillBrief { config: string; } +export interface ExternalToolApiRouting { + summary: string; + useWhen: string[]; + doNotUseWhen?: string[]; +} + export interface ExternalToolApi { id?: string; name: string; @@ -41,6 +47,7 @@ export interface ExternalToolApi { path: string; headers?: Record | null; parametersSchema: Record; + routing: ExternalToolApiRouting; createdAt?: number; } @@ -120,4 +127,3 @@ export const AgentAPI = { deletePlugin: (agentId: string, pluginId: string) => api.delete(`/agents/${agentId}/plugins/${pluginId}`).then((r) => r.data) }; - diff --git a/src/components/ExternalToolEditor.tsx b/src/components/ExternalToolEditor.tsx index 3b6bec2..855b500 100644 --- a/src/components/ExternalToolEditor.tsx +++ b/src/components/ExternalToolEditor.tsx @@ -1,6 +1,6 @@ import { CopyOutlined, MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; import { App as AntApp, Button, Card, Form, Input, Modal, Select, Space } from 'antd'; -import { AgentAPI, ExternalToolApi, ExternalToolPlugin, ExternalToolPluginPayload } from '../api'; +import { AgentAPI, ExternalToolApi, ExternalToolApiRouting, ExternalToolPlugin, ExternalToolPluginPayload } from '../api'; interface Props { open: boolean; @@ -10,9 +10,12 @@ interface Props { onSaved?: () => void | Promise; } -interface ToolApiFormValue extends Omit { +interface ToolApiRoutingFormValue extends ExternalToolApiRouting {} + +interface ToolApiFormValue extends Omit { headers?: string; parametersSchema: string; + routing: ToolApiRoutingFormValue; } interface ToolPluginFormValue extends Omit { @@ -27,6 +30,11 @@ const EMPTY_API: ToolApiFormValue = { path: '', headers: '{}', parametersSchema: JSON.stringify({ type: 'object', properties: {} }, null, 2), + routing: { + summary: '', + useWhen: [''], + doNotUseWhen: [], + }, }; function parseJsonObject(value: string | undefined, fieldName: string, optional = false) { @@ -83,6 +91,53 @@ function parseJsonObject(value: string | undefined, fieldName: string, optional } } +function normalizeStringList(values?: string[]) { + return Array.from(new Set((values ?? []).map((item) => item?.trim()).filter((item): item is string => Boolean(item)))); +} + +function validateTrimmedText(message: string) { + return async (_: unknown, value: string | undefined) => { + if (!value?.trim()) { + throw new Error(message); + } + }; +} + +function validateRoutingList(message: string, min = 0) { + return async (_: unknown, value: string[] | undefined) => { + const normalized = normalizeStringList(value); + if (normalized.length < min) { + throw new Error(message); + } + + if ((value ?? []).some((item) => !item?.trim())) { + throw new Error('列表项不能为空'); + } + }; +} + +function normalizeRouting(value: ToolApiRoutingFormValue | undefined, apiName: string): ExternalToolApiRouting { + if (!value) { + throw new Error(`API ${apiName} 的路由规则不能为空`); + } + + const summary = value.summary?.trim(); + if (!summary) { + throw new Error(`API ${apiName} 的路由摘要不能为空`); + } + + const useWhen = normalizeStringList(value.useWhen); + if (useWhen.length < 1) { + throw new Error(`API ${apiName} 的 useWhen 至少保留一项`); + } + + return { + summary, + useWhen, + doNotUseWhen: normalizeStringList(value.doNotUseWhen), + }; +} + export default function ExternalToolEditor({ open, agentId, plugin, onClose, onSaved }: Props) { const { message } = AntApp.useApp(); const [form] = Form.useForm(); @@ -104,6 +159,7 @@ export default function ExternalToolEditor({ open, agentId, plugin, onClose, onS path: item.path.trim(), headers: parseJsonObject(item.headers, `API ${item.name} 的请求头`, true), parametersSchema: parseJsonObject(item.parametersSchema, `API ${item.name} 的依赖参数`), + routing: normalizeRouting(item.routing, item.name.trim() || '未命名 API'), })), }; @@ -153,6 +209,11 @@ export default function ExternalToolEditor({ open, agentId, plugin, onClose, onS path: item.path, headers: JSON.stringify(item.headers || {}, null, 2), parametersSchema: JSON.stringify(item.parametersSchema || { type: 'object', properties: {} }, null, 2), + routing: { + summary: item.routing?.summary || '', + useWhen: item.routing?.useWhen?.length ? item.routing.useWhen : [''], + doNotUseWhen: item.routing?.doNotUseWhen || [], + }, })), } : { @@ -259,11 +320,15 @@ export default function ExternalToolEditor({ open, agentId, plugin, onClose, onS - +
@@ -278,6 +343,77 @@ export default function ExternalToolEditor({ open, agentId, plugin, onClose, onS
+ + + + + + {(routingFields, { add: addUseWhen, remove: removeUseWhen }, { errors }) => ( + +
+
+ useWhen +
至少一项,描述什么情况下应该调用这个 API。
+
+ +
+ {routingFields.map((routingField) => ( + + + + + + + {routingFields.map((routingField) => ( + + + + +