import { z } from "zod"; import type { JSONSchema7 } from "json-schema"; import type { ToolExecutor, ToolContext, ToolResult } from "../../registry"; import type { DocumentToolConfig } from "./config"; import { createDocument, getDocumentById, listDocuments, updateDocument, deleteDocument, deserializeTags, type AgentDocumentType, } from "../../../agent-document"; const documentActionSchema = z.enum(["create", "read", "update", "delete", "list"]); export const documentInputSchema = z.object({ action: documentActionSchema.describe("文档操作类型"), title: z.string().min(1).max(200).optional().describe("文档标题(create/update 必填)"), content: z.string().max(50000).optional().describe("文档正文(create/update 必填)"), type: z.enum(["memory", "document"]).optional().describe("文档类型,默认 document"), tags: z.array(z.string()).optional().describe("标签列表"), id: z.number().int().positive().optional().describe("文档 ID(read/update/delete 必填)"), keyword: z.string().optional().describe("搜索关键词(list 时可选)"), page: z.number().int().positive().optional().describe("页码(list 时可选,默认 1)"), pageSize: z.number().int().positive().max(100).optional().describe("每页数量(list 时可选,默认 20)"), fullContent: z.boolean().optional().describe("read 时是否返回完整 content,默认 false(仅返回 summary)"), }); type DocumentInput = z.infer; function toISO(date: Date | null | undefined): string | null { return date ? date.toISOString() : null; } export const documentExecutor: ToolExecutor = { buildInputSchema(_config: DocumentToolConfig): JSONSchema7 { return { type: "object", properties: { action: { type: "string", enum: ["create", "read", "update", "delete", "list"], description: "文档操作类型", }, title: { type: "string", description: "文档标题(create/update 必填)" }, content: { type: "string", description: "文档正文(create/update 必填)" }, type: { type: "string", enum: ["memory", "document"], description: "文档类型,默认 document", }, tags: { type: "array", items: { type: "string" }, description: "标签列表" }, id: { type: "number", description: "文档 ID(read/update/delete 必填)" }, keyword: { type: "string", description: "搜索关键词(list 时可选)" }, page: { type: "number", description: "页码(list 时可选,默认 1)" }, pageSize: { type: "number", description: "每页数量(list 时可选,默认 20)" }, fullContent: { type: "boolean", description: "read 时是否返回完整 content,默认 false" }, }, required: ["action"], }; }, buildDescription(config: DocumentToolConfig): string { const approvalDesc = config.approvalRequiredActions.length > 0 ? `需要审批的操作: ${config.approvalRequiredActions.join(", ")}。` : "无需审批。"; return `创建、读取、更新、删除、列出文档(memory/document 两种类型)。${approvalDesc}用于上下文记忆持久化和交付物产出。`; }, async execute( input: unknown, config: DocumentToolConfig, ctx: ToolContext, ): Promise { const start = Date.now(); const parsed = documentInputSchema.safeParse(input); if (!parsed.success) { return { success: false, data: null, error: `输入参数校验失败: ${parsed.error.message}`, metadata: { durationMs: Date.now() - start }, }; } const data = parsed.data; if (ctx.userId === null || ctx.userId === undefined) { return { success: false, data: null, error: "无法识别当前用户:会话上下文中缺少 userId。该工具需要在登录会话中调用。", metadata: { durationMs: Date.now() - start }, }; } const userId = ctx.userId; try { switch (data.action) { case "create": { if (!data.title || !data.content) { return { success: false, data: null, error: "create 操作需要 title 和 content 参数", metadata: { durationMs: Date.now() - start }, }; } if (data.content.length > config.maxContentLength) { return { success: false, data: null, error: `content 长度超过上限 ${config.maxContentLength}`, metadata: { durationMs: Date.now() - start }, }; } const doc = await createDocument({ userId, agentId: ctx.agentId ?? null, type: data.type ?? config.defaultType, title: data.title, content: data.content, tags: data.tags ?? null, }); return { success: true, data: { id: doc.id, type: doc.type, title: doc.title, summary: doc.summary, tags: deserializeTags(doc.tags), createdAt: toISO(doc.createdAt), updatedAt: toISO(doc.updatedAt), }, metadata: { durationMs: Date.now() - start }, }; } case "read": { if (!data.id) { return { success: false, data: null, error: "read 操作需要 id 参数", metadata: { durationMs: Date.now() - start }, }; } const doc = await getDocumentById(data.id, userId); if (!doc) { return { success: false, data: null, error: `文档不存在或无权访问 (id=${data.id})`, metadata: { durationMs: Date.now() - start }, }; } const result: Record = { id: doc.id, type: doc.type, title: doc.title, summary: doc.summary, tags: deserializeTags(doc.tags), createdAt: toISO(doc.createdAt), updatedAt: toISO(doc.updatedAt), }; if (data.fullContent) { result.content = doc.content; } return { success: true, data: result, metadata: { durationMs: Date.now() - start }, }; } case "update": { if (!data.id) { return { success: false, data: null, error: "update 操作需要 id 参数", metadata: { durationMs: Date.now() - start }, }; } if (data.content && data.content.length > config.maxContentLength) { return { success: false, data: null, error: `content 长度超过上限 ${config.maxContentLength}`, metadata: { durationMs: Date.now() - start }, }; } const updated = await updateDocument(data.id, userId, { title: data.title, content: data.content, tags: data.tags, }); if (!updated) { return { success: false, data: null, error: `文档不存在或无权访问 (id=${data.id})`, metadata: { durationMs: Date.now() - start }, }; } return { success: true, data: { id: updated.id, type: updated.type, title: updated.title, summary: updated.summary, tags: deserializeTags(updated.tags), createdAt: toISO(updated.createdAt), updatedAt: toISO(updated.updatedAt), }, metadata: { durationMs: Date.now() - start }, }; } case "delete": { if (!data.id) { return { success: false, data: null, error: "delete 操作需要 id 参数", metadata: { durationMs: Date.now() - start }, }; } const deleted = await deleteDocument(data.id, userId); if (!deleted) { return { success: false, data: null, error: `文档不存在或无权访问 (id=${data.id})`, metadata: { durationMs: Date.now() - start }, }; } return { success: true, data: { id: data.id, deleted: true }, metadata: { durationMs: Date.now() - start }, }; } case "list": { const result = await listDocuments({ userId, agentId: ctx.agentId ?? undefined, type: data.type as AgentDocumentType | undefined, keyword: data.keyword, page: data.page, pageSize: data.pageSize ?? config.listPageSize, }); return { success: true, data: { items: result.items.map((item) => ({ id: item.id, type: item.type, title: item.title, summary: item.summary, tags: deserializeTags(item.tags), createdAt: toISO(item.createdAt), updatedAt: toISO(item.updatedAt), })), total: result.total, page: result.page, pageSize: result.pageSize, hasMore: result.hasMore, }, metadata: { durationMs: Date.now() - start }, }; } default: return { success: false, data: null, error: `未知的 action: ${data.action}`, metadata: { durationMs: Date.now() - start }, }; } } catch (e) { return { success: false, data: null, error: e instanceof Error ? e.message : String(e), metadata: { durationMs: Date.now() - start }, }; } }, };