#frozen_string_literal: true module DiscourseAi module AiBot module Tools class SettingContext < Tool MAX_CONTEXT_TOKENS = 2000 CODE_FILE_EXTENSIONS = "rb,js,gjs,hbs" class << self def rg_installed? if defined?(@rg_installed) @rg_installed else @rg_installed = begin Discourse::Utils.execute_command("which", "rg") true rescue Discourse::Utils::CommandError false end end end def signature { name: name, description: "Will provide you with full context regarding a particular site setting in Discourse", parameters: [ { name: "setting_name", description: "The name of the site setting we need context for", type: "string", required: true, }, ], } end def name "setting_context" end end def setting_name parameters[:setting_name] end def invoke(_bot_user, llm) if !self.class.rg_installed? return( { setting_name: setting_name, context: "This command requires the rg command line tool to be installed on the server", } ) end if !SiteSetting.has_setting?(setting_name) { setting_name: setting_name, context: "This setting does not exist" } else description = SiteSetting.description(setting_name) result = +"# #{setting_name}\n#{description}\n\n" setting_info = find_setting_info(setting_name, [Rails.root.join("config", "site_settings.yml").to_s]) if !setting_info setting_info = find_setting_info(setting_name, Dir[Rails.root.join("plugins/**/settings.yml")]) end result << setting_info result << "\n\n" %w[lib app plugins].each do |dir| path = Rails.root.join(dir).to_s result << Discourse::Utils.execute_command( "rg", setting_name, path, "-g", "!**/spec/**", "-g", "!**/dist/**", "-g", "*.{#{CODE_FILE_EXTENSIONS}}", "-C", "10", "--color", "never", "--heading", "--no-ignore", chdir: path, success_status_codes: [0, 1], ) end result.gsub!(/^#{Regexp.escape(Rails.root.to_s)}/, "") result = llm.tokenizer.truncate(result, MAX_CONTEXT_TOKENS) { setting_name: setting_name, context: result } end end private def find_setting_info(name, paths) path, result = nil paths.each do |search_path| result = Discourse::Utils.execute_command( "rg", name, search_path, "-g", "*.{#{CODE_FILE_EXTENSIONS}}", "-A", "10", "--color", "never", "--heading", success_status_codes: [0, 1], ) if !result.blank? path = search_path break end end if result.blank? nil else rows = result.split("\n") leading_spaces = rows[0].match(/^\s*/)[0].length filtered = [] rows.each do |row| if !filtered.blank? break if row.match(/^\s*/)[0].length <= leading_spaces end filtered << row end filtered.unshift("#{path}") filtered.join("\n") end end def description_args parameters.slice(:setting_name) end end end end end