@decocms/runtime 1.0.0-alpha.10 → 1.0.0-alpha.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/mastra.ts DELETED
@@ -1,670 +0,0 @@
1
- /* oxlint-disable no-explicit-any */
2
- /* oxlint-disable ban-types */
3
- import { HttpServerTransport } from "@deco/mcp/http";
4
- import {
5
- createTool as mastraCreateTool,
6
- Tool,
7
- type ToolAction,
8
- type ToolExecutionContext,
9
- } from "@mastra/core";
10
- import { RuntimeContext } from "@mastra/core/di";
11
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
12
- import { z } from "zod";
13
- import { zodToJsonSchema } from "zod-to-json-schema";
14
- import type { DefaultEnv } from "./index.ts";
15
- import {
16
- ResourceCreateInputSchema,
17
- ResourceCreateOutputSchema,
18
- ResourceDeleteInputSchema,
19
- ResourceDeleteOutputSchema,
20
- ResourceSearchInputSchema,
21
- ResourceSearchOutputSchema,
22
- ResourcesListOutputSchema,
23
- ResourcesReadInputSchema,
24
- ResourcesReadOutputSchema,
25
- ResourceUpdateInputSchema,
26
- ResourceUpdateOutputSchema,
27
- } from "./resources.ts";
28
- import { createStateValidationTool, State } from "./state.ts";
29
- import { ViewsListOutputSchema } from "./views.ts";
30
-
31
- export const createRuntimeContext = (prev?: RuntimeContext<AppContext>) => {
32
- const runtimeContext = new RuntimeContext<AppContext>();
33
- const store = State.getStore();
34
- if (!store) {
35
- if (prev) {
36
- return prev;
37
- }
38
- throw new Error("Missing context, did you forget to call State.bind?");
39
- }
40
- const { env, ctx, req } = store;
41
- runtimeContext.set("env", env);
42
- runtimeContext.set("ctx", ctx);
43
- runtimeContext.set("req", req);
44
- return runtimeContext;
45
- };
46
-
47
- /**
48
- * creates a private tool that always ensure for athentication before being executed
49
- */
50
- export function createPrivateTool<
51
- TSchemaIn extends z.ZodSchema = z.ZodSchema,
52
- TSchemaOut extends z.ZodSchema | undefined = undefined,
53
- TSuspendSchema extends z.ZodSchema = z.ZodSchema,
54
- TResumeSchema extends z.ZodSchema = z.ZodSchema,
55
- TContext extends
56
- ToolExecutionContext<TSchemaIn> = ToolExecutionContext<TSchemaIn>,
57
- TExecute extends ToolAction<
58
- TSchemaIn,
59
- TSchemaOut,
60
- any,
61
- any,
62
- TContext
63
- >["execute"] = ToolAction<
64
- TSchemaIn,
65
- TSchemaOut,
66
- any,
67
- any,
68
- TContext
69
- >["execute"],
70
- >(
71
- opts: ToolAction<
72
- TSchemaIn,
73
- TSchemaOut,
74
- TSuspendSchema,
75
- TResumeSchema,
76
- TContext
77
- > & {
78
- execute?: TExecute;
79
- },
80
- ): [TSchemaIn, TSchemaOut, TSuspendSchema, TResumeSchema, TExecute] extends [
81
- z.ZodSchema,
82
- z.ZodSchema,
83
- z.ZodSchema,
84
- z.ZodSchema,
85
- Function,
86
- ]
87
- ? Tool<TSchemaIn, TSchemaOut, TSuspendSchema, TResumeSchema, TContext> & {
88
- inputSchema: TSchemaIn;
89
- outputSchema: TSchemaOut;
90
- execute: (context: TContext) => Promise<any>;
91
- }
92
- : Tool<TSchemaIn, TSchemaOut, TSuspendSchema, TResumeSchema, TContext> {
93
- const execute = opts.execute;
94
- if (typeof execute === "function") {
95
- opts.execute = ((input, options) => {
96
- const env = input.runtimeContext.get("env") as DefaultEnv;
97
- if (env) {
98
- env.DECO_REQUEST_CONTEXT.ensureAuthenticated();
99
- }
100
- return execute(input, options);
101
- }) as TExecute;
102
- }
103
- return createTool(opts);
104
- }
105
-
106
- export interface StreamableTool<TSchemaIn extends z.ZodSchema = z.ZodSchema> {
107
- id: string;
108
- inputSchema: TSchemaIn;
109
- streamable?: true;
110
- description?: string;
111
- execute: (input: ToolExecutionContext<TSchemaIn>) => Promise<Response>;
112
- }
113
-
114
- export function createStreamableTool<
115
- TSchemaIn extends z.ZodSchema = z.ZodSchema,
116
- >(streamableTool: StreamableTool<TSchemaIn>): StreamableTool<TSchemaIn> {
117
- return {
118
- ...streamableTool,
119
- execute: (input: ToolExecutionContext<TSchemaIn>) => {
120
- const env = input.runtimeContext.get("env") as DefaultEnv;
121
- if (env) {
122
- env.DECO_REQUEST_CONTEXT.ensureAuthenticated();
123
- }
124
- return streamableTool.execute({
125
- ...input,
126
- runtimeContext: createRuntimeContext(input.runtimeContext),
127
- });
128
- },
129
- };
130
- }
131
-
132
- export function createTool<
133
- TSchemaIn extends z.ZodSchema | undefined = undefined,
134
- TSchemaOut extends z.ZodSchema | undefined = undefined,
135
- TSuspendSchema extends z.ZodSchema = z.ZodSchema,
136
- TResumeSchema extends z.ZodSchema = z.ZodSchema,
137
- TContext extends ToolExecutionContext<
138
- TSchemaIn,
139
- TSuspendSchema,
140
- TResumeSchema
141
- > = ToolExecutionContext<TSchemaIn, TSuspendSchema, TResumeSchema>,
142
- TExecute extends ToolAction<
143
- TSchemaIn,
144
- TSchemaOut,
145
- TSuspendSchema,
146
- TResumeSchema,
147
- TContext
148
- >["execute"] = ToolAction<
149
- TSchemaIn,
150
- TSchemaOut,
151
- TSuspendSchema,
152
- TResumeSchema,
153
- TContext
154
- >["execute"],
155
- >(
156
- opts: ToolAction<
157
- TSchemaIn,
158
- TSchemaOut,
159
- TSuspendSchema,
160
- TResumeSchema,
161
- TContext
162
- > & {
163
- execute?: TExecute;
164
- },
165
- ): [TSchemaIn, TSchemaOut, TSuspendSchema, TResumeSchema, TExecute] extends [
166
- z.ZodSchema,
167
- z.ZodSchema,
168
- z.ZodSchema,
169
- z.ZodSchema,
170
- Function,
171
- ]
172
- ? Tool<TSchemaIn, TSchemaOut, TSuspendSchema, TResumeSchema, TContext> & {
173
- inputSchema: TSchemaIn;
174
- outputSchema: TSchemaOut;
175
- execute: (context: TContext) => Promise<any>;
176
- }
177
- : Tool<TSchemaIn, TSchemaOut, TSuspendSchema, TResumeSchema, TContext> {
178
- // @ts-expect-error - TSchemaIn is not a ZodType
179
- return mastraCreateTool({
180
- ...opts,
181
- execute:
182
- typeof opts?.execute === "function"
183
- ? (((input) => {
184
- return opts.execute!({
185
- ...input,
186
- runtimeContext: createRuntimeContext(input.runtimeContext),
187
- });
188
- }) as TExecute)
189
- : opts.execute,
190
- });
191
- }
192
-
193
- export type ExecWithContext<TF extends (...args: any[]) => any> = (
194
- input: Omit<Parameters<TF>[0], "runtimeContext"> & {
195
- runtimeContext: RuntimeContext<AppContext>;
196
- },
197
- ) => ReturnType<TF>;
198
-
199
- export interface ViewExport {
200
- title: string;
201
- icon: string;
202
- url: string;
203
- tools?: string[];
204
- rules?: string[];
205
- installBehavior?: "none" | "open" | "autoPin";
206
- }
207
-
208
- export type Resources<Env = any, TSchema extends z.ZodTypeAny = never> = Array<
209
- (env: Env & DefaultEnv<TSchema>) => {
210
- name: string;
211
- icon: string;
212
- title: string;
213
- description?: string;
214
- tools: {
215
- read: (args: { uri: string }) => Promise<unknown>;
216
- search: (args: {
217
- term: string;
218
- cursor?: string;
219
- limit?: number;
220
- }) => Promise<unknown>;
221
- create?: (
222
- args: z.infer<typeof ResourceCreateInputSchema>,
223
- ) => Promise<unknown>;
224
- update?: (
225
- args: z.infer<typeof ResourceUpdateInputSchema>,
226
- ) => Promise<unknown>;
227
- delete?: (
228
- args: z.infer<typeof ResourceDeleteInputSchema>,
229
- ) => Promise<unknown>;
230
- };
231
- views?: {
232
- list?: { url?: string; tools?: string[]; rules?: string[] };
233
- detail?: {
234
- url?: string;
235
- mimeTypePattern?: string;
236
- resourceName?: string;
237
- tools?: string[];
238
- rules?: string[];
239
- };
240
- };
241
- }
242
- >;
243
- export interface Integration {
244
- id: string;
245
- appId: string;
246
- }
247
- export type CreatedTool =
248
- | ReturnType<typeof createTool>
249
- | ReturnType<typeof createStreamableTool>;
250
- export function isStreamableTool(tool: CreatedTool): tool is StreamableTool {
251
- return tool && "streamable" in tool && tool.streamable === true;
252
- }
253
- export interface CreateMCPServerOptions<
254
- Env = any,
255
- TSchema extends z.ZodTypeAny = never,
256
- > {
257
- before?: (env: Env & DefaultEnv<TSchema>) => Promise<void> | void;
258
- oauth?: {
259
- state?: TSchema;
260
- scopes?: string[];
261
- };
262
- views?: (
263
- env: Env & DefaultEnv<TSchema>,
264
- ) => Promise<ViewExport[]> | ViewExport[];
265
- resources?: Resources<Env, TSchema>;
266
- tools?:
267
- | Array<
268
- (
269
- env: Env & DefaultEnv<TSchema>,
270
- ) =>
271
- | Promise<CreatedTool>
272
- | CreatedTool
273
- | CreatedTool[]
274
- | Promise<CreatedTool[]>
275
- >
276
- | ((
277
- env: Env & DefaultEnv<TSchema>,
278
- ) => CreatedTool[] | Promise<CreatedTool[]>);
279
- }
280
-
281
- export type Fetch<TEnv = any> = (
282
- req: Request,
283
- env: TEnv,
284
- ctx: ExecutionContext,
285
- ) => Promise<Response> | Response;
286
-
287
- export interface AppContext<TEnv = any> {
288
- env: TEnv;
289
- ctx: { waitUntil: (promise: Promise<any>) => void };
290
- req?: Request;
291
- }
292
-
293
- const decoChatOAuthToolsFor = <TSchema extends z.ZodTypeAny = never>({
294
- state: schema,
295
- scopes,
296
- }: CreateMCPServerOptions<any, TSchema>["oauth"] = {}) => {
297
- const jsonSchema = schema
298
- ? zodToJsonSchema(schema)
299
- : { type: "object", properties: {} };
300
- return [
301
- createTool({
302
- id: "DECO_CHAT_OAUTH_START",
303
- description: "OAuth for Deco Chat",
304
- inputSchema: z.object({
305
- returnUrl: z.string(),
306
- }),
307
- outputSchema: z.object({
308
- stateSchema: z.any(),
309
- scopes: z.array(z.string()).optional(),
310
- }),
311
- execute: () => {
312
- return Promise.resolve({
313
- stateSchema: jsonSchema,
314
- scopes,
315
- });
316
- },
317
- }),
318
- ];
319
- };
320
-
321
- type CallTool = (opts: {
322
- toolCallId: string;
323
- toolCallInput: any;
324
- }) => Promise<any>;
325
-
326
- export type MCPServer<TEnv = any, TSchema extends z.ZodTypeAny = never> = {
327
- fetch: Fetch<TEnv & DefaultEnv<TSchema>>;
328
- callTool: CallTool;
329
- };
330
-
331
- export const createMCPServer = <
332
- TEnv = any,
333
- TSchema extends z.ZodTypeAny = never,
334
- >(
335
- options: CreateMCPServerOptions<TEnv, TSchema>,
336
- ): MCPServer<TEnv, TSchema> => {
337
- const createServer = async (bindings: TEnv & DefaultEnv<TSchema>) => {
338
- await options.before?.(bindings);
339
-
340
- const server = new McpServer(
341
- { name: "@deco/mcp-api", version: "1.0.0" },
342
- { capabilities: { tools: {} } },
343
- );
344
-
345
- // Resolve resources first; build resource tools; append later
346
- const resolvedResources = await Promise.all(
347
- options.resources?.map((r) => r(bindings)) ?? [],
348
- );
349
- const readHandlers = new Map<
350
- string,
351
- (a: { uri: string }) => Promise<any>
352
- >();
353
- const searchHandlers = new Map<
354
- string,
355
- (a: { term: string; cursor?: string; limit?: number }) => Promise<any>
356
- >();
357
- const createHandlers = new Map<string, (a: any) => Promise<any>>();
358
- const updateHandlers = new Map<string, (a: any) => Promise<any>>();
359
- const deleteHandlers = new Map<string, (a: any) => Promise<any>>();
360
- for (const r of resolvedResources) {
361
- if (r?.tools?.read) readHandlers.set(r.name, r.tools.read);
362
- if (r?.tools?.search) searchHandlers.set(r.name, r.tools.search);
363
- if (r?.tools?.create) createHandlers.set(r.name, r.tools.create);
364
- if (r?.tools?.update) updateHandlers.set(r.name, r.tools.update);
365
- if (r?.tools?.delete) deleteHandlers.set(r.name, r.tools.delete);
366
- }
367
- const resourceTools: ReturnType<typeof createTool>[] = [];
368
- if (resolvedResources.length > 0) {
369
- resourceTools.push(
370
- createTool({
371
- id: "DECO_CHAT_RESOURCES_READ",
372
- description: "Read a resource by uri (name + uri)",
373
- inputSchema: ResourcesReadInputSchema,
374
- outputSchema: ResourcesReadOutputSchema,
375
- execute: (input) => {
376
- // @ts-expect-error - input.name is not a string
377
- const fn = readHandlers.get(input.name);
378
- if (!fn) {
379
- // @ts-expect-error - input.name is not a string
380
- throw new Error(`READ not implemented for ${input.name}`);
381
- }
382
- // @ts-expect-error - input.name is not a string
383
- return fn({ uri: input.uri });
384
- },
385
- }),
386
- );
387
- resourceTools.push(
388
- createTool({
389
- id: "DECO_CHAT_RESOURCES_SEARCH",
390
- description: "Search resources (name + term)",
391
- inputSchema: ResourceSearchInputSchema,
392
- outputSchema: ResourceSearchOutputSchema,
393
- execute: (input) => {
394
- // @ts-expect-error - input.name is not a string
395
- const fn = searchHandlers.get(input.name);
396
- if (!fn) {
397
- // @ts-expect-error - input.name is not a string
398
- throw new Error(`SEARCH not implemented for ${input.name}`);
399
- }
400
- // @ts-expect-error - input.name is not a string
401
- const { term, cursor, limit } = input;
402
- return fn({ term, cursor, limit });
403
- },
404
- }),
405
- );
406
- resourceTools.push(
407
- createTool({
408
- id: "DECO_CHAT_RESOURCES_CREATE",
409
- description: "Create a resource (name + content)",
410
- inputSchema: ResourceCreateInputSchema,
411
- outputSchema: ResourceCreateOutputSchema,
412
- execute: (input) => {
413
- // @ts-expect-error - input.name is not a string
414
- const fn = createHandlers.get(input.name);
415
- if (!fn) {
416
- // @ts-expect-error - input.name is not a string
417
- throw new Error(`CREATE not implemented for ${input.name}`);
418
- }
419
- return fn(input);
420
- },
421
- }),
422
- );
423
- resourceTools.push(
424
- createTool({
425
- id: "DECO_CHAT_RESOURCES_UPDATE",
426
- description: "Update a resource (name + uri)",
427
- inputSchema: ResourceUpdateInputSchema,
428
- outputSchema: ResourceUpdateOutputSchema,
429
- execute: (input) => {
430
- // @ts-expect-error - input.name is not a string
431
- const fn = updateHandlers.get(input.name);
432
- if (!fn) {
433
- // @ts-expect-error - input.name is not a string
434
- throw new Error(`UPDATE not implemented for ${input.name}`);
435
- }
436
- return fn(input);
437
- },
438
- }),
439
- );
440
- resourceTools.push(
441
- createTool({
442
- id: "DECO_CHAT_RESOURCES_DELETE",
443
- description: "Delete a resource (name + uri)",
444
- inputSchema: ResourceDeleteInputSchema,
445
- outputSchema: ResourceDeleteOutputSchema,
446
- execute: (input) => {
447
- // @ts-expect-error - input.name is not a string
448
- const fn = deleteHandlers.get(input.name);
449
- if (!fn) {
450
- // @ts-expect-error - input.name is not a string
451
- throw new Error(`DELETE not implemented for ${input.name}`);
452
- }
453
- return fn(input);
454
- },
455
- }),
456
- );
457
- resourceTools.push(
458
- createTool({
459
- id: "DECO_CHAT_RESOURCES_LIST",
460
- description: "List resource types",
461
- inputSchema: z.object({}),
462
- outputSchema: ResourcesListOutputSchema,
463
- execute: () =>
464
- Promise.resolve({
465
- resources: resolvedResources.map((r) => ({
466
- name: r.name,
467
- icon: r.icon,
468
- title: r.title,
469
- description: r.description ?? "",
470
- hasCreate: Boolean(createHandlers.get(r.name)),
471
- hasUpdate: Boolean(updateHandlers.get(r.name)),
472
- hasDelete: Boolean(deleteHandlers.get(r.name)),
473
- })),
474
- }),
475
- }),
476
- );
477
- }
478
-
479
- const toolsFn =
480
- typeof options.tools === "function"
481
- ? options.tools
482
- : async (bindings: TEnv & DefaultEnv<TSchema>) => {
483
- if (typeof options.tools === "function") {
484
- return await options.tools(bindings);
485
- }
486
- return await Promise.all(
487
- options.tools?.flatMap(async (tool) => {
488
- const toolResult = tool(bindings);
489
- const awaited = await toolResult;
490
- if (Array.isArray(awaited)) {
491
- return awaited;
492
- }
493
- return [awaited];
494
- }) ?? [],
495
- ).then((t) => t.flat());
496
- };
497
- const tools = await toolsFn(bindings);
498
-
499
- tools.push(...decoChatOAuthToolsFor<TSchema>(options.oauth));
500
- tools.push(createStateValidationTool(options.oauth?.state));
501
-
502
- tools.push(
503
- createTool({
504
- id: `DECO_CHAT_VIEWS_LIST`,
505
- description: "List views exposed by this MCP",
506
- inputSchema: z.any(),
507
- outputSchema: ViewsListOutputSchema,
508
- execute: async () => {
509
- const base = ((await options.views?.(bindings)) ?? []).map((v) => ({
510
- id: undefined,
511
- // Stable machine name for routing: UPPERCASE + underscores
512
- name: v.title.toUpperCase().replace(/[^A-Z0-9]/g, "_"),
513
- title: v.title,
514
- description: undefined,
515
- icon: v.icon,
516
- url: v.url,
517
- tools: v.tools ?? [],
518
- rules: v.rules ?? [],
519
- installBehavior: v.installBehavior ?? "none",
520
- }));
521
- const resourceViews = resolvedResources
522
- .map((r) => {
523
- const listUrl =
524
- r.views?.list?.url ??
525
- `internal://resource/list?name=${encodeURIComponent(r.name)}`;
526
-
527
- // Default CRUD tool ids for resources
528
- const defaultListTools: string[] = (() => {
529
- const base = [
530
- "DECO_CHAT_RESOURCES_LIST",
531
- "DECO_CHAT_RESOURCES_READ",
532
- "DECO_CHAT_RESOURCES_SEARCH",
533
- ];
534
- const canCreate = Boolean(createHandlers.get(r.name));
535
- const canUpdate = Boolean(updateHandlers.get(r.name));
536
- const canDelete = Boolean(deleteHandlers.get(r.name));
537
- if (canCreate) base.push("DECO_CHAT_RESOURCES_CREATE");
538
- if (canUpdate) base.push("DECO_CHAT_RESOURCES_UPDATE");
539
- if (canDelete) base.push("DECO_CHAT_RESOURCES_DELETE");
540
- return base;
541
- })();
542
-
543
- const defaultListRules: string[] = [
544
- `You are viewing the ${
545
- r.title ?? r.name
546
- } resources list. Resources are changeable via Resource tools (DECO_CHAT_RESOURCES_*). Use the appropriate tools to read, search, create, update, or delete items; do not fabricate data.`,
547
- ];
548
-
549
- const list = [
550
- {
551
- name: `${r.name.toUpperCase()}_LIST`,
552
- title: `${r.name} List`,
553
- description: r.description,
554
- icon: r.icon,
555
- url: listUrl,
556
- tools: r.views?.list?.tools ?? defaultListTools,
557
- rules: r.views?.list?.rules ?? defaultListRules,
558
- },
559
- ];
560
- const detailUrl =
561
- r.views?.detail?.url ??
562
- `internal://resource/detail?name=${encodeURIComponent(r.name)}`;
563
- const detail = [
564
- {
565
- name: `${r.name.toUpperCase()}_DETAIL`,
566
- title: `${r.name} Detail`,
567
- description: r.description,
568
- icon: r.icon,
569
- url: detailUrl,
570
- mimeTypePattern: r.views?.detail?.mimeTypePattern,
571
- resourceName: r.views?.detail?.resourceName ?? r.name,
572
- tools: r.views?.detail?.tools ?? [],
573
- rules: r.views?.detail?.rules ?? [],
574
- },
575
- ];
576
- return [...list, ...detail];
577
- })
578
- .flat();
579
-
580
- return { views: [...base, ...resourceViews] };
581
- },
582
- }),
583
- );
584
-
585
- for (const tool of tools) {
586
- server.registerTool(
587
- tool.id,
588
- {
589
- _meta: {
590
- streamable: isStreamableTool(tool),
591
- },
592
- description: tool.description,
593
- inputSchema:
594
- tool.inputSchema && "shape" in tool.inputSchema
595
- ? (tool.inputSchema.shape as z.ZodRawShape)
596
- : z.object({}).shape,
597
- outputSchema: isStreamableTool(tool)
598
- ? z.object({ bytes: z.record(z.string(), z.number()) }).shape
599
- : tool.outputSchema &&
600
- typeof tool.outputSchema === "object" &&
601
- "shape" in tool.outputSchema
602
- ? (tool.outputSchema.shape as z.ZodRawShape)
603
- : z.object({}).shape,
604
- },
605
- async (args) => {
606
- let result = await tool.execute!({
607
- context: args,
608
- runId: crypto.randomUUID(),
609
- runtimeContext: createRuntimeContext(),
610
- });
611
-
612
- if (isStreamableTool(tool) && result instanceof Response) {
613
- result = { bytes: await result.bytes() };
614
- }
615
- return {
616
- structuredContent: result,
617
- content: [
618
- {
619
- type: "text",
620
- text: JSON.stringify(result),
621
- },
622
- ],
623
- };
624
- },
625
- );
626
- }
627
-
628
- return { server, tools };
629
- };
630
-
631
- const fetch = async (
632
- req: Request,
633
- env: TEnv & DefaultEnv<TSchema>,
634
- _ctx: ExecutionContext,
635
- ) => {
636
- const { server } = await createServer(env);
637
- const transport = new HttpServerTransport();
638
-
639
- await server.connect(transport);
640
-
641
- return await transport.handleMessage(req);
642
- };
643
-
644
- const callTool: CallTool = async ({ toolCallId, toolCallInput }) => {
645
- const currentState = State.getStore();
646
- if (!currentState) {
647
- throw new Error("Missing state, did you forget to call State.bind?");
648
- }
649
- const env = currentState?.env;
650
- const { tools } = await createServer(env);
651
- const tool = tools.find((t) => t.id === toolCallId);
652
- const execute = tool?.execute;
653
- if (!execute) {
654
- throw new Error(
655
- `Tool ${toolCallId} not found or does not have an execute function`,
656
- );
657
- }
658
-
659
- return execute({
660
- context: toolCallInput,
661
- runId: crypto.randomUUID(),
662
- runtimeContext: createRuntimeContext(),
663
- });
664
- };
665
-
666
- return {
667
- fetch,
668
- callTool,
669
- };
670
- };