class OpenUI::Rails::Configuration

Global, process-wide configuration for the gem.

OpenUI::Rails.configure do |config|
  config.transport = :turbo
  config.library_spec_path = Rails.root.join("config/openui/library.spec.json")
  config.runtime_assets = :self_hosted
end

Constants

ASSET_STRATEGIES

Runtime asset strategies supported by the gem.

DEFAULT_ACTIONS_ENDPOINT

Default engine endpoint for action submissions.

DEFAULT_IDEMPOTENCY_TTL

Lifetime of an action idempotency claim, in seconds.

DEFAULT_PENDING_BYTE_LIMIT

Buffer size before the bridge emits a delta, in bytes.

DEFAULT_STREAM_BYTE_LIMIT

Maximum response size passed through the stream bridge, in bytes.

TRANSPORTS

Transport names accepted by the host preference setting.

Attributes

action_registry[RW]
actions_endpoint[RW]
actor_resolver[RW]
authorize_action[RW]
idempotency_scope[RW]
idempotency_ttl[RW]
library_name[RW]
library_spec_path[RW]
local_runtime_path[RW]
on_error[RW]
pending_byte_limit[RW]
prompt_path[RW]
runtime_assets[R]
runtime_integrity[RW]
runtime_stylesheet_integrity[RW]
runtime_stylesheet_url[RW]
runtime_url[RW]
runtime_version[RW]
stream_byte_limit[RW]
surface_id_param[RW]
surface_resolver[RW]
transport[R]

Public Class Methods

new() click to toggle source

Create the default integration settings.

@return [Configuration]

# File lib/openui/rails/configuration.rb, line 42
def initialize
  @transport = :turbo
  @runtime_assets = :self_hosted
  @library_spec_path = nil
  @prompt_path = nil
  @library_name = :chat
  @actions_endpoint = DEFAULT_ACTIONS_ENDPOINT
  @surface_id_param = :surface_id
  @stream_byte_limit = DEFAULT_STREAM_BYTE_LIMIT
  @pending_byte_limit = DEFAULT_PENDING_BYTE_LIMIT
  @idempotency_ttl = DEFAULT_IDEMPOTENCY_TTL
  @idempotency_store = nil
  @authorize_action = nil
  @on_error = nil
  @action_registry = nil
  @surface_resolver = nil
  @actor_resolver = nil
  @runtime_version = nil
  @runtime_url = nil
  @runtime_integrity = nil
  @local_runtime_path = "/openui"
end

Public Instance Methods

actor_resolver=(callable) click to toggle source

Set the callable that resolves the current actor from the engine controller.

