feat(core): Cleanup after migrating tools. (#6199)

Co-authored-by: Jacob Richman <jacob314@gmail.com>
This commit is contained in:
joshualitt 2025-08-19 13:55:06 -07:00 committed by GitHub
parent 2143731f6e
commit b9cece767d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 86 additions and 132 deletions

View File

@ -43,7 +43,7 @@ const createFakeCompletedToolCall = (
status: 'success', status: 'success',
request, request,
tool, tool,
invocation: tool.build({}), invocation: tool.build({ param: 'test' }),
response: { response: {
callId: request.callId, callId: request.callId,
responseParts: { responseParts: {

View File

@ -13,7 +13,6 @@ import {
ToolResult, ToolResult,
Kind, Kind,
} from '../tools/tools.js'; } from '../tools/tools.js';
import { Schema, Type } from '@google/genai';
import { import {
ModifiableDeclarativeTool, ModifiableDeclarativeTool,
ModifyContext, ModifyContext,
@ -74,9 +73,9 @@ export class MockTool extends BaseDeclarativeTool<
name = 'mock-tool', name = 'mock-tool',
displayName?: string, displayName?: string,
description = 'A mock tool for testing.', description = 'A mock tool for testing.',
params: Schema = { params = {
type: Type.OBJECT, type: 'object',
properties: { param: { type: Type.STRING } }, properties: { param: { type: 'string' } },
}, },
) { ) {
super(name, displayName ?? name, description, Kind.Other, params); super(name, displayName ?? name, description, Kind.Other, params);

View File

@ -395,7 +395,7 @@ describe('EditTool', () => {
}); });
}); });
it('should throw error if params are invalid', async () => { it('should throw error if file path is not absolute', async () => {
const params: EditToolParams = { const params: EditToolParams = {
file_path: 'relative.txt', file_path: 'relative.txt',
old_string: 'old', old_string: 'old',
@ -404,6 +404,17 @@ describe('EditTool', () => {
expect(() => tool.build(params)).toThrow(/File path must be absolute/); expect(() => tool.build(params)).toThrow(/File path must be absolute/);
}); });
it('should throw error if file path is empty', async () => {
const params: EditToolParams = {
file_path: '',
old_string: 'old',
new_string: 'new',
};
expect(() => tool.build(params)).toThrow(
/The 'file_path' parameter must be non-empty./,
);
});
it('should edit an existing file and return diff with fileName', async () => { it('should edit an existing file and return diff with fileName', async () => {
const initialContent = 'This is some old text.'; const initialContent = 'This is some old text.';
const newContent = 'This is some new text.'; // old -> new const newContent = 'This is some new text.'; // old -> new

View File

@ -19,7 +19,6 @@ import {
ToolResultDisplay, ToolResultDisplay,
} from './tools.js'; } from './tools.js';
import { ToolErrorType } from './tool-error.js'; import { ToolErrorType } from './tool-error.js';
import { SchemaValidator } from '../utils/schemaValidator.js';
import { makeRelative, shortenPath } from '../utils/paths.js'; import { makeRelative, shortenPath } from '../utils/paths.js';
import { isNodeError } from '../utils/errors.js'; import { isNodeError } from '../utils/errors.js';
import { Config, ApprovalMode } from '../config/config.js'; import { Config, ApprovalMode } from '../config/config.js';
@ -475,13 +474,11 @@ Expectation for required parameters:
* @param params Parameters to validate * @param params Parameters to validate
* @returns Error message string or null if valid * @returns Error message string or null if valid
*/ */
override validateToolParams(params: EditToolParams): string | null { protected override validateToolParamValues(
const errors = SchemaValidator.validate( params: EditToolParams,
this.schema.parametersJsonSchema, ): string | null {
params, if (!params.file_path) {
); return "The 'file_path' parameter must be non-empty.";
if (errors) {
return errors;
} }
if (!path.isAbsolute(params.file_path)) { if (!path.isAbsolute(params.file_path)) {

View File

@ -7,7 +7,6 @@
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { glob, escape } from 'glob'; import { glob, escape } from 'glob';
import { SchemaValidator } from '../utils/schemaValidator.js';
import { import {
BaseDeclarativeTool, BaseDeclarativeTool,
BaseToolInvocation, BaseToolInvocation,
@ -287,15 +286,9 @@ export class GlobTool extends BaseDeclarativeTool<GlobToolParams, ToolResult> {
/** /**
* Validates the parameters for the tool. * Validates the parameters for the tool.
*/ */
override validateToolParams(params: GlobToolParams): string | null { protected override validateToolParamValues(
const errors = SchemaValidator.validate( params: GlobToolParams,
this.schema.parametersJsonSchema, ): string | null {
params,
);
if (errors) {
return errors;
}
const searchDirAbsolute = path.resolve( const searchDirAbsolute = path.resolve(
this.config.getTargetDir(), this.config.getTargetDir(),
params.path || '.', params.path || '.',

View File

@ -17,7 +17,6 @@ import {
ToolInvocation, ToolInvocation,
ToolResult, ToolResult,
} from './tools.js'; } from './tools.js';
import { SchemaValidator } from '../utils/schemaValidator.js';
import { makeRelative, shortenPath } from '../utils/paths.js'; import { makeRelative, shortenPath } from '../utils/paths.js';
import { getErrorMessage, isNodeError } from '../utils/errors.js'; import { getErrorMessage, isNodeError } from '../utils/errors.js';
import { isGitRepository } from '../utils/gitUtils.js'; import { isGitRepository } from '../utils/gitUtils.js';
@ -614,15 +613,9 @@ export class GrepTool extends BaseDeclarativeTool<GrepToolParams, ToolResult> {
* @param params Parameters to validate * @param params Parameters to validate
* @returns An error message string if invalid, null otherwise * @returns An error message string if invalid, null otherwise
*/ */
override validateToolParams(params: GrepToolParams): string | null { protected override validateToolParamValues(
const errors = SchemaValidator.validate( params: GrepToolParams,
this.schema.parametersJsonSchema, ): string | null {
params,
);
if (errors) {
return errors;
}
try { try {
new RegExp(params.pattern); new RegExp(params.pattern);
} catch (error) { } catch (error) {

View File

@ -13,7 +13,6 @@ import {
ToolInvocation, ToolInvocation,
ToolResult, ToolResult,
} from './tools.js'; } from './tools.js';
import { SchemaValidator } from '../utils/schemaValidator.js';
import { makeRelative, shortenPath } from '../utils/paths.js'; import { makeRelative, shortenPath } from '../utils/paths.js';
import { Config, DEFAULT_FILE_FILTERING_OPTIONS } from '../config/config.js'; import { Config, DEFAULT_FILE_FILTERING_OPTIONS } from '../config/config.js';
@ -314,14 +313,9 @@ export class LSTool extends BaseDeclarativeTool<LSToolParams, ToolResult> {
* @param params Parameters to validate * @param params Parameters to validate
* @returns An error message string if invalid, null otherwise * @returns An error message string if invalid, null otherwise
*/ */
override validateToolParams(params: LSToolParams): string | null { protected override validateToolParamValues(
const errors = SchemaValidator.validate( params: LSToolParams,
this.schema.parametersJsonSchema, ): string | null {
params,
);
if (errors) {
return errors;
}
if (!path.isAbsolute(params.path)) { if (!path.isAbsolute(params.path)) {
return `Path must be absolute: ${params.path}`; return `Path must be absolute: ${params.path}`;
} }

View File

@ -86,7 +86,7 @@ describe('DiscoveredMCPTool', () => {
inputSchema, inputSchema,
); );
// Clear allowlist before each relevant test, especially for shouldConfirmExecute // Clear allowlist before each relevant test, especially for shouldConfirmExecute
const invocation = tool.build({}) as any; const invocation = tool.build({ param: 'mock' }) as any;
invocation.constructor.allowlist.clear(); invocation.constructor.allowlist.clear();
}); });
@ -278,7 +278,7 @@ describe('DiscoveredMCPTool', () => {
); );
it('should handle a simple text response correctly', async () => { it('should handle a simple text response correctly', async () => {
const params = { query: 'test' }; const params = { param: 'test' };
const successMessage = 'This is a success message.'; const successMessage = 'This is a success message.';
// Simulate the response from the GenAI SDK, which wraps the MCP // Simulate the response from the GenAI SDK, which wraps the MCP
@ -312,7 +312,7 @@ describe('DiscoveredMCPTool', () => {
}); });
it('should handle an AudioBlock response', async () => { it('should handle an AudioBlock response', async () => {
const params = { action: 'play' }; const params = { param: 'play' };
const sdkResponse: Part[] = [ const sdkResponse: Part[] = [
{ {
functionResponse: { functionResponse: {
@ -349,7 +349,7 @@ describe('DiscoveredMCPTool', () => {
}); });
it('should handle a ResourceLinkBlock response', async () => { it('should handle a ResourceLinkBlock response', async () => {
const params = { resource: 'get' }; const params = { param: 'get' };
const sdkResponse: Part[] = [ const sdkResponse: Part[] = [
{ {
functionResponse: { functionResponse: {
@ -383,7 +383,7 @@ describe('DiscoveredMCPTool', () => {
}); });
it('should handle an embedded text ResourceBlock response', async () => { it('should handle an embedded text ResourceBlock response', async () => {
const params = { resource: 'get' }; const params = { param: 'get' };
const sdkResponse: Part[] = [ const sdkResponse: Part[] = [
{ {
functionResponse: { functionResponse: {
@ -415,7 +415,7 @@ describe('DiscoveredMCPTool', () => {
}); });
it('should handle an embedded binary ResourceBlock response', async () => { it('should handle an embedded binary ResourceBlock response', async () => {
const params = { resource: 'get' }; const params = { param: 'get' };
const sdkResponse: Part[] = [ const sdkResponse: Part[] = [
{ {
functionResponse: { functionResponse: {
@ -457,7 +457,7 @@ describe('DiscoveredMCPTool', () => {
}); });
it('should handle a mix of content block types', async () => { it('should handle a mix of content block types', async () => {
const params = { action: 'complex' }; const params = { param: 'complex' };
const sdkResponse: Part[] = [ const sdkResponse: Part[] = [
{ {
functionResponse: { functionResponse: {
@ -500,7 +500,7 @@ describe('DiscoveredMCPTool', () => {
}); });
it('should ignore unknown content block types', async () => { it('should ignore unknown content block types', async () => {
const params = { action: 'test' }; const params = { param: 'test' };
const sdkResponse: Part[] = [ const sdkResponse: Part[] = [
{ {
functionResponse: { functionResponse: {
@ -526,7 +526,7 @@ describe('DiscoveredMCPTool', () => {
}); });
it('should handle a complex mix of content block types', async () => { it('should handle a complex mix of content block types', async () => {
const params = { action: 'super-complex' }; const params = { param: 'super-complex' };
const sdkResponse: Part[] = [ const sdkResponse: Part[] = [
{ {
functionResponse: { functionResponse: {
@ -596,14 +596,14 @@ describe('DiscoveredMCPTool', () => {
undefined, undefined,
true, true,
); );
const invocation = trustedTool.build({}); const invocation = trustedTool.build({ param: 'mock' });
expect( expect(
await invocation.shouldConfirmExecute(new AbortController().signal), await invocation.shouldConfirmExecute(new AbortController().signal),
).toBe(false); ).toBe(false);
}); });
it('should return false if server is allowlisted', async () => { it('should return false if server is allowlisted', async () => {
const invocation = tool.build({}) as any; const invocation = tool.build({ param: 'mock' }) as any;
invocation.constructor.allowlist.add(serverName); invocation.constructor.allowlist.add(serverName);
expect( expect(
await invocation.shouldConfirmExecute(new AbortController().signal), await invocation.shouldConfirmExecute(new AbortController().signal),
@ -612,7 +612,7 @@ describe('DiscoveredMCPTool', () => {
it('should return false if tool is allowlisted', async () => { it('should return false if tool is allowlisted', async () => {
const toolAllowlistKey = `${serverName}.${serverToolName}`; const toolAllowlistKey = `${serverName}.${serverToolName}`;
const invocation = tool.build({}) as any; const invocation = tool.build({ param: 'mock' }) as any;
invocation.constructor.allowlist.add(toolAllowlistKey); invocation.constructor.allowlist.add(toolAllowlistKey);
expect( expect(
await invocation.shouldConfirmExecute(new AbortController().signal), await invocation.shouldConfirmExecute(new AbortController().signal),
@ -620,7 +620,7 @@ describe('DiscoveredMCPTool', () => {
}); });
it('should return confirmation details if not trusted and not allowlisted', async () => { it('should return confirmation details if not trusted and not allowlisted', async () => {
const invocation = tool.build({}); const invocation = tool.build({ param: 'mock' });
const confirmation = await invocation.shouldConfirmExecute( const confirmation = await invocation.shouldConfirmExecute(
new AbortController().signal, new AbortController().signal,
); );
@ -643,7 +643,7 @@ describe('DiscoveredMCPTool', () => {
}); });
it('should add server to allowlist on ProceedAlwaysServer', async () => { it('should add server to allowlist on ProceedAlwaysServer', async () => {
const invocation = tool.build({}) as any; const invocation = tool.build({ param: 'mock' }) as any;
const confirmation = await invocation.shouldConfirmExecute( const confirmation = await invocation.shouldConfirmExecute(
new AbortController().signal, new AbortController().signal,
); );
@ -667,7 +667,7 @@ describe('DiscoveredMCPTool', () => {
it('should add tool to allowlist on ProceedAlwaysTool', async () => { it('should add tool to allowlist on ProceedAlwaysTool', async () => {
const toolAllowlistKey = `${serverName}.${serverToolName}`; const toolAllowlistKey = `${serverName}.${serverToolName}`;
const invocation = tool.build({}) as any; const invocation = tool.build({ param: 'mock' }) as any;
const confirmation = await invocation.shouldConfirmExecute( const confirmation = await invocation.shouldConfirmExecute(
new AbortController().signal, new AbortController().signal,
); );
@ -690,7 +690,7 @@ describe('DiscoveredMCPTool', () => {
}); });
it('should handle Cancel confirmation outcome', async () => { it('should handle Cancel confirmation outcome', async () => {
const invocation = tool.build({}) as any; const invocation = tool.build({ param: 'mock' }) as any;
const confirmation = await invocation.shouldConfirmExecute( const confirmation = await invocation.shouldConfirmExecute(
new AbortController().signal, new AbortController().signal,
); );
@ -717,7 +717,7 @@ describe('DiscoveredMCPTool', () => {
}); });
it('should handle ProceedOnce confirmation outcome', async () => { it('should handle ProceedOnce confirmation outcome', async () => {
const invocation = tool.build({}) as any; const invocation = tool.build({ param: 'mock' }) as any;
const confirmation = await invocation.shouldConfirmExecute( const confirmation = await invocation.shouldConfirmExecute(
new AbortController().signal, new AbortController().signal,
); );

View File

@ -20,7 +20,6 @@ import * as Diff from 'diff';
import { DEFAULT_DIFF_OPTIONS } from './diffOptions.js'; import { DEFAULT_DIFF_OPTIONS } from './diffOptions.js';
import { tildeifyPath } from '../utils/paths.js'; import { tildeifyPath } from '../utils/paths.js';
import { ModifiableDeclarativeTool, ModifyContext } from './modifiable-tool.js'; import { ModifiableDeclarativeTool, ModifyContext } from './modifiable-tool.js';
import { SchemaValidator } from '../utils/schemaValidator.js';
const memoryToolSchemaData: FunctionDeclaration = { const memoryToolSchemaData: FunctionDeclaration = {
name: 'save_memory', name: 'save_memory',
@ -294,15 +293,9 @@ export class MemoryTool
); );
} }
override validateToolParams(params: SaveMemoryParams): string | null { protected override validateToolParamValues(
const errors = SchemaValidator.validate( params: SaveMemoryParams,
this.schema.parametersJsonSchema, ): string | null {
params,
);
if (errors) {
return errors;
}
if (params.fact.trim() === '') { if (params.fact.trim() === '') {
return 'Parameter "fact" must be a non-empty string.'; return 'Parameter "fact" must be a non-empty string.';
} }

View File

@ -71,6 +71,15 @@ describe('ReadFileTool', () => {
); );
}); });
it('should throw error if path is empty', () => {
const params: ReadFileToolParams = {
absolute_path: '',
};
expect(() => tool.build(params)).toThrow(
/The 'absolute_path' parameter must be non-empty./,
);
});
it('should throw error if offset is negative', () => { it('should throw error if offset is negative', () => {
const params: ReadFileToolParams = { const params: ReadFileToolParams = {
absolute_path: path.join(tempRootDir, 'test.txt'), absolute_path: path.join(tempRootDir, 'test.txt'),

View File

@ -5,7 +5,6 @@
*/ */
import path from 'path'; import path from 'path';
import { SchemaValidator } from '../utils/schemaValidator.js';
import { makeRelative, shortenPath } from '../utils/paths.js'; import { makeRelative, shortenPath } from '../utils/paths.js';
import { import {
BaseDeclarativeTool, BaseDeclarativeTool,
@ -199,18 +198,14 @@ export class ReadFileTool extends BaseDeclarativeTool<
); );
} }
protected override validateToolParams( protected override validateToolParamValues(
params: ReadFileToolParams, params: ReadFileToolParams,
): string | null { ): string | null {
const errors = SchemaValidator.validate( const filePath = params.absolute_path;
this.schema.parametersJsonSchema, if (params.absolute_path.trim() === '') {
params, return "The 'absolute_path' parameter must be non-empty.";
);
if (errors) {
return errors;
} }
const filePath = params.absolute_path;
if (!path.isAbsolute(filePath)) { if (!path.isAbsolute(filePath)) {
return `File path must be absolute, but was relative: ${filePath}. You must provide an absolute path.`; return `File path must be absolute, but was relative: ${filePath}. You must provide an absolute path.`;
} }

View File

@ -11,7 +11,6 @@ import {
ToolInvocation, ToolInvocation,
ToolResult, ToolResult,
} from './tools.js'; } from './tools.js';
import { SchemaValidator } from '../utils/schemaValidator.js';
import { getErrorMessage } from '../utils/errors.js'; import { getErrorMessage } from '../utils/errors.js';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
@ -637,19 +636,6 @@ Use this tool when the user's query implies needing the content of several files
); );
} }
protected override validateToolParams(
params: ReadManyFilesParams,
): string | null {
const errors = SchemaValidator.validate(
this.schema.parametersJsonSchema,
params,
);
if (errors) {
return errors;
}
return null;
}
protected createInvocation( protected createInvocation(
params: ReadManyFilesParams, params: ReadManyFilesParams,
): ToolInvocation<ReadManyFilesParams, ToolResult> { ): ToolInvocation<ReadManyFilesParams, ToolResult> {

View File

@ -19,7 +19,6 @@ import {
ToolConfirmationOutcome, ToolConfirmationOutcome,
Kind, Kind,
} from './tools.js'; } from './tools.js';
import { SchemaValidator } from '../utils/schemaValidator.js';
import { getErrorMessage } from '../utils/errors.js'; import { getErrorMessage } from '../utils/errors.js';
import { summarizeToolOutput } from '../utils/summarizer.js'; import { summarizeToolOutput } from '../utils/summarizer.js';
import { import {
@ -361,7 +360,7 @@ export class ShellTool extends BaseDeclarativeTool<
); );
} }
protected override validateToolParams( protected override validateToolParamValues(
params: ShellToolParams, params: ShellToolParams,
): string | null { ): string | null {
const commandCheck = isCommandAllowed(params.command, this.config); const commandCheck = isCommandAllowed(params.command, this.config);
@ -374,13 +373,6 @@ export class ShellTool extends BaseDeclarativeTool<
} }
return commandCheck.reason; return commandCheck.reason;
} }
const errors = SchemaValidator.validate(
this.schema.parametersJsonSchema,
params,
);
if (errors) {
return errors;
}
if (!params.command.trim()) { if (!params.command.trim()) {
return 'Command cannot be empty.'; return 'Command cannot be empty.';
} }

View File

@ -7,6 +7,7 @@
import { FunctionDeclaration, PartListUnion } from '@google/genai'; import { FunctionDeclaration, PartListUnion } from '@google/genai';
import { ToolErrorType } from './tool-error.js'; import { ToolErrorType } from './tool-error.js';
import { DiffUpdateResult } from '../ide/ideContext.js'; import { DiffUpdateResult } from '../ide/ideContext.js';
import { SchemaValidator } from '../utils/schemaValidator.js';
/** /**
* Represents a validated and ready-to-execute tool call. * Represents a validated and ready-to-execute tool call.
@ -170,7 +171,7 @@ export abstract class DeclarativeTool<
* @param params The raw parameters from the model. * @param params The raw parameters from the model.
* @returns An error message string if invalid, null otherwise. * @returns An error message string if invalid, null otherwise.
*/ */
protected validateToolParams(_params: TParams): string | null { validateToolParams(_params: TParams): string | null {
// Base implementation can be extended by subclasses. // Base implementation can be extended by subclasses.
return null; return null;
} }
@ -278,6 +279,23 @@ export abstract class BaseDeclarativeTool<
return this.createInvocation(params); return this.createInvocation(params);
} }
override validateToolParams(params: TParams): string | null {
const errors = SchemaValidator.validate(
this.schema.parametersJsonSchema,
params,
);
if (errors) {
return errors;
}
return this.validateToolParamValues(params);
}
protected validateToolParamValues(_params: TParams): string | null {
// Base implementation can be extended by subclasses.
return null;
}
protected abstract createInvocation( protected abstract createInvocation(
params: TParams, params: TParams,
): ToolInvocation<TParams, TResult>; ): ToolInvocation<TParams, TResult>;

View File

@ -4,7 +4,6 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
import { SchemaValidator } from '../utils/schemaValidator.js';
import { import {
BaseDeclarativeTool, BaseDeclarativeTool,
BaseToolInvocation, BaseToolInvocation,
@ -339,16 +338,9 @@ export class WebFetchTool extends BaseDeclarativeTool<
} }
} }
protected override validateToolParams( protected override validateToolParamValues(
params: WebFetchToolParams, params: WebFetchToolParams,
): string | null { ): string | null {
const errors = SchemaValidator.validate(
this.schema.parametersJsonSchema,
params,
);
if (errors) {
return errors;
}
if (!params.prompt || params.prompt.trim() === '') { if (!params.prompt || params.prompt.trim() === '') {
return "The 'prompt' parameter cannot be empty and must contain URL(s) and instructions."; return "The 'prompt' parameter cannot be empty and must contain URL(s) and instructions.";
} }

View File

@ -12,7 +12,6 @@ import {
ToolInvocation, ToolInvocation,
ToolResult, ToolResult,
} from './tools.js'; } from './tools.js';
import { SchemaValidator } from '../utils/schemaValidator.js';
import { getErrorMessage } from '../utils/errors.js'; import { getErrorMessage } from '../utils/errors.js';
import { Config } from '../config/config.js'; import { Config } from '../config/config.js';
@ -192,17 +191,9 @@ export class WebSearchTool extends BaseDeclarativeTool<
* @param params The parameters to validate * @param params The parameters to validate
* @returns An error message string if validation fails, null if valid * @returns An error message string if validation fails, null if valid
*/ */
protected override validateToolParams( protected override validateToolParamValues(
params: WebSearchToolParams, params: WebSearchToolParams,
): string | null { ): string | null {
const errors = SchemaValidator.validate(
this.schema.parametersJsonSchema,
params,
);
if (errors) {
return errors;
}
if (!params.query || params.query.trim() === '') { if (!params.query || params.query.trim() === '') {
return "The 'query' parameter cannot be empty."; return "The 'query' parameter cannot be empty.";
} }

View File

@ -21,7 +21,6 @@ import {
ToolResult, ToolResult,
} from './tools.js'; } from './tools.js';
import { ToolErrorType } from './tool-error.js'; import { ToolErrorType } from './tool-error.js';
import { SchemaValidator } from '../utils/schemaValidator.js';
import { makeRelative, shortenPath } from '../utils/paths.js'; import { makeRelative, shortenPath } from '../utils/paths.js';
import { getErrorMessage, isNodeError } from '../utils/errors.js'; import { getErrorMessage, isNodeError } from '../utils/errors.js';
import { import {
@ -417,17 +416,9 @@ export class WriteFileTool
); );
} }
protected override validateToolParams( protected override validateToolParamValues(
params: WriteFileToolParams, params: WriteFileToolParams,
): string | null { ): string | null {
const errors = SchemaValidator.validate(
this.schema.parametersJsonSchema,
params,
);
if (errors) {
return errors;
}
const filePath = params.file_path; const filePath = params.file_path;
if (!filePath) { if (!filePath) {