|
1 | | -import { getAgentActionUtilsRecord } from '../../../shared/AgentUtils' |
2 | | -import { AgentActionUtil } from '../../../shared/actions/AgentActionUtil' |
3 | | -import { AgentAction } from '../../../shared/types/AgentAction' |
4 | | -import { zoomOutCanvas } from '../../../shared/zoomOutCanvas' |
| 1 | +// Plan 005-B: factory helper (defineCatalogTool) + domain sub-builders. |
5 | 2 | import { TldrawAgent } from '../../agent/TldrawAgent' |
6 | | -import { searchCommonsImages } from '../../commons/commonsImages' |
7 | | -import { insertCommonsImage } from '../../commons/insertCommonsImage' |
8 | | -import { previewMermaidDiagram } from '../../mermaid/mermaidDiagram' |
9 | | -import { |
10 | | - ACTION_PURPOSES, |
11 | | - ACTION_TOOL_NAMES, |
12 | | - CAMERA_PURPOSES, |
13 | | - COMMONS_PURPOSES, |
14 | | - EMBED_PURPOSES, |
15 | | - GRID_PURPOSES, |
16 | | - MERMAID_PURPOSES, |
17 | | - NEW_DRAW_PAGE_WORKFLOW, |
18 | | - PAGE_PURPOSES, |
19 | | - UNTRUSTED_ACTION_TOOL_NAMES, |
20 | | - type ToolName, |
21 | | -} from '../constants' |
22 | | -import { inspectEmbeds } from '../embedTools' |
23 | | -import { createActionInputSchema, executeAction } from '../execute/executeAction' |
24 | | -import { arrangeGrid } from '../gridTools' |
25 | | -import { createCanvasPage, inspectCanvasPages } from '../pageTools' |
26 | | -import { |
27 | | - ADD_COMMONS_IMAGE_INPUT_SCHEMA, |
28 | | - ARRANGE_GRID_INPUT_SCHEMA, |
29 | | - AddCommonsImageInput, |
30 | | - ArrangeGridInput, |
31 | | - CREATE_MERMAID_DIAGRAM_INPUT_SCHEMA, |
32 | | - CREATE_PAGE_INPUT_SCHEMA, |
33 | | - CreateMermaidDiagramInput, |
34 | | - CreatePageInput, |
35 | | - DESCRIBE_TOOLS_INPUT_SCHEMA, |
36 | | - DescribeToolsInput, |
37 | | - INSPECT_EMBEDS_INPUT_SCHEMA, |
38 | | - INSPECT_PAGES_INPUT_SCHEMA, |
39 | | - InspectEmbedsInput, |
40 | | - InspectPagesInput, |
41 | | - LIST_TOOLS_INPUT_SCHEMA, |
42 | | - SEARCH_COMMONS_IMAGES_INPUT_SCHEMA, |
43 | | - SearchCommonsImagesInput, |
44 | | - ZOOM_OUT_INPUT_SCHEMA, |
45 | | - ZoomOutInput, |
46 | | -} from '../schemas/standaloneSchemas' |
47 | | -import { invalidArgumentsResult, isAbortError, toolErrorResult } from '../toolResults' |
| 3 | +import type { ToolName } from '../constants' |
48 | 4 | import type { CatalogEntry } from '../types' |
| 5 | +import { registerActionTools } from './actionTools' |
| 6 | +import { registerDiscoveryTools } from './discoveryTools' |
| 7 | +import { registerStandaloneTools } from './standaloneTools' |
49 | 8 |
|
50 | 9 | export function createCatalog(agent: TldrawAgent) { |
51 | | - const actionUtils = getAgentActionUtilsRecord(agent) |
52 | 10 | const catalog = new Map<ToolName, CatalogEntry>() |
53 | | - |
54 | | - for (const name of ACTION_TOOL_NAMES) { |
55 | | - const util = actionUtils[name] as AgentActionUtil<AgentAction> |
56 | | - const actionSchema = util.getSchema() |
57 | | - if (!actionSchema) { |
58 | | - console.warn(`WebMCP action tool "${name}" has no schema and was not registered.`) |
59 | | - continue |
60 | | - } |
61 | | - |
62 | | - catalog.set(name, { |
63 | | - name, |
64 | | - purpose: ACTION_PURPOSES[name], |
65 | | - inputSchema: createActionInputSchema(name, actionSchema), |
66 | | - execute: (input, context) => executeAction(agent, util, actionSchema, name, input, context), |
67 | | - readOnly: false, |
68 | | - untrustedContent: UNTRUSTED_ACTION_TOOL_NAMES.has(name), |
69 | | - }) |
70 | | - } |
71 | | - |
72 | | - catalog.set('search_commons_images', { |
73 | | - name: 'search_commons_images', |
74 | | - purpose: COMMONS_PURPOSES.search_commons_images, |
75 | | - inputSchema: SEARCH_COMMONS_IMAGES_INPUT_SCHEMA, |
76 | | - execute: async (input, context) => { |
77 | | - const parsed = SearchCommonsImagesInput.safeParse(input) |
78 | | - if (!parsed.success) return invalidArgumentsResult('search_commons_images', parsed.error) |
79 | | - |
80 | | - try { |
81 | | - const images = await searchCommonsImages(parsed.data.query, context?.signal) |
82 | | - return { |
83 | | - ok: true, |
84 | | - tool: 'search_commons_images', |
85 | | - query: parsed.data.query, |
86 | | - count: images.length, |
87 | | - images, |
88 | | - } |
89 | | - } catch (error) { |
90 | | - return toolErrorResult('search_commons_images', error) |
91 | | - } |
92 | | - }, |
93 | | - readOnly: true, |
94 | | - untrustedContent: true, |
95 | | - }) |
96 | | - |
97 | | - catalog.set('add_commons_image', { |
98 | | - name: 'add_commons_image', |
99 | | - purpose: COMMONS_PURPOSES.add_commons_image, |
100 | | - inputSchema: ADD_COMMONS_IMAGE_INPUT_SCHEMA, |
101 | | - execute: async (input, context) => { |
102 | | - const parsed = AddCommonsImageInput.safeParse(input) |
103 | | - if (!parsed.success) return invalidArgumentsResult('add_commons_image', parsed.error) |
104 | | - |
105 | | - try { |
106 | | - const result = await insertCommonsImage(agent.editor, parsed.data, context?.signal) |
107 | | - return { ok: true, tool: 'add_commons_image', ...result } |
108 | | - } catch (error) { |
109 | | - return toolErrorResult('add_commons_image', error) |
110 | | - } |
111 | | - }, |
112 | | - readOnly: false, |
113 | | - untrustedContent: true, |
114 | | - }) |
115 | | - |
116 | | - catalog.set('create_mermaid_diagram', { |
117 | | - name: 'create_mermaid_diagram', |
118 | | - purpose: MERMAID_PURPOSES.create_mermaid_diagram, |
119 | | - inputSchema: CREATE_MERMAID_DIAGRAM_INPUT_SCHEMA, |
120 | | - execute: async (input, context) => { |
121 | | - if (context?.signal?.aborted) { |
122 | | - return { ok: false, tool: 'create_mermaid_diagram', error: 'Tool call was aborted.' } |
123 | | - } |
124 | | - |
125 | | - const parsed = CreateMermaidDiagramInput.safeParse(input) |
126 | | - if (!parsed.success) return invalidArgumentsResult('create_mermaid_diagram', parsed.error) |
127 | | - |
128 | | - try { |
129 | | - const result = await previewMermaidDiagram(agent.editor, parsed.data, context?.signal) |
130 | | - return { ok: true, tool: 'create_mermaid_diagram', ...result } |
131 | | - } catch (error) { |
132 | | - if (isAbortError(error)) { |
133 | | - return { ok: false, tool: 'create_mermaid_diagram', error: 'Tool call was aborted.' } |
134 | | - } |
135 | | - return toolErrorResult('create_mermaid_diagram', error) |
136 | | - } |
137 | | - }, |
138 | | - readOnly: false, |
139 | | - untrustedContent: false, |
140 | | - }) |
141 | | - |
142 | | - catalog.set('create_page', { |
143 | | - name: 'create_page', |
144 | | - purpose: PAGE_PURPOSES.create_page, |
145 | | - inputSchema: CREATE_PAGE_INPUT_SCHEMA, |
146 | | - execute: (input, context) => { |
147 | | - if (context?.signal?.aborted) { |
148 | | - return { ok: false, tool: 'create_page', error: 'Tool call was aborted.' } |
149 | | - } |
150 | | - |
151 | | - const parsed = CreatePageInput.safeParse(input ?? {}) |
152 | | - if (!parsed.success) return invalidArgumentsResult('create_page', parsed.error) |
153 | | - |
154 | | - try { |
155 | | - return { ok: true, tool: 'create_page', ...createCanvasPage(agent.editor) } |
156 | | - } catch (error) { |
157 | | - return toolErrorResult('create_page', error) |
158 | | - } |
159 | | - }, |
160 | | - readOnly: false, |
161 | | - untrustedContent: false, |
162 | | - }) |
163 | | - |
164 | | - catalog.set('inspect_pages', { |
165 | | - name: 'inspect_pages', |
166 | | - purpose: PAGE_PURPOSES.inspect_pages, |
167 | | - inputSchema: INSPECT_PAGES_INPUT_SCHEMA, |
168 | | - execute: (input, context) => { |
169 | | - if (context?.signal?.aborted) { |
170 | | - return { ok: false, tool: 'inspect_pages', error: 'Tool call was aborted.' } |
171 | | - } |
172 | | - |
173 | | - const parsed = InspectPagesInput.safeParse(input ?? {}) |
174 | | - if (!parsed.success) return invalidArgumentsResult('inspect_pages', parsed.error) |
175 | | - |
176 | | - const totalPages = agent.editor.getPages().length |
177 | | - if (parsed.data.pageNumber !== undefined && parsed.data.pageNumber > totalPages) { |
178 | | - return { |
179 | | - ok: false, |
180 | | - tool: 'inspect_pages', |
181 | | - error: `Page number ${parsed.data.pageNumber} does not exist.`, |
182 | | - totalPages, |
183 | | - } |
184 | | - } |
185 | | - |
186 | | - return { |
187 | | - ok: true, |
188 | | - tool: 'inspect_pages', |
189 | | - ...inspectCanvasPages(agent.editor, parsed.data.pageNumber), |
190 | | - } |
191 | | - }, |
192 | | - readOnly: true, |
193 | | - untrustedContent: false, |
194 | | - }) |
195 | | - |
196 | | - catalog.set('inspect_embeds', { |
197 | | - name: 'inspect_embeds', |
198 | | - purpose: EMBED_PURPOSES.inspect_embeds, |
199 | | - inputSchema: INSPECT_EMBEDS_INPUT_SCHEMA, |
200 | | - execute: (input, context) => { |
201 | | - if (context?.signal?.aborted) { |
202 | | - return { ok: false, tool: 'inspect_embeds', error: 'Tool call was aborted.' } |
203 | | - } |
204 | | - const parsed = InspectEmbedsInput.safeParse(input ?? {}) |
205 | | - if (!parsed.success) return invalidArgumentsResult('inspect_embeds', parsed.error) |
206 | | - try { |
207 | | - return { ok: true, tool: 'inspect_embeds', ...inspectEmbeds(agent.editor, parsed.data) } |
208 | | - } catch (error) { |
209 | | - return toolErrorResult('inspect_embeds', error) |
210 | | - } |
211 | | - }, |
212 | | - readOnly: true, |
213 | | - untrustedContent: true, |
214 | | - }) |
215 | | - |
216 | | - catalog.set('zoom_out', { |
217 | | - name: 'zoom_out', |
218 | | - purpose: CAMERA_PURPOSES.zoom_out, |
219 | | - inputSchema: ZOOM_OUT_INPUT_SCHEMA, |
220 | | - execute: (input, context) => { |
221 | | - if (context?.signal?.aborted) { |
222 | | - return { ok: false, tool: 'zoom_out', error: 'Tool call was aborted.' } |
223 | | - } |
224 | | - const parsed = ZoomOutInput.safeParse(input ?? {}) |
225 | | - if (!parsed.success) return invalidArgumentsResult('zoom_out', parsed.error) |
226 | | - try { |
227 | | - return { ok: true, tool: 'zoom_out', ...zoomOutCanvas(agent.editor, parsed.data) } |
228 | | - } catch (error) { |
229 | | - return toolErrorResult('zoom_out', error) |
230 | | - } |
231 | | - }, |
232 | | - readOnly: false, |
233 | | - untrustedContent: false, |
234 | | - }) |
235 | | - |
236 | | - catalog.set('arrange_grid', { |
237 | | - name: 'arrange_grid', |
238 | | - purpose: GRID_PURPOSES.arrange_grid, |
239 | | - inputSchema: ARRANGE_GRID_INPUT_SCHEMA, |
240 | | - execute: (input, context) => { |
241 | | - if (context?.signal?.aborted) { |
242 | | - return { ok: false, tool: 'arrange_grid', error: 'Tool call was aborted.' } |
243 | | - } |
244 | | - const parsed = ArrangeGridInput.safeParse(input) |
245 | | - if (!parsed.success) return invalidArgumentsResult('arrange_grid', parsed.error) |
246 | | - try { |
247 | | - return { ok: true, tool: 'arrange_grid', ...arrangeGrid(agent.editor, parsed.data) } |
248 | | - } catch (error) { |
249 | | - return toolErrorResult('arrange_grid', error) |
250 | | - } |
251 | | - }, |
252 | | - readOnly: false, |
253 | | - untrustedContent: false, |
254 | | - }) |
255 | | - |
256 | | - catalog.set('list_tools', { |
257 | | - name: 'list_tools', |
258 | | - purpose: |
259 | | - 'List every registered draw-app tool with a one-line summary and the required new-drawing page workflow; call this first to choose tools.', |
260 | | - inputSchema: LIST_TOOLS_INPUT_SCHEMA, |
261 | | - execute: () => ({ |
262 | | - workflow: NEW_DRAW_PAGE_WORKFLOW, |
263 | | - tools: [...catalog.values()].map(({ name, purpose }) => ({ name, purpose })), |
264 | | - }), |
265 | | - readOnly: true, |
266 | | - untrustedContent: false, |
267 | | - }) |
268 | | - |
269 | | - catalog.set('describe_tools', { |
270 | | - name: 'describe_tools', |
271 | | - purpose: 'Return full argument schemas for 1–10 unique tools before calling those compactly registered tools.', |
272 | | - inputSchema: DESCRIBE_TOOLS_INPUT_SCHEMA, |
273 | | - execute: (input) => { |
274 | | - const parsed = DescribeToolsInput.safeParse(input) |
275 | | - if (!parsed.success) return invalidArgumentsResult('describe_tools', parsed.error) |
276 | | - |
277 | | - return { |
278 | | - tools: parsed.data.names.map((name) => { |
279 | | - const entry = catalog.get(name) |
280 | | - return entry |
281 | | - ? { name: entry.name, purpose: entry.purpose, inputSchema: entry.inputSchema } |
282 | | - : { name, error: 'Tool is not registered.' } |
283 | | - }), |
284 | | - } |
285 | | - }, |
286 | | - readOnly: true, |
287 | | - untrustedContent: false, |
288 | | - }) |
289 | | - |
| 11 | + registerActionTools(agent, catalog) |
| 12 | + registerStandaloneTools(agent, catalog) |
| 13 | + registerDiscoveryTools(catalog) |
290 | 14 | return catalog |
291 | 15 | } |
0 commit comments