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.
 
 
 
 

376 lines
13 KiB

import { dbGlobal } from "drizzle-pkg/lib/db";
import { agentTools } from "drizzle-pkg/lib/schema/agent-tool";
import { eq, asc } from "drizzle-orm";
import { tool, jsonSchema } from "ai";
import { z } from "zod";
import { registerToolType, getExecutor, type ToolContext, type ToolResult } from "./registry";
import { writeToolLog } from "./log";
import { parseFetchConfig, DEFAULT_FETCH_CONFIG } from "./executors/fetch/config";
import { fetchExecutor, fetchInputSchema } from "./executors/fetch/fetch";
import { parseCalculatorConfig, DEFAULT_CALCULATOR_CONFIG } from "./executors/calculator/config";
import { calculatorExecutor, calculatorInputSchema } from "./executors/calculator/calculator";
import { parseDatetimeConfig, DEFAULT_DATETIME_CONFIG } from "./executors/datetime/config";
import { datetimeExecutor, datetimeInputSchema } from "./executors/datetime/datetime";
import { parseUuidConfig, DEFAULT_UUID_CONFIG } from "./executors/uuid/config";
import { uuidExecutor, uuidInputSchema } from "./executors/uuid/uuid";
import { parseBase64Config, DEFAULT_BASE64_CONFIG } from "./executors/base64/config";
import { base64Executor, base64InputSchema } from "./executors/base64/base64";
import { parseJsonFormatterConfig, DEFAULT_JSON_FORMATTER_CONFIG } from "./executors/json-formatter/config";
import { jsonFormatterExecutor, jsonFormatterInputSchema } from "./executors/json-formatter/json-formatter";
import { parseRegexTesterConfig, DEFAULT_REGEX_TESTER_CONFIG } from "./executors/regex-tester/config";
import { regexTesterExecutor, regexTesterInputSchema } from "./executors/regex-tester/regex-tester";
// ============ 工具类型注册表 ============
interface ToolTypeRegistration {
executor: import("./registry").ToolExecutor<any>;
parseConfig: (raw: unknown) => Record<string, unknown>;
defaultConfig: Record<string, unknown>;
zodSchema: z.ZodType;
}
const TOOL_TYPE_REGISTRY: Record<string, ToolTypeRegistration> = {
fetch: {
executor: fetchExecutor,
parseConfig: parseFetchConfig,
defaultConfig: DEFAULT_FETCH_CONFIG,
zodSchema: fetchInputSchema,
},
calculator: {
executor: calculatorExecutor,
parseConfig: parseCalculatorConfig,
defaultConfig: DEFAULT_CALCULATOR_CONFIG,
zodSchema: calculatorInputSchema,
},
datetime: {
executor: datetimeExecutor,
parseConfig: parseDatetimeConfig,
defaultConfig: DEFAULT_DATETIME_CONFIG,
zodSchema: datetimeInputSchema,
},
uuid: {
executor: uuidExecutor,
parseConfig: parseUuidConfig,
defaultConfig: DEFAULT_UUID_CONFIG,
zodSchema: uuidInputSchema,
},
base64: {
executor: base64Executor,
parseConfig: parseBase64Config,
defaultConfig: DEFAULT_BASE64_CONFIG,
zodSchema: base64InputSchema,
},
"json-formatter": {
executor: jsonFormatterExecutor,
parseConfig: parseJsonFormatterConfig,
defaultConfig: DEFAULT_JSON_FORMATTER_CONFIG,
zodSchema: jsonFormatterInputSchema,
},
"regex-tester": {
executor: regexTesterExecutor,
parseConfig: parseRegexTesterConfig,
defaultConfig: DEFAULT_REGEX_TESTER_CONFIG,
zodSchema: regexTesterInputSchema,
},
};
// 立即注册所有工具类型
for (const [type, reg] of Object.entries(TOOL_TYPE_REGISTRY)) {
registerToolType(type, reg.executor);
}
export type AgentToolRow = typeof agentTools.$inferSelect;
export interface CreateAgentToolInput {
name: string;
slug: string;
description: string;
type: string;
config: Record<string, unknown>;
enabled?: boolean;
sortOrder?: number;
}
export interface UpdateAgentToolInput {
name?: string;
slug?: string;
description?: string;
type?: string;
config?: Record<string, unknown>;
enabled?: boolean;
sortOrder?: number;
}
export async function listAgentTools(): Promise<AgentToolRow[]> {
return dbGlobal.select().from(agentTools).orderBy(asc(agentTools.sortOrder), asc(agentTools.createdAt));
}
export async function getAgentToolById(id: string): Promise<AgentToolRow | null> {
const rows = await dbGlobal.select().from(agentTools).where(eq(agentTools.id, id)).limit(1);
return rows[0] ?? null;
}
export async function getAgentToolBySlug(slug: string): Promise<AgentToolRow | null> {
const rows = await dbGlobal.select().from(agentTools).where(eq(agentTools.slug, slug)).limit(1);
return rows[0] ?? null;
}
function generateId(): string {
return `at_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
}
function validateConfig(type: string, config: Record<string, unknown>): Record<string, unknown> {
const reg = TOOL_TYPE_REGISTRY[type];
if (reg) {
return reg.parseConfig(config);
}
return config;
}
function getDefaultConfig(type: string): Record<string, unknown> {
return TOOL_TYPE_REGISTRY[type]?.defaultConfig ?? {};
}
function getZodSchema(type: string): z.ZodType {
return TOOL_TYPE_REGISTRY[type]?.zodSchema ?? z.object({});
}
export interface ToolTypeMeta {
type: string;
defaultConfig: Record<string, unknown>;
inputSchema: Record<string, unknown>;
}
export function getToolTypeMetaList(): ToolTypeMeta[] {
return Object.entries(TOOL_TYPE_REGISTRY).map(([type, reg]) => ({
type,
defaultConfig: reg.defaultConfig,
inputSchema: z.toJSONSchema(reg.zodSchema) as Record<string, unknown>,
}));
}
export function getToolTypeMeta(type: string): ToolTypeMeta | null {
const reg = TOOL_TYPE_REGISTRY[type];
if (!reg) return null;
return {
type,
defaultConfig: reg.defaultConfig,
inputSchema: z.toJSONSchema(reg.zodSchema) as Record<string, unknown>,
};
}
export async function createAgentTool(input: CreateAgentToolInput): Promise<AgentToolRow> {
const existing = await getAgentToolBySlug(input.slug);
if (existing) {
throw new Error(`slug 已存在: ${input.slug}`);
}
const validatedConfig = validateConfig(input.type, input.config);
const id = generateId();
await dbGlobal.insert(agentTools).values({
id,
name: input.name,
slug: input.slug,
description: input.description,
type: input.type,
config: JSON.stringify(validatedConfig),
enabled: input.enabled === false ? 0 : 1,
sortOrder: input.sortOrder ?? 0,
});
const row = await getAgentToolById(id);
if (!row) throw new Error("创建后查询失败");
return row;
}
export async function updateAgentTool(id: string, input: UpdateAgentToolInput): Promise<AgentToolRow | null> {
const existing = await getAgentToolById(id);
if (!existing) return null;
if (input.slug && input.slug !== existing.slug) {
const slugConflict = await getAgentToolBySlug(input.slug);
if (slugConflict) {
throw new Error(`slug 已存在: ${input.slug}`);
}
}
const updates: Partial<AgentToolRow> = {};
if (input.name !== undefined) updates.name = input.name;
if (input.slug !== undefined) updates.slug = input.slug;
if (input.description !== undefined) updates.description = input.description;
if (input.type !== undefined) updates.type = input.type;
if (input.config !== undefined) {
const typeToValidate = input.type ?? existing.type;
updates.config = JSON.stringify(validateConfig(typeToValidate, input.config));
}
if (input.enabled !== undefined) updates.enabled = input.enabled ? 1 : 0;
if (input.sortOrder !== undefined) updates.sortOrder = input.sortOrder;
if (Object.keys(updates).length === 0) return existing;
await dbGlobal.update(agentTools).set(updates).where(eq(agentTools.id, id));
return getAgentToolById(id);
}
export async function deleteAgentTool(id: string): Promise<void> {
await dbGlobal.delete(agentTools).where(eq(agentTools.id, id));
}
export async function executeAgentTool(
id: string,
input: unknown,
userId: number | null,
): Promise<ToolResult> {
const agentTool = await getAgentToolById(id);
if (!agentTool) {
return {
success: false,
data: null,
error: `工具不存在: ${id}`,
metadata: { durationMs: 0 },
};
}
if (!agentTool.enabled) {
return {
success: false,
data: null,
error: `工具已禁用: ${agentTool.slug}`,
metadata: { durationMs: 0 },
};
}
const executor = getExecutor(agentTool.type);
if (!executor) {
return {
success: false,
error: `工具类型未注册: ${agentTool.type}`,
metadata: { durationMs: 0 },
};
}
let config: unknown;
try {
config = JSON.parse(agentTool.config);
} catch {
config = getDefaultConfig(agentTool.type);
}
const ctx: ToolContext = {
toolId: agentTool.id,
toolSlug: agentTool.slug,
userId,
};
const result = await executor.execute(input, config, ctx);
// 写日志
const status = result.success ? "success" : result.error?.includes("超时") ? "timeout" : "error";
await writeToolLog({
toolId: agentTool.id,
toolSlug: agentTool.slug,
userId,
input: JSON.stringify(input),
output: result.success ? JSON.stringify(result.data)?.slice(0, 10000) ?? null : null,
status: status as "success" | "error" | "timeout",
errorMessage: result.error ?? null,
durationMs: result.metadata?.durationMs ?? 0,
});
return result;
}
export interface EnabledToolInfo {
slug: string;
name: string;
type: string;
description: string;
}
export async function getEnabledToolInfos(): Promise<EnabledToolInfo[]> {
const tools = await dbGlobal
.select()
.from(agentTools)
.where(eq(agentTools.enabled, 1))
.orderBy(asc(agentTools.sortOrder));
const result: EnabledToolInfo[] = [];
for (const agentTool of tools) {
const executor = getExecutor(agentTool.type);
if (!executor) continue;
let config: unknown;
try {
config = JSON.parse(agentTool.config);
} catch {
config = getDefaultConfig(agentTool.type);
}
result.push({
slug: agentTool.slug,
name: agentTool.name,
type: agentTool.type,
description: executor.buildDescription(config),
});
}
return result;
}
export async function getEnabledToolsForLlm(): Promise<Record<string, ReturnType<typeof tool>>> {
const tools = await dbGlobal
.select()
.from(agentTools)
.where(eq(agentTools.enabled, 1))
.orderBy(asc(agentTools.sortOrder));
const result: Record<string, any> = {};
for (const agentTool of tools) {
const executor = getExecutor(agentTool.type);
if (!executor) continue;
let config: unknown;
try {
config = JSON.parse(agentTool.config);
} catch {
config = getDefaultConfig(agentTool.type);
}
const zodSchema = getZodSchema(agentTool.type);
const jsonSch = z.toJSONSchema(zodSchema) as Record<string, unknown>;
result[agentTool.slug] = tool({
description: executor.buildDescription(config),
parameters: jsonSchema(jsonSch, {
validate: (v: unknown) => {
const r = zodSchema.safeParse(v);
return r.success
? { success: true as const, value: r.data }
: { success: false as const, error: r.error };
},
}),
execute: async (input: unknown) => {
const execResult = await executeAgentTool(agentTool.id, input, null);
if (!execResult.success) {
return `工具执行失败: ${execResult.error ?? "未知错误"}。请停止调用此工具,基于已有信息回答用户或告知用户此工具不可用。`;
}
const meta = execResult.metadata;
const sizeInfo = meta?.responseSize ? `${meta.responseSize} bytes` : `未知大小`;
const statusInfo = meta?.statusCode ? `HTTP ${meta.statusCode}` : "";
const dataStr = typeof execResult.data === "string" ? execResult.data : JSON.stringify(execResult.data);
return `[${agentTool.type} 结果 ${statusInfo} ${sizeInfo}]\n${dataStr}`;
},
});
}
return result;
}
// 导出各工具默认配置和类型
export { DEFAULT_FETCH_CONFIG } from "./executors/fetch/config";
export type { FetchToolConfig } from "./executors/fetch/config";
export { DEFAULT_CALCULATOR_CONFIG } from "./executors/calculator/config";
export type { CalculatorToolConfig } from "./executors/calculator/config";
export { DEFAULT_DATETIME_CONFIG } from "./executors/datetime/config";
export type { DatetimeToolConfig } from "./executors/datetime/config";
export { DEFAULT_UUID_CONFIG } from "./executors/uuid/config";
export type { UuidToolConfig } from "./executors/uuid/config";
export { DEFAULT_BASE64_CONFIG } from "./executors/base64/config";
export type { Base64ToolConfig } from "./executors/base64/config";
export { DEFAULT_JSON_FORMATTER_CONFIG } from "./executors/json-formatter/config";
export type { JsonFormatterToolConfig } from "./executors/json-formatter/config";
export { DEFAULT_REGEX_TESTER_CONFIG } from "./executors/regex-tester/config";
export type { RegexTesterToolConfig } from "./executors/regex-tester/config";
export type { ToolExecutor, ToolContext, ToolResult } from "./registry";