UNPKG

@lobehub/chat

Version:

Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.

174 lines (150 loc) 4.91 kB
import type { ArgumentMatcher, HumanInterventionPolicy, HumanInterventionRule, ShouldInterveneParams, } from '@lobechat/types'; /** * Intervention Checker * Determines whether a tool call requires human intervention */ export class InterventionChecker { /** * Check if a tool call requires intervention * * @param params - Parameters object containing config, toolArgs, confirmedHistory, and toolKey * @returns Policy to apply */ static shouldIntervene(params: ShouldInterveneParams): HumanInterventionPolicy { const { config, toolArgs = {}, confirmedHistory = [], toolKey } = params; // No config means never intervene (auto-execute) if (!config) return 'never'; // Simple policy string if (typeof config === 'string') { // For 'first' policy, check if already confirmed if (config === 'first' && toolKey && confirmedHistory.includes(toolKey)) { return 'never'; } return config; } // Array of rules - find first matching rule for (const rule of config) { if (this.matchesRule(rule, toolArgs)) { const policy = rule.policy; // For 'first' policy, check if already confirmed if (policy === 'first' && toolKey && confirmedHistory.includes(toolKey)) { return 'never'; } return policy; } } // No rule matched - default to always for safety return 'always'; } /** * Check if tool arguments match a rule * * @param rule - Rule to check * @param toolArgs - Tool call arguments * @returns true if matches */ private static matchesRule(rule: HumanInterventionRule, toolArgs: Record<string, any>): boolean { // No match criteria means it's a default rule if (!rule.match) return true; // Check each parameter matcher for (const [paramName, matcher] of Object.entries(rule.match)) { const paramValue = toolArgs[paramName]; // Parameter not present in args if (paramValue === undefined) return false; // Check if value matches if (!this.matchesArgument(matcher, paramValue)) { return false; } } return true; } /** * Check if a parameter value matches the matcher * * @param matcher - Argument matcher * @param value - Parameter value * @returns true if matches */ private static matchesArgument(matcher: ArgumentMatcher, value: any): boolean { const strValue = String(value); // Simple string matcher if (typeof matcher === 'string') { return this.matchPattern(matcher, strValue); } // Complex matcher with type const { pattern, type } = matcher; switch (type) { case 'exact': { return strValue === pattern; } case 'prefix': { return strValue.startsWith(pattern); } case 'wildcard': { return this.matchPattern(pattern, strValue); } case 'regex': { return new RegExp(pattern).test(strValue); } default: { return false; } } } /** * Match wildcard pattern (supports * wildcard) * * @param pattern - Pattern with wildcards * @param value - Value to match * @returns true if matches */ private static matchPattern(pattern: string, value: string): boolean { // Check for colon-based prefix matching (e.g., "git add:*") if (pattern.includes(':')) { const [prefix, suffix] = pattern.split(':'); if (suffix === '*') { return value.startsWith(prefix + ':') || value === prefix; } } // Convert wildcard pattern to regex const regexPattern = pattern .replaceAll(/[$()+.?[\\\]^{|}]/g, '\\$&') // Escape special chars .replaceAll('*', '.*'); // Replace * with .* return new RegExp(`^${regexPattern}$`).test(value); } /** * Generate tool key from identifier and API name * * @param identifier - Tool identifier * @param apiName - API name * @param argsHash - Optional hash of arguments * @returns Tool key in format "identifier/apiName" or "identifier/apiName#hash" */ static generateToolKey(identifier: string, apiName: string, argsHash?: string): string { const baseKey = `${identifier}/${apiName}`; return argsHash ? `${baseKey}#${argsHash}` : baseKey; } /** * Generate simple hash of arguments for 'once' policy * * @param args - Tool call arguments * @returns Hash string */ static hashArguments(args: Record<string, any>): string { const sortedKeys = Object.keys(args).sort(); const str = sortedKeys.map((key) => `${key}=${JSON.stringify(args[key])}`).join('&'); // Simple hash function let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = (hash << 5) - hash + char; hash = hash & hash; // Convert to 32-bit integer } return Math.abs(hash).toString(36); } }