mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-28 01:19:30 +08:00
Introduce a "secret contracts" system that lets AI tools declare required credentials by alias and bind them to existing AiSecret records via a new join table. Key changes: - New `ai_tool_secret_bindings` table and AiToolSecretBinding model linking AiTool to AiSecret by alias - `secret_contracts` JSONB column on ai_tools for declaring required credential aliases - `secrets.get(alias)` API available in tool scripts at runtime, with preloaded secret caching - Admin UI for managing credential contracts and bindings on the tool editor form, with status badges on the tool list - Tool presets (stock_quote, image generation) updated to use `secrets.get()` instead of hardcoded placeholder keys - Secrets list editor shows tool usages alongside LLM and embedding usages - Test modal supports passing secret bindings and rendering custom_raw output - Validation, error handling, and i18n for contracts/bindings - Persona import/export updated to include secret_contracts --------- Co-authored-by: Keegan George <kgeorge13@gmail.com> Co-authored-by: awesomerobot <kris.aubuchon@discourse.org>
62 lines
1.7 KiB
Ruby
Vendored
62 lines
1.7 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
class AiToolSecretBinding < ActiveRecord::Base
|
|
belongs_to :ai_tool
|
|
belongs_to :ai_secret
|
|
belongs_to :created_by, class_name: "User", optional: true
|
|
|
|
validates :alias,
|
|
presence: true,
|
|
length: {
|
|
maximum: 100,
|
|
},
|
|
format: {
|
|
with: AiTool::SECRET_ALIAS_PATTERN,
|
|
message: I18n.t("discourse_ai.tools.name.characters"),
|
|
},
|
|
uniqueness: {
|
|
scope: :ai_tool_id,
|
|
}
|
|
validates :ai_secret_id, presence: true
|
|
|
|
validate :secret_exists
|
|
validate :alias_declared
|
|
|
|
private
|
|
|
|
def secret_exists
|
|
return if ai_secret_id.blank?
|
|
return if AiSecret.exists?(ai_secret_id)
|
|
|
|
errors.add(:ai_secret_id, I18n.t("discourse_ai.tools.secret_bindings.secret_not_found"))
|
|
end
|
|
|
|
def alias_declared
|
|
return if self[:alias].blank? || ai_tool.blank?
|
|
return if ai_tool.secret_contract_for(self[:alias]).present?
|
|
|
|
errors.add(
|
|
:alias,
|
|
I18n.t("discourse_ai.tools.secret_bindings.alias_not_declared", alias: self[:alias]),
|
|
)
|
|
end
|
|
end
|
|
|
|
# == Schema Information
|
|
#
|
|
# Table name: ai_tool_secret_bindings
|
|
#
|
|
# id :bigint not null, primary key
|
|
# alias :string(100) not null
|
|
# created_at :datetime not null
|
|
# updated_at :datetime not null
|
|
# ai_secret_id :bigint not null
|
|
# ai_tool_id :bigint not null
|
|
# created_by_id :integer
|
|
#
|
|
# Indexes
|
|
#
|
|
# index_ai_tool_secret_bindings_on_ai_secret_id (ai_secret_id)
|
|
# index_ai_tool_secret_bindings_on_ai_tool_id (ai_tool_id)
|
|
# index_ai_tool_secret_bindings_on_ai_tool_id_and_alias (ai_tool_id,alias) UNIQUE
|
|
#
|