返回 oh-my-ppt
factory.ts
根目录 / src / main / agent-runtime / agent / factory.ts
1 import log from 'electron-log/main.js'
2 import { createDeepAgent } from 'deepagents'
3 import { buildDeckAgentSystemPrompt } from '../prompt/composers/deck-system'
4 import { buildEditAgentSystemPrompt } from '../prompt/composers/edit-system'
5 import { createSessionBoundDeckTools } from '../tools/deck-tools'
6 import { getRequiredProductSkillNamesForSlideSize } from '../../product-skills'
7 import { resolveModel } from '../model/resolve'
8 import type { ModelRuntimeConfig } from '../model/usage'
9 import { attachProductSkillsBackend } from '../skills/backend'
10 import { createProductGeneralPurposeSubagent, GuardedFilesystemBackend } from './backend'
11 import type { DeepAgentStreamResult, SessionDeckGenerationContext } from './types'
12
13 export type CreateSessionEditAgentArgs = {
14 provider: string
15 apiKey: string
16 model: string
17 baseUrl?: string
18 temperature?: number
19 maxTokens?: number
20 modelRuntime?: ModelRuntimeConfig
21 styleId?: string | null
22 context: SessionDeckGenerationContext
23 }
24
25 export type CreateSessionDeckAgentArgs = CreateSessionEditAgentArgs & {
26 systemPromptAddendum?: string
27 }
28
29 function shouldBlockNativeEditFile(context: SessionDeckGenerationContext): boolean {
30 if (context.editScope === 'presentation-container') return true
31 return !Boolean(context.selectedSelector?.trim())
32 }
33
34 function shouldBlockNativeWriteFile(context: SessionDeckGenerationContext): boolean {
35 // Every edit scope has a narrower write path with scope and validation enforcement:
36 // selector -> edit_file, page -> update_single_page_file,
37 // deck -> update_page_file, container -> set_index_transition.
38 return context.mode === 'edit'
39 }
40
41 export function createSessionEditAgent(args: CreateSessionEditAgentArgs): DeepAgentStreamResult {
42 const model = resolveModel(
43 args.provider,
44 args.apiKey,
45 args.model,
46 args.baseUrl,
47 args.temperature,
48 args.maxTokens,
49 args.modelRuntime
50 )
51 const context: SessionDeckGenerationContext = {
52 ...args.context,
53 provider: args.provider,
54 model: args.model
55 }
56 const disableNativeEditFile = shouldBlockNativeEditFile(context)
57 const disableNativeWriteFile = shouldBlockNativeWriteFile(context)
58 const backend = new GuardedFilesystemBackend({
59 rootDir: context.projectDir,
60 virtualMode: true,
61 disableEditFile: disableNativeEditFile,
62 disableWriteFile: disableNativeWriteFile,
63 editBlockedReason: disableNativeEditFile
64 ? '当前编辑任务禁止使用 edit_file。请改用 update_single_page_file(pageId, content) 或 update_page_file(pageId, content)。'
65 : undefined,
66 writeBlockedReason:
67 '当前编辑任务禁止使用 write_file。请使用 update_single_page_file(pageId, content)、update_page_file(pageId, content) 或允许的 edit_file。'
68 })
69 const requiredSkillNames = getRequiredProductSkillNamesForSlideSize(context.slideSize)
70 const agentBackend = attachProductSkillsBackend(backend, 'session-edit', requiredSkillNames)
71 const tools = createSessionBoundDeckTools(context)
72 const systemPrompt = buildEditAgentSystemPrompt(args.styleId, context)
73 const hasSelector = Boolean(context.selectedSelector?.trim())
74 const isDeckEdit = context.mode === 'edit' && context.editScope === 'deck'
75 const isContainerEdit = context.mode === 'edit' && context.editScope === 'presentation-container'
76 const promptMode = isContainerEdit
77 ? 'container'
78 : hasSelector
79 ? 'selector'
80 : isDeckEdit
81 ? 'deck'
82 : 'single-page'
83
84 log.info('[deepagent] create session edit agent', {
85 sessionId: context.sessionId,
86 provider: args.provider,
87 model: args.model,
88 styleId: args.styleId || '',
89 projectDir: context.projectDir,
90 indexPath: context.indexPath,
91 selectedPageId: context.selectedPageId,
92 selectPageIds: context.selectPageIds,
93 disableNativeEditFile,
94 disableNativeWriteFile,
95 promptMode,
96 skillsEnabled: agentBackend.enabled,
97 requiredSkillNames
98 })
99
100 return createDeepAgent({
101 model: model as any,
102 backend: agentBackend.backend,
103 systemPrompt,
104 tools: tools as any,
105 middleware: agentBackend.middleware as any,
106 subagents: createProductGeneralPurposeSubagent({
107 model,
108 tools,
109 backend: agentBackend.backend,
110 skillSource: agentBackend.skillSource,
111 requiredSkillNames
112 })
113 })
114 }
115
116 export function createSessionDeckAgent(args: CreateSessionDeckAgentArgs): DeepAgentStreamResult {
117 const model = resolveModel(
118 args.provider,
119 args.apiKey,
120 args.model,
121 args.baseUrl,
122 args.temperature,
123 args.maxTokens,
124 args.modelRuntime
125 )
126 const context: SessionDeckGenerationContext = {
127 ...args.context,
128 provider: args.provider,
129 model: args.model
130 }
131 const backend = new GuardedFilesystemBackend({
132 rootDir: context.projectDir,
133 virtualMode: true,
134 disableEditFile: true,
135 editBlockedReason: context.templatePageReadRequired
136 ? '当前模板生成任务禁止使用 edit_file。请使用 update_template_page_file(pageId, content)。'
137 : '当前生成/全局编辑任务禁止使用 edit_file。请使用 update_single_page_file(pageId, content) 或 update_page_file(pageId, content)。'
138 })
139 const requiredSkillNames = getRequiredProductSkillNamesForSlideSize(context.slideSize)
140 const agentBackend = attachProductSkillsBackend(backend, 'session-deck', requiredSkillNames)
141 const getToolName = (tool: unknown): string => {
142 const maybe = tool as { name?: unknown; lc_kwargs?: { name?: unknown } }
143 if (typeof maybe.name === 'string') return maybe.name
144 if (typeof maybe.lc_kwargs?.name === 'string') return maybe.lc_kwargs.name
145 return ''
146 }
147 const tools = createSessionBoundDeckTools(context)
148 const systemPrompt = [
149 buildDeckAgentSystemPrompt(args.styleId, context),
150 args.systemPromptAddendum?.trim() || ''
151 ]
152 .filter(Boolean)
153 .join('\n\n')
154
155 log.info('[deepagent] create session deck agent', {
156 sessionId: context.sessionId,
157 provider: args.provider,
158 model: args.model,
159 styleId: args.styleId || '',
160 projectDir: context.projectDir,
161 indexPath: context.indexPath,
162 selectedPageId: context.selectedPageId,
163 skillsEnabled: agentBackend.enabled,
164 requiredSkillNames,
165 selectedPagePath:
166 context.selectedPageId && context.pageFileMap[context.selectedPageId]
167 ? context.pageFileMap[context.selectedPageId]
168 : '',
169 totalPages: context.outlineTitles.length,
170 toolNames: tools.map((tool) => getToolName(tool)).filter((name) => name.length > 0)
171 })
172
173 return createDeepAgent({
174 model: model as any,
175 backend: agentBackend.backend,
176 systemPrompt,
177 tools: tools as any,
178 middleware: agentBackend.middleware as any,
179 subagents: createProductGeneralPurposeSubagent({
180 model,
181 tools,
182 backend: agentBackend.backend,
183 skillSource: agentBackend.skillSource,
184 requiredSkillNames
185 })
186 })
187 }
188
188 lines TYPESCRIPT