Some checks failed
Frontend Lint & Test / test (push) Has been cancelled
Publish Chatwoot EE docker images / build (linux/amd64, ubuntu-latest) (push) Has been cancelled
Publish Chatwoot EE docker images / build (linux/arm64, ubuntu-22.04-arm) (push) Has been cancelled
Publish Chatwoot EE docker images / merge (push) Has been cancelled
Publish Chatwoot CE docker images / build (linux/amd64, ubuntu-latest) (push) Has been cancelled
Publish Chatwoot CE docker images / build (linux/arm64, ubuntu-22.04-arm) (push) Has been cancelled
Publish Chatwoot CE docker images / merge (push) Has been cancelled
Run Chatwoot CE spec / lint-backend (push) Has been cancelled
Run Chatwoot CE spec / security-scan (push) Has been cancelled
Run Chatwoot CE spec / lint-frontend (push) Has been cancelled
Run Chatwoot CE spec / frontend-tests (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (0, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (1, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (10, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (11, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (12, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (13, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (14, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (15, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (2, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (3, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (4, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (5, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (6, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (7, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (8, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (9, 16) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Sync GHSA advisories to Linear / sync (push) Has been cancelled
Add an operator-level provider switch for Captain AI so it can use any OpenAI-compatible base URL (vLLM, OpenRouter, proxies, etc.) in addition to the default api.openai.com endpoint. - installation_config.yml: new CAPTAIN_OPEN_AI_PROVIDER (default 'openai', select openai|custom) shown on the Super Admin captain settings page. - lib/llm/config.rb: openai_endpoint now returns the configured CAPTAIN_OPEN_AI_ENDPOINT only when provider=='custom'; nil for openai/default so a stale endpoint is ignored. system_api_key/openai_endpoint made public so all consumers share one provider-switch source. - Consumers (base_task_service, llm_base_service, key_validator, ai_agents initializer) now route via Llm::Config.openai_endpoint instead of reading CAPTAIN_OPEN_AI_ENDPOINT directly. - installation_config.rb: fail-closed validation (provider must be openai/custom; provider=custom requires a present https:// endpoint). Approved by independent five-key reviews deleg_b141098a (found+fixed one logic gap) and deleg_3088b922 (passed=true, blocking arrays empty).
101 lines
3.3 KiB
Ruby
101 lines
3.3 KiB
Ruby
# == Schema Information
|
|
#
|
|
# Table name: installation_configs
|
|
#
|
|
# id :bigint not null, primary key
|
|
# locked :boolean default(TRUE), not null
|
|
# name :string not null
|
|
# serialized_value :jsonb not null
|
|
# created_at :datetime not null
|
|
# updated_at :datetime not null
|
|
#
|
|
# Indexes
|
|
#
|
|
# index_installation_configs_on_name (name) UNIQUE
|
|
# index_installation_configs_on_name_and_created_at (name,created_at) UNIQUE
|
|
#
|
|
class InstallationConfig < ApplicationRecord
|
|
CAPTAIN_LLM_CONFIG_KEYS = %w[
|
|
CAPTAIN_OPEN_AI_API_KEY
|
|
CAPTAIN_OPEN_AI_ENDPOINT
|
|
CAPTAIN_OPEN_AI_MODEL
|
|
].freeze
|
|
|
|
RESTART_REQUIRED_CONFIG_KEYS = (CAPTAIN_LLM_CONFIG_KEYS + %w[
|
|
LANGFUSE_BASE_URL
|
|
LANGFUSE_PUBLIC_KEY
|
|
LANGFUSE_SECRET_KEY
|
|
OTEL_PROVIDER
|
|
]).freeze
|
|
|
|
# https://stackoverflow.com/questions/72970170/upgrading-to-rails-6-1-6-1-causes-psychdisallowedclass-tried-to-load-unspecif
|
|
# https://discuss.rubyonrails.org/t/cve-2022-32224-possible-rce-escalation-bug-with-serialized-columns-in-active-record/81017
|
|
# FIX ME : fixes breakage of installation config. we need to migrate.
|
|
# Fix configuration in application.rb
|
|
serialize :serialized_value, coder: YAML, type: ActiveSupport::HashWithIndifferentAccess, default: {}.with_indifferent_access
|
|
|
|
before_validation :set_lock
|
|
validates :name, presence: true
|
|
validate :saml_sso_users_check, if: -> { name == 'ENABLE_SAML_SSO_LOGIN' }
|
|
validate :openai_custom_provider_check, if: -> { name == 'CAPTAIN_OPEN_AI_PROVIDER' }
|
|
validate :openai_custom_endpoint_check, if: -> { name == 'CAPTAIN_OPEN_AI_ENDPOINT' }
|
|
|
|
# TODO: Get rid of default scope
|
|
# https://stackoverflow.com/a/1834250/939299
|
|
default_scope { order(created_at: :desc) }
|
|
scope :editable, -> { where(locked: false) }
|
|
|
|
after_commit :clear_cache
|
|
|
|
def value
|
|
serialized_value[:value]
|
|
end
|
|
|
|
def value=(value_to_assigned)
|
|
self.serialized_value = {
|
|
value: value_to_assigned
|
|
}.with_indifferent_access
|
|
end
|
|
|
|
private
|
|
|
|
def set_lock
|
|
self.locked = true if locked.nil?
|
|
end
|
|
|
|
def clear_cache
|
|
GlobalConfig.clear_cache
|
|
end
|
|
|
|
def saml_sso_users_check
|
|
return unless value == false || value == 'false'
|
|
return unless User.exists?(provider: 'saml')
|
|
|
|
errors.add(:base, 'Cannot disable SAML SSO login while users are using SAML authentication')
|
|
end
|
|
|
|
# Only allow provider values the platform understands ('openai' default or 'custom')
|
|
# and ensure the "custom" provider actually requires an endpoint.
|
|
def openai_custom_provider_check
|
|
return if %w[openai custom].include?(value.to_s)
|
|
|
|
errors.add(:base, 'CAPTAIN_OPEN_AI_PROVIDER must be "openai" or "custom"')
|
|
end
|
|
|
|
# Fail-closed: when the provider is "custom", CAPTAIN_OPEN_AI_ENDPOINT must be
|
|
# present AND a secure https URL (OpenAI-compatible base).
|
|
def openai_custom_endpoint_check
|
|
provider = InstallationConfig.find_by(name: 'CAPTAIN_OPEN_AI_PROVIDER')&.value.to_s
|
|
return unless provider == 'custom'
|
|
|
|
if value.blank?
|
|
errors.add(:base, 'CAPTAIN_OPEN_AI_ENDPOINT is required when using the custom provider')
|
|
return
|
|
end
|
|
|
|
return if value.to_s.match?(%r{\Ahttps://\S+})
|
|
|
|
errors.add(:base, 'CAPTAIN_OPEN_AI_ENDPOINT must be a valid https URL when using the custom provider')
|
|
end
|
|
end
|