@kinvolk/headlamp-plugin
Version:
The needed infrastructure for building Headlamp plugins.
179 lines (154 loc) • 5.08 kB
text/typescript
import { EventEmitter } from 'events';
export interface ToolCall {
id: string;
name: string;
description?: string;
arguments: Record<string, any>;
type: 'mcp' | 'regular';
}
export interface ToolApprovalRequest {
requestId: string;
toolCalls: ToolCall[];
resolve: (approvedToolIds: string[]) => void;
reject: (error: Error) => void;
}
export class ToolApprovalManager extends EventEmitter {
private static instance: ToolApprovalManager | null = null;
private pendingRequest: ToolApprovalRequest | null = null;
private autoApproveSettings: Map<string, boolean> = new Map();
private sessionAutoApproval: boolean = false;
private constructor() {
super();
}
public static getInstance(): ToolApprovalManager {
if (!ToolApprovalManager.instance) {
ToolApprovalManager.instance = new ToolApprovalManager();
}
return ToolApprovalManager.instance;
}
/**
* Request approval for tool execution
*/
public async requestApproval(toolCalls: ToolCall[]): Promise<string[]> {
// Check if session auto-approval is enabled
if (this.sessionAutoApproval) {
return toolCalls.map(tool => tool.id);
}
// Check for individual tool auto-approvals
const autoApprovedTools: string[] = [];
const needsApprovalTools: ToolCall[] = [];
for (const tool of toolCalls) {
if (this.autoApproveSettings.get(tool.name)) {
autoApprovedTools.push(tool.id);
} else {
needsApprovalTools.push(tool);
}
}
// If all tools are auto-approved, return them
if (needsApprovalTools.length === 0) {
return autoApprovedTools;
}
// If there's already a pending request, reject the previous one
if (this.pendingRequest) {
this.pendingRequest.reject(new Error('Request superseded by new tool approval request'));
}
return new Promise<string[]>((resolve, reject) => {
const requestId = `tool-approval-${Date.now()}-${Math.random()}`;
this.pendingRequest = {
requestId,
toolCalls: needsApprovalTools,
resolve: (approvedToolIds: string[]) => {
// Combine auto-approved and manually approved tools
const allApprovedIds = [...autoApprovedTools, ...approvedToolIds];
this.pendingRequest = null;
resolve(allApprovedIds);
},
reject: (error: Error) => {
this.pendingRequest = null;
reject(error);
},
};
// Emit event for UI components to listen to
this.emit('approval-requested', this.pendingRequest);
});
}
/**
* Approve tools from the UI
*/
public approveTools(requestId: string, approvedToolIds: string[], rememberChoice = false): void {
if (!this.pendingRequest || this.pendingRequest.requestId !== requestId) {
console.warn('No matching pending request for approval:', requestId);
return;
}
// Handle remember choice
if (rememberChoice) {
// If all tools were approved, enable session auto-approval
const allToolIds = this.pendingRequest.toolCalls.map(tool => tool.id);
if (approvedToolIds.length === allToolIds.length) {
this.sessionAutoApproval = true;
} else {
// Remember individual tool approvals
for (const toolCall of this.pendingRequest.toolCalls) {
if (approvedToolIds.includes(toolCall.id)) {
this.autoApproveSettings.set(toolCall.name, true);
}
}
}
}
this.pendingRequest.resolve(approvedToolIds);
}
/**
* Deny all tools from the UI
*/
public denyTools(requestId: string): void {
if (!this.pendingRequest || this.pendingRequest.requestId !== requestId) {
console.warn('No matching pending request for denial:', requestId);
return;
}
this.pendingRequest.reject(new Error('User denied tool execution'));
}
/**
* Get current pending request
*/
public getPendingRequest(): ToolApprovalRequest | null {
return this.pendingRequest;
}
/**
* Clear session settings (called when user explicitly clears or starts new session)
*/
public clearSession(): void {
this.sessionAutoApproval = false;
this.autoApproveSettings.clear();
}
/**
* Set session auto-approval
*/
public setSessionAutoApproval(enabled: boolean): void {
this.sessionAutoApproval = enabled;
}
/**
* Check if session auto-approval is enabled
*/
public isSessionAutoApprovalEnabled(): boolean {
return this.sessionAutoApproval;
}
/**
* Get auto-approval settings for debugging
*/
public getAutoApprovalSettings(): {
sessionAutoApproval: boolean;
toolSettings: Array<{ toolName: string; autoApprove: boolean }>;
} {
return {
sessionAutoApproval: this.sessionAutoApproval,
toolSettings: Array.from(this.autoApproveSettings.entries()).map(
([toolName, autoApprove]) => ({
toolName,
autoApprove,
})
),
};
}
}
// Export singleton instance
export const toolApprovalManager = ToolApprovalManager.getInstance();