返回 oh-my-ppt
backend.ts
根目录 / src / main / agent-runtime / agent / backend.ts
1 import type { BaseLanguageModel } from '@langchain/core/language_models/base'
2 import path from 'path'
3 import {
4 CompositeBackend,
5 FilesystemBackend,
6 GENERAL_PURPOSE_SUBAGENT,
7 type EditResult,
8 type WriteResult
9 } from 'deepagents'
10 import { createProductSkillsMiddlewareSet } from '../skills/backend'
11 import type { RequiredProductSkillName } from '../../product-skills'
12
13 const normalizeVirtualPath = (filePath: string): string => {
14 const normalized = filePath.trim().replace(/\\/g, '/')
15 return path.posix.normalize(normalized.startsWith('/') ? normalized : `/${normalized}`)
16 }
17
18 export class GuardedFilesystemBackend extends FilesystemBackend {
19 constructor(
20 options: { rootDir?: string; virtualMode?: boolean; maxFileSizeMb?: number } & {
21 disableEditFile?: boolean
22 disableWriteFile?: boolean
23 allowedEditPaths?: readonly string[]
24 editBlockedReason?: string
25 writeBlockedReason?: string
26 }
27 ) {
28 super(options)
29 this.disableEditFile = Boolean(options.disableEditFile)
30 this.disableWriteFile = Boolean(options.disableWriteFile)
31 this.allowedEditPaths = options.allowedEditPaths
32 ? new Set(options.allowedEditPaths.map(normalizeVirtualPath))
33 : undefined
34 this.editBlockedReason =
35 options.editBlockedReason ||
36 '当前任务禁止调用 edit_file。请使用 update_single_page_file(pageId, content) 或 update_page_file(pageId, content)。'
37 this.writeBlockedReason =
38 options.writeBlockedReason || '当前任务禁止调用 write_file。请使用受控的页面写入工具。'
39 }
40
41 private readonly disableEditFile: boolean
42 private readonly disableWriteFile: boolean
43 private readonly allowedEditPaths: ReadonlySet<string> | undefined
44 private readonly editBlockedReason: string
45 private readonly writeBlockedReason: string
46
47 async write(filePath: string, content: string): Promise<WriteResult> {
48 if (this.disableWriteFile) return { error: this.writeBlockedReason }
49 return super.write(filePath, content)
50 }
51
52 async edit(
53 filePath: string,
54 oldString: string,
55 newString: string,
56 replaceAll?: boolean
57 ): Promise<EditResult> {
58 if (this.disableEditFile) return { error: this.editBlockedReason }
59 if (this.allowedEditPaths && !this.allowedEditPaths.has(normalizeVirtualPath(filePath))) {
60 const allowedPaths = [...this.allowedEditPaths].join(', ')
61 return {
62 error:
63 `当前精修任务只能使用 edit_file 修改目标页面${allowedPaths ? `:${allowedPaths}` : ''}。`
64 }
65 }
66 return super.edit(filePath, oldString, newString, replaceAll)
67 }
68 }
69
70 export function createProductGeneralPurposeSubagent(args: {
71 model: BaseLanguageModel
72 tools: unknown[]
73 backend: FilesystemBackend | CompositeBackend
74 skillSource: string
75 requiredSkillNames: readonly RequiredProductSkillName[]
76 }): any[] {
77 if (!(args.backend instanceof CompositeBackend)) return []
78 return [
79 {
80 ...GENERAL_PURPOSE_SUBAGENT,
81 model: args.model as any,
82 tools: args.tools as any,
83 middleware: createProductSkillsMiddlewareSet(
84 args.backend,
85 args.skillSource,
86 'general-purpose',
87 args.requiredSkillNames
88 )
89 }
90 ]
91 }
92
92 lines TYPESCRIPT