discourse/plugins/discourse-ai/lib/engine.rb
Alan Guo Xiang Tan 467d42c2df
DEV: Add DiscourseAi.register_feature public API (#39127)
This commit introduces `DiscourseAi.register_feature` as the public
API for other plugins to register AI-powered features. Previously,
plugins had to manually register with `DiscoursePluginRegistry`, define
their own agent site setting in `settings.yml`, create their own
`EnumSiteSetting` subclass for the agent enum, register the site setting
area, and assign settings to that area. This was a lot of boilerplate
that every plugin would need to duplicate.

This commit consolidates all of that into a single
`DiscourseAi.register_feature` call that handles:

1. Auto-defining the `<module>_<feature>_agent` site setting with the
correct enum, `depends_on`, and `depends_behavior` configuration
2. Registering the `ai-features/<module_name>` site setting area so the
admin edit page can load the settings
3. Assigning both the generated agent setting and the
`enabled_by_setting` to that area
4. Registering the feature in the `DiscoursePluginRegistry` external AI
features registry

The data-explorer plugin is updated to use the new API, which lets it
drop its custom `AiAgentEnumerator` class, the manual
`data_explorer_query_generation_agent` setting definition in
`settings.yml`, and the manual area registration and assignment code.
2026-05-12 10:16:07 +08:00

94 lines
3 KiB
Ruby
Vendored

# frozen_string_literal: true
module DiscourseAi
class Engine < ::Rails::Engine
engine_name PLUGIN_NAME
isolate_namespace DiscourseAi
end
# Register an AI-powered feature from another plugin. The feature will
# appear in the AI features admin page, where admins can select which agent
# backs it.
#
# The agent_klass must be a subclass of {DiscourseAi::Agents::Agent} and
# implement at minimum `#tools` (returning an array of tool classes) and
# `#system_prompt` (returning the prompt string).
#
# Each tool class must be a subclass of {DiscourseAi::Agents::Tools::Tool}
# and implement:
# - `.signature` - returns a hash with `name`, `description`, and `parameters`
# - `.name` - returns the tool name string
# - `#invoke` - executes the tool and returns a result hash
#
# @example Defining a tool, agent, and registering the feature
# class MyPlugin::Tools::RunQuery < DiscourseAi::Agents::Tools::Tool
# def self.signature
# { name: "run_query", description: "Runs a query", parameters: [] }
# end
#
# def self.name
# "run_query"
# end
#
# def invoke
# { result: "ok" }
# end
# end
#
# class MyPlugin::QueryAgent < DiscourseAi::Agents::Agent
# def tools
# [MyPlugin::Tools::RunQuery]
# end
#
# def system_prompt
# "You are a query expert."
# end
# end
#
# DiscourseAi.register_feature(
# module_name: :my_plugin,
# feature: :query_generation,
# agent_klass: MyPlugin::QueryAgent,
# enabled_by_setting: "my_plugin_ai_enabled",
# plugin: self,
# )
#
# @param module_name [Symbol] groups features under a module in the admin UI
# @param feature [Symbol] the feature name within the module
# @param agent_klass [Class] a subclass of {DiscourseAi::Agents::Agent}
# @param enabled_by_setting [String, nil] site setting that gates this feature
# @param plugin [Plugin::Instance] the plugin instance registering the feature
def self.register_feature(module_name:, feature:, agent_klass:, enabled_by_setting: nil, plugin:)
area = "ai-features/#{module_name}"
plugin.register_site_setting_area(area)
setting_name = "#{module_name}_#{feature}_agent"
SiteSetting.send(
:setting,
setting_name.to_sym,
DiscourseAi::Agents::Agent.external_agent_id(agent_klass).to_s,
type: "enum",
enum: "DiscourseAi::Configuration::AgentEnumerator",
depends_on: ["discourse_ai_enabled", enabled_by_setting].compact,
depends_behavior: "hidden",
area: area,
)
if enabled_by_setting.present?
key = enabled_by_setting.to_sym
existing = SiteSetting.areas[key]
SiteSetting.areas[key] = existing ? (Array.wrap(existing) | [area]) : area
end
DiscoursePluginRegistry.register_external_ai_feature(
{
module_name: module_name,
feature: feature,
agent_klass: agent_klass,
enabled_by_setting: enabled_by_setting,
visible: true,
},
plugin,
)
end
end