Fix linting errors in a number of core and tool files (partial)

- As part of this work I also started building out errors.ts which will be a cumulation of error helpers to better handle the challenging `catch (error: unknown)` requirement.
- More changes are to come, this is truly a partial change in order to not disrupt as many people as possible.

Part of https://b.corp.google.com/issues/411384603
This commit is contained in:
Taylor Mullen
2025-04-18 13:37:51 -04:00
committed by N. Taylor Mullen
parent 93fd6a9160
commit 7cd3b95317
9 changed files with 62 additions and 62 deletions

View File

@@ -25,9 +25,9 @@ import { GeminiEventType, GeminiStream } from './gemini-stream.js';
type ToolExecutionOutcome = { type ToolExecutionOutcome = {
callId: string; callId: string;
name: string; name: string;
args: Record<string, any>; args: Record<string, never>;
result?: ToolResult; result?: ToolResult;
error?: any; error?: Error;
confirmationDetails?: ToolCallConfirmationDetails; confirmationDetails?: ToolCallConfirmationDetails;
}; };
@@ -126,7 +126,7 @@ ${folderStructure}
let pendingToolCalls: Array<{ let pendingToolCalls: Array<{
callId: string; callId: string;
name: string; name: string;
args: Record<string, any>; args: Record<string, never>;
}> = []; }> = [];
let yieldedTextInTurn = false; let yieldedTextInTurn = false;
const chunksForDebug = []; const chunksForDebug = [];
@@ -148,7 +148,7 @@ ${folderStructure}
call.id ?? call.id ??
`${call.name}-${Date.now()}-${Math.random().toString(16).slice(2)}`; `${call.name}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
const name = call.name || 'undefined_tool_name'; const name = call.name || 'undefined_tool_name';
const args = (call.args || {}) as Record<string, any>; const args = (call.args || {}) as Record<string, never>;
pendingToolCalls.push({ callId, name, args }); pendingToolCalls.push({ callId, name, args });
const evtValue: ToolCallEvent = { const evtValue: ToolCallEvent = {
@@ -281,7 +281,7 @@ ${folderStructure}
(executedTool: ToolExecutionOutcome): Part => { (executedTool: ToolExecutionOutcome): Part => {
const { name, result, error } = executedTool; const { name, result, error } = executedTool;
const output = { output: result?.llmContent }; const output = { output: result?.llmContent };
let toolOutcomePayload: any; let toolOutcomePayload: Record<string, unknown>;
if (error) { if (error) {
const errorMessage = error?.message || String(error); const errorMessage = error?.message || String(error);
@@ -445,11 +445,11 @@ Respond *only* in JSON format according to the following schema. Do not include
async generateJson( async generateJson(
contents: Content[], contents: Content[],
schema: SchemaUnion, schema: SchemaUnion,
): Promise<any> { ): Promise<Record<string, unknown>> {
const model = getModel(); const model = getModel();
try { try {
const result = await this.ai.models.generateContent({ const result = await this.ai.models.generateContent({
model: model, model,
config: { config: {
...this.defaultHyperParameters, ...this.defaultHyperParameters,
systemInstruction: CoreSystemPrompt, systemInstruction: CoreSystemPrompt,

View File

@@ -154,14 +154,14 @@ export const processGeminiStream = async ({
if (signal.aborted) { if (signal.aborted) {
throw new Error('Request cancelled by user'); throw new Error('Request cancelled by user');
} }
} catch (error: any) { } catch (error: unknown) {
if (renderTimeoutId) { if (renderTimeoutId) {
// Ensure render loop stops on error // Ensure render loop stops on error
clearTimeout(renderTimeoutId); clearTimeout(renderTimeoutId);
renderTimeoutId = null; renderTimeoutId = null;
} }
// Delegate history update for error message // Delegate history update for error message
addErrorMessageToHistory(error, setHistory, getNextMessageId); addErrorMessageToHistory(error as (Error | DOMException), setHistory, getNextMessageId);
} finally { } finally {
isStreamComplete = true; // Signal stream end for render loop completion isStreamComplete = true; // Signal stream end for render loop completion
if (renderTimeoutId) { if (renderTimeoutId) {

View File

@@ -178,7 +178,7 @@ export const handleToolCallChunk = (
* it to the last non-user message or creating a new entry. * it to the last non-user message or creating a new entry.
*/ */
export const addErrorMessageToHistory = ( export const addErrorMessageToHistory = (
error: any, error: DOMException | Error,
setHistory: React.Dispatch<React.SetStateAction<HistoryItem[]>>, setHistory: React.Dispatch<React.SetStateAction<HistoryItem[]>>,
getNextMessageId: () => number, getNextMessageId: () => number,
): void => { ): void => {

View File

@@ -11,6 +11,7 @@ import {
import { makeRelative, shortenPath } from '../utils/paths.js'; import { makeRelative, shortenPath } from '../utils/paths.js';
import { ReadFileTool } from './read-file.tool.js'; import { ReadFileTool } from './read-file.tool.js';
import { WriteFileTool } from './write-file.tool.js'; import { WriteFileTool } from './write-file.tool.js';
import { isNodeError } from '../utils/errors.js';
/** /**
* Parameters for the Edit tool * Parameters for the Edit tool
@@ -37,11 +38,6 @@ export interface EditToolParams {
expected_replacements?: number; expected_replacements?: number;
} }
/**
* Result from the Edit tool
*/
export interface EditToolResult extends ToolResult {}
interface CalculatedEdit { interface CalculatedEdit {
currentContent: string | null; currentContent: string | null;
newContent: string; newContent: string;
@@ -54,7 +50,7 @@ interface CalculatedEdit {
* Implementation of the Edit tool that modifies files. * Implementation of the Edit tool that modifies files.
* This tool maintains state for the "Always Edit" confirmation preference. * This tool maintains state for the "Always Edit" confirmation preference.
*/ */
export class EditTool extends BaseTool<EditToolParams, EditToolResult> { export class EditTool extends BaseTool<EditToolParams, ToolResult> {
private shouldAlwaysEdit = false; private shouldAlwaysEdit = false;
private readonly rootDirectory: string; private readonly rootDirectory: string;
@@ -174,8 +170,8 @@ export class EditTool extends BaseTool<EditToolParams, EditToolResult> {
try { try {
currentContent = fs.readFileSync(params.file_path, 'utf8'); currentContent = fs.readFileSync(params.file_path, 'utf8');
fileExists = true; fileExists = true;
} catch (err: any) { } catch (err: unknown) {
if (err.code !== 'ENOENT') { if (!isNodeError(err) || err.code !== 'ENOENT') {
throw err; throw err;
} }
fileExists = false; fileExists = false;
@@ -300,7 +296,7 @@ export class EditTool extends BaseTool<EditToolParams, EditToolResult> {
* @param params Parameters for the edit operation * @param params Parameters for the edit operation
* @returns Result of the edit operation * @returns Result of the edit operation
*/ */
async execute(params: EditToolParams): Promise<EditToolResult> { async execute(params: EditToolParams): Promise<ToolResult> {
if (!this.validateParams(params)) { if (!this.validateParams(params)) {
return { return {
llmContent: 'Invalid parameters for file edit operation', llmContent: 'Invalid parameters for file edit operation',

View File

@@ -20,16 +20,11 @@ export interface GlobToolParams {
path?: string; path?: string;
} }
/**
* Result from the GlobTool
*/
export interface GlobToolResult extends ToolResult {}
/** /**
* Implementation of the GlobTool that finds files matching patterns, * Implementation of the GlobTool that finds files matching patterns,
* sorted by modification time (newest first). * sorted by modification time (newest first).
*/ */
export class GlobTool extends BaseTool<GlobToolParams, GlobToolResult> { export class GlobTool extends BaseTool<GlobToolParams, ToolResult> {
/** /**
* The root directory that this tool is grounded in. * The root directory that this tool is grounded in.
* All file operations will be restricted to this directory. * All file operations will be restricted to this directory.
@@ -125,9 +120,9 @@ export class GlobTool extends BaseTool<GlobToolParams, GlobToolResult> {
if (!fs.statSync(searchDirAbsolute).isDirectory()) { if (!fs.statSync(searchDirAbsolute).isDirectory()) {
return `Search path is not a directory: ${shortenPath(makeRelative(searchDirAbsolute, this.rootDirectory))} (absolute: ${searchDirAbsolute})`; return `Search path is not a directory: ${shortenPath(makeRelative(searchDirAbsolute, this.rootDirectory))} (absolute: ${searchDirAbsolute})`;
} }
} catch (e: any) { } catch (e: unknown) {
// Catch potential permission errors during sync checks // Catch potential permission errors during sync checks
return `Error accessing search path: ${e.message}`; return `Error accessing search path: ${e}`;
} }
// Validate glob pattern (basic non-empty check) // Validate glob pattern (basic non-empty check)
@@ -165,7 +160,7 @@ export class GlobTool extends BaseTool<GlobToolParams, GlobToolResult> {
* @param params Parameters for the glob search * @param params Parameters for the glob search
* @returns Result of the glob search * @returns Result of the glob search
*/ */
async execute(params: GlobToolParams): Promise<GlobToolResult> { async execute(params: GlobToolParams): Promise<ToolResult> {
const validationError = this.invalidParams(params); const validationError = this.invalidParams(params);
if (validationError) { if (validationError) {
return { return {

View File

@@ -7,6 +7,7 @@ import fastGlob from 'fast-glob'; // Used for JS fallback file searching
import { BaseTool, ToolResult } from './tools.js'; import { BaseTool, ToolResult } from './tools.js';
import { SchemaValidator } from '../utils/schemaValidator.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';
// --- Interfaces (kept separate for clarity) --- // --- Interfaces (kept separate for clarity) ---
@@ -39,17 +40,12 @@ interface GrepMatch {
line: string; line: string;
} }
/**
* Result from the GrepTool
*/
export interface GrepToolResult extends ToolResult {}
// --- GrepTool Class --- // --- GrepTool Class ---
/** /**
* Implementation of the GrepTool that searches file contents using git grep, system grep, or JS fallback. * Implementation of the GrepTool that searches file contents using git grep, system grep, or JS fallback.
*/ */
export class GrepTool extends BaseTool<GrepToolParams, GrepToolResult> { export class GrepTool extends BaseTool<GrepToolParams, ToolResult> {
private rootDirectory: string; private rootDirectory: string;
/** /**
@@ -114,12 +110,12 @@ export class GrepTool extends BaseTool<GrepToolParams, GrepToolResult> {
if (!stats.isDirectory()) { if (!stats.isDirectory()) {
throw new Error(`Path is not a directory: ${targetPath}`); throw new Error(`Path is not a directory: ${targetPath}`);
} }
} catch (err: any) { } catch (error: unknown) {
if (err.code === 'ENOENT') { if (isNodeError(error) && error.code !== 'ENOENT') {
throw new Error(`Path does not exist: ${targetPath}`); throw new Error(`Path does not exist: ${targetPath}`);
} }
throw new Error( throw new Error(
`Failed to access path stats for ${targetPath}: ${err.message}`, `Failed to access path stats for ${targetPath}: ${error}`,
); );
} }
@@ -164,7 +160,7 @@ export class GrepTool extends BaseTool<GrepToolParams, GrepToolResult> {
* @param params Parameters for the grep search * @param params Parameters for the grep search
* @returns Result of the grep search * @returns Result of the grep search
*/ */
async execute(params: GrepToolParams): Promise<GrepToolResult> { async execute(params: GrepToolParams): Promise<ToolResult> {
const validationError = this.invalidParams(params); const validationError = this.invalidParams(params);
if (validationError) { if (validationError) {
console.error(`GrepTool Parameter Validation Failed: ${validationError}`); console.error(`GrepTool Parameter Validation Failed: ${validationError}`);
@@ -253,7 +249,7 @@ export class GrepTool extends BaseTool<GrepToolParams, GrepToolResult> {
}); });
child.on('close', (code) => resolve(code === 0)); child.on('close', (code) => resolve(code === 0));
child.on('error', () => resolve(false)); child.on('error', () => resolve(false));
} catch (e) { } catch {
resolve(false); resolve(false);
} }
}); });
@@ -277,10 +273,10 @@ export class GrepTool extends BaseTool<GrepToolParams, GrepToolResult> {
return true; return true;
} }
return false; return false;
} catch (err: any) { } catch (error: unknown) {
if (err.code !== 'ENOENT') { if (!isNodeError(error) || error.code !== 'ENOENT') {
console.error( console.error(
`Error checking for .git in ${currentPath}: ${err.message}`, `Error checking for .git in ${currentPath}: ${error}`,
); );
return false; return false;
} }
@@ -291,9 +287,9 @@ export class GrepTool extends BaseTool<GrepToolParams, GrepToolResult> {
} }
currentPath = path.dirname(currentPath); currentPath = path.dirname(currentPath);
} }
} catch (err: any) { } catch (error: unknown) {
console.error( console.error(
`Error traversing directory structure upwards from ${dirPath}: ${err instanceof Error ? err.message : String(err)}`, `Error traversing directory structure upwards from ${dirPath}: ${error instanceof Error ? error.message : error}`,
); );
} }
return false; return false;
@@ -446,9 +442,9 @@ export class GrepTool extends BaseTool<GrepToolParams, GrepToolResult> {
}); });
}); });
return this.parseGrepOutput(output, absolutePath); return this.parseGrepOutput(output, absolutePath);
} catch (gitError: any) { } catch (gitError: unknown) {
console.error( console.error(
`GrepTool: git grep strategy failed: ${gitError.message}. Falling back...`, `GrepTool: git grep strategy failed: ${getErrorMessage(gitError)}. Falling back...`,
); );
} }
} }
@@ -512,9 +508,9 @@ export class GrepTool extends BaseTool<GrepToolParams, GrepToolResult> {
}); });
}); });
return this.parseGrepOutput(output, absolutePath); return this.parseGrepOutput(output, absolutePath);
} catch (grepError: any) { } catch (grepError: unknown) {
console.error( console.error(
`GrepTool: System grep strategy failed: ${grepError.message}. Falling back...`, `GrepTool: System grep strategy failed: ${getErrorMessage(grepError)}. Falling back...`,
); );
} }
} }
@@ -559,19 +555,19 @@ export class GrepTool extends BaseTool<GrepToolParams, GrepToolResult> {
}); });
} }
}); });
} catch (readError: any) { } catch (readError: unknown) {
if (readError.code !== 'ENOENT') { if (!isNodeError(readError) || readError.code !== 'ENOENT') {
console.error( console.error(
`GrepTool: Could not read or process file ${fileAbsolutePath}: ${readError.message}`, `GrepTool: Could not read or process file ${fileAbsolutePath}: ${getErrorMessage(readError)}`,
); );
} }
} }
} }
return allMatches; return allMatches;
} catch (error: any) { } catch (error: unknown) {
console.error( console.error(
`GrepTool: Error during performGrepSearch (Strategy: ${strategyUsed}): ${error.message}`, `GrepTool: Error during performGrepSearch (Strategy: ${strategyUsed}): ${getErrorMessage(error)}`,
); );
throw error; // Re-throw to be caught by the execute method's handler throw error; // Re-throw to be caught by the execute method's handler
} }

View File

@@ -24,17 +24,12 @@ export interface ReadFileToolParams {
limit?: number; limit?: number;
} }
/**
* Standardized result from the ReadFile tool
*/
export interface ReadFileToolResult extends ToolResult {}
/** /**
* Implementation of the ReadFile tool that reads files from the filesystem * Implementation of the ReadFile tool that reads files from the filesystem
*/ */
export class ReadFileTool extends BaseTool< export class ReadFileTool extends BaseTool<
ReadFileToolParams, ReadFileToolParams,
ReadFileToolResult ToolResult
> { > {
static readonly Name: string = 'read_file'; static readonly Name: string = 'read_file';
@@ -166,7 +161,7 @@ export class ReadFileTool extends BaseTool<
// If more than 30% are non-printable, likely binary // If more than 30% are non-printable, likely binary
return nonPrintableCount / bytesRead > 0.3; return nonPrintableCount / bytesRead > 0.3;
} catch (error) { } catch {
return false; return false;
} }
} }
@@ -214,7 +209,7 @@ export class ReadFileTool extends BaseTool<
* @param params Parameters for the file reading * @param params Parameters for the file reading
* @returns Result with file contents * @returns Result with file contents
*/ */
async execute(params: ReadFileToolParams): Promise<ReadFileToolResult> { async execute(params: ReadFileToolParams): Promise<ToolResult> {
const validationError = this.invalidParams(params); const validationError = this.invalidParams(params);
const filePath = params.file_path; const filePath = params.file_path;
if (validationError) { if (validationError) {

View File

@@ -12,7 +12,7 @@ export interface ToolCallEvent {
status: ToolCallStatus; status: ToolCallStatus;
callId: string; callId: string;
name: string; name: string;
args: Record<string, any>; args: Record<string, never>;
resultDisplay: ToolResultDisplay | undefined; resultDisplay: ToolResultDisplay | undefined;
confirmationDetails: ToolCallConfirmationDetails | undefined; confirmationDetails: ToolCallConfirmationDetails | undefined;
} }

View File

@@ -0,0 +1,18 @@
export function isNodeError(error: unknown): error is NodeJS.ErrnoException {
return error instanceof Error && 'code' in error;
}
export function getErrorMessage(error: unknown): string {
if (error instanceof Error) {
return error.message;
} else {
// Attempt to convert the non-Error value to a string for logging
try {
const errorMessage = String(error);
return errorMessage;
} catch {
// If String() itself fails (highly unlikely)
return 'Failed to get error details';
}
}
}