migrate fireToolNotificationHook to hookSystem (#17398)

Co-authored-by: Tommaso Sciortino <sciortino@gmail.com>
This commit is contained in:
Vedant Mahajan
2026-01-24 18:52:08 +05:30
committed by GitHub
parent 0242a3dc56
commit 84e882770b
6 changed files with 109 additions and 168 deletions

View File

@@ -24,6 +24,7 @@ import type {
BeforeToolSelectionHookOutput,
McpToolContext,
} from './types.js';
import { NotificationType } from './types.js';
import type { AggregatedHookResult } from './hookAggregator.js';
import type {
GenerateContentParameters,
@@ -33,6 +34,7 @@ import type {
ToolConfig,
ToolListUnion,
} from '@google/genai';
import type { ToolCallConfirmationDetails } from '../tools/tools.js';
/**
* Main hook system that coordinates all hook-related functionality
@@ -78,6 +80,73 @@ export interface AfterModelHookResult {
reason?: string;
}
/**
* Converts ToolCallConfirmationDetails to a serializable format for hooks.
* Excludes function properties (onConfirm, ideConfirmation) that can't be serialized.
*/
function toSerializableDetails(
details: ToolCallConfirmationDetails,
): Record<string, unknown> {
const base: Record<string, unknown> = {
type: details.type,
title: details.title,
};
switch (details.type) {
case 'edit':
return {
...base,
fileName: details.fileName,
filePath: details.filePath,
fileDiff: details.fileDiff,
originalContent: details.originalContent,
newContent: details.newContent,
isModifying: details.isModifying,
};
case 'exec':
return {
...base,
command: details.command,
rootCommand: details.rootCommand,
};
case 'mcp':
return {
...base,
serverName: details.serverName,
toolName: details.toolName,
toolDisplayName: details.toolDisplayName,
};
case 'info':
return {
...base,
prompt: details.prompt,
urls: details.urls,
};
default:
return base;
}
}
/**
* Gets the message to display in the notification hook for tool confirmation.
*/
function getNotificationMessage(
confirmationDetails: ToolCallConfirmationDetails,
): string {
switch (confirmationDetails.type) {
case 'edit':
return `Tool ${confirmationDetails.title} requires editing`;
case 'exec':
return `Tool ${confirmationDetails.title} requires execution`;
case 'mcp':
return `Tool ${confirmationDetails.title} requires MCP`;
case 'info':
return `Tool ${confirmationDetails.title} requires information`;
default:
return `Tool requires confirmation`;
}
}
export class HookSystem {
private readonly hookRegistry: HookRegistry;
private readonly hookRunner: HookRunner;
@@ -312,7 +381,7 @@ export class HookSystem {
);
return result.finalOutput;
} catch (error) {
debugLogger.debug(`BeforeTool hook failed for ${toolName}:`, error);
debugLogger.debug(`BeforeToolEvent failed for ${toolName}:`, error);
return undefined;
}
}
@@ -336,8 +405,28 @@ export class HookSystem {
);
return result.finalOutput;
} catch (error) {
debugLogger.debug(`AfterTool hook failed for ${toolName}:`, error);
debugLogger.debug(`AfterToolEvent failed for ${toolName}:`, error);
return undefined;
}
}
async fireToolNotificationEvent(
confirmationDetails: ToolCallConfirmationDetails,
): Promise<void> {
try {
const message = getNotificationMessage(confirmationDetails);
const serializedDetails = toSerializableDetails(confirmationDetails);
await this.hookEventHandler.fireNotificationEvent(
NotificationType.ToolPermission,
message,
serializedDetails,
);
} catch (error) {
debugLogger.debug(
`NotificationEvent failed for ${confirmationDetails.title}:`,
error,
);
}
}
}