@param callable [#call, nil] receives the engine controller

@return [#call, nil]

# File lib/openui/rails/configuration.rb, line 163
def actor_resolver=(callable)
  unless callable.nil? || callable.respond_to?(:call)
    raise ConfigurationError, "actor_resolver must respond to #call"
  end

  @actor_resolver = callable
end
authorize_action=(callable) click to toggle source

Set the global authorizer used when an action has no local authorizer.

@param callable [#call, nil] receives an Actions::Invocation

@return [#call, nil]

# File lib/openui/rails/configuration.rb, line 137
def authorize_action=(callable)
  unless callable.nil? || callable.respond_to?(:call)
    raise ConfigurationError, "authorize_action must respond to #call"
  end

  @authorize_action = callable
end
idempotency_scope=(callable) click to toggle source

Set a callable that returns a stable actor and tenant idempotency scope.

@param callable [#call, nil] receives an Actions::Invocation

@return [#call, nil]

# File lib/openui/rails/configuration.rb, line 124
def idempotency_scope=(callable)
  unless callable.nil? || callable.respond_to?(:call)
    raise ConfigurationError, "idempotency_scope must respond to #call"
  end

  @idempotency_scope = callable
end
idempotency_store() click to toggle source

Return the configured store, creating a process-local memory store by default.

@return [#claim, record]

# File lib/openui/rails/configuration.rb, line 174
def idempotency_store
  @idempotency_store ||= Actions::Idempotency::MemoryStore.new
end
idempotency_store=(store) click to toggle source

Set the store used to claim and record action idempotency keys. A shared store must implement the atomic contract described in the configuration guide.

@param store [#claim, record, nil]

@return [#claim, record]

# File lib/openui/rails/configuration.rb, line 101
def idempotency_store=(store)
  unless store.nil? || (store.respond_to?(:claim) && store.respond_to?(:record))
    raise ConfigurationError, "idempotency_store must respond to #claim and #record"
  end

  @idempotency_store = store
end
library_fingerprint() click to toggle source

Fingerprint of the pinned library manifest, or nil when unconfigured.

@return [String, nil]

# File lib/openui/rails/configuration.rb, line 189
def library_fingerprint
  library_manifest&.fingerprint
end
library_manifest() click to toggle source

Memoized manifest for the configured ‘library_spec_path`.

@return [LibraryManifest, nil]

# File lib/openui/rails/configuration.rb, line 196
def library_manifest
  return @library_manifest if defined?(@library_manifest) && @library_manifest

  path = library_spec_path
  return nil if path.nil?

  @library_manifest = LibraryManifest.load(path)
end
library_spec_path=(path) click to toggle source

Set the manifest path and clear the cached manifest.

@param path [String, Pathname, nil]

@return [String, Pathname, nil]

# File lib/openui/rails/configuration.rb, line 114
def library_spec_path=(path)
  @library_spec_path = path
  reset!
end
reset!() click to toggle source

Drop memoized derived state (used after config changes and in tests). The action registry and idempotency store are runtime state and are preserved so re-configuring does not silently unregister actions.

@return [self]

# File lib/openui/rails/configuration.rb, line 210
def reset!
  remove_instance_variable(:@library_manifest) if defined?(@library_manifest)
  self
end
runtime_assets=(value) click to toggle source

Choose self-hosted or externally served runtime assets.

@param value [Symbol] ‘:self_hosted` or `:external`

@return [Symbol]

# File lib/openui/rails/configuration.rb, line 84
def runtime_assets=(value)
  value = value.to_sym if value.respond_to?(:to_sym)
  unless ASSET_STRATEGIES.include?(value)
    raise ConfigurationError,
          "unsupported runtime_assets #{value.inspect}; expected one of #{ASSET_STRATEGIES.inspect}"
  end

  @runtime_assets = value
end
surface_resolver=(callable) click to toggle source

Set the resolver that loads a surface inside the actor’s permitted scope.

@param callable [#call, nil] receives surface_id, actor, and context

@return [#call, nil]

# File lib/openui/rails/configuration.rb, line 150
def surface_resolver=(callable)
  unless callable.nil? || callable.respond_to?(:call)
    raise ConfigurationError, "surface_resolver must respond to #call"
  end

  @surface_resolver = callable
end
transport=(value) click to toggle source

Set the preferred host stream transport.

@param value [Symbol] ‘:turbo`, `:action_cable`, or `:none`

@return [Symbol]

# File lib/openui/rails/configuration.rb, line 70
def transport=(value)
  value = value.to_sym if value.respond_to?(:to_sym)
  unless TRANSPORTS.include?(value)
    raise ConfigurationError, "unsupported transport #{value.inspect}; expected one of #{TRANSPORTS.inspect}"
  end

  @transport = value
end
validate!() click to toggle source

Explicit host readiness check. Not run during boot: the generator and runtime-fetch task must work before library files have been created.

@return [true]

@raise [ConfigurationError, ManifestError] when required settings or files are invalid

# File lib/openui/rails/configuration.rb, line 221
def validate!
  raise ConfigurationError, "library_spec_path is not configured" if library_spec_path.nil?

  if runtime_assets == :external && (runtime_url.nil? || runtime_url.empty?)
    raise ConfigurationError, "runtime_assets :external requires an explicit, version-pinned runtime_url"
  end

  unless pending_byte_limit.is_a?(Integer) && stream_byte_limit.is_a?(Integer) &&
      pending_byte_limit > 0 && stream_byte_limit > 0
    raise ConfigurationError, "stream byte limits must be positive"
  end

  if pending_byte_limit > stream_byte_limit
    raise ConfigurationError, "pending_byte_limit must not exceed stream_byte_limit"
  end

  unless idempotency_ttl.is_a?(Numeric) && idempotency_ttl.finite? && idempotency_ttl > 0
    raise ConfigurationError, "idempotency_ttl must be positive"
  end

  reset!
  library_manifest
  PromptBuilder.load(self) if prompt_path
  true
end