Class: LittleGhost::ModelResolver

Inherits:
Object
  • Object
show all
Defined in:
lib/little_ghost/model_resolver.rb

Overview

Resolves logical profiles, canonical provider:model-id targets, and inline model mappings into executable Model objects. Subclasses may override #resolve, #details, or #refresh! while preserving those public signatures.

Constant Summary collapse

DEFAULT_CREDENTIALS =

:nodoc:

[ # :nodoc:
  ["LITTLEGHOST_OPENROUTER_API_KEY", "openrouter"],
  ["LITTLEGHOST_OPENAI_API_KEY", "openai"],
  ["OPENROUTER_API_KEY", "openrouter"],
  ["OPENAI_API_KEY", "openai"]
].freeze
DEFAULT_MODELS =

:nodoc:

{ # :nodoc:
  "openrouter" => "openai/gpt-5.6-luna",
  "openai" => "gpt-5.6-luna"
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(providers: nil, profiles: nil, default_model: nil, provider_registry: ProviderRegistry.new, catalog: nil, catalog_sources: [], provider_adapters: {}, credential_resolver: nil) ⇒ ModelResolver

Builds a resolver from explicit provider connections and profiles. With neither, conventional credentials select GPT-5.6 Luna.



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/little_ghost/model_resolver.rb', line 24

def initialize(providers: nil, profiles: nil, default_model: nil, provider_registry: ProviderRegistry.new,
  catalog: nil, catalog_sources: [], provider_adapters: {}, credential_resolver: nil)
  unless providers.nil? || providers.is_a?(Providers::Configuration)
    raise ArgumentError, "providers must be a LittleGhost::Providers::Configuration"
  end

  @providers = providers
  @connections = providers&.connections || {}
  @profiles = normalize_profiles(profiles || {})
  @default_model = default_model&.to_s || "default"
  @provider_registry = provider_adapters.empty? ? provider_registry : ProviderRegistry.new(adapters: provider_adapters)
  @credential_resolver = credential_resolver
  configure_default_providers unless providers
  configure_default_profiles unless profiles
  sources = catalog_sources.empty? ? built_in_catalog_sources : catalog_sources
  @catalog = catalog || Models::Catalog.new(sources:)
end

Instance Attribute Details

#catalogObject (readonly)

Provider configuration and model metadata catalog.



20
21
22
# File 'lib/little_ghost/model_resolver.rb', line 20

def catalog
  @catalog
end

#default_modelObject (readonly)

Logical role used when an agent does not declare one.



43
44
45
# File 'lib/little_ghost/model_resolver.rb', line 43

def default_model
  @default_model
end

#providersObject (readonly)

Provider configuration and model metadata catalog.



20
21
22
# File 'lib/little_ghost/model_resolver.rb', line 20

def providers
  @providers
end

Instance Method Details

#details(target) ⇒ Object

Returns normalized metadata for target without constructing a provider.



82
83
# File 'lib/little_ghost/model_resolver.rb', line 82

def details(target) = catalog.details(target)
# Refreshes configured metadata sources, retaining stale data on failure.

#refresh!(target: nil) ⇒ Object

Refreshes configured metadata sources, retaining stale data on failure.



84
# File 'lib/little_ghost/model_resolver.rb', line 84

def refresh!(target: nil) = catalog.refresh!(target:)

#resolve(selection, invocation: nil, context: nil, profiles: nil, **options) ⇒ Object

Resolves a logical role, canonical target, or inline model mapping into an executable Model. Inline mappings require provider and model; remaining entries are trusted model settings. The optional profiles mapping applies only to role selections and is never read from invocation.



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/little_ghost/model_resolver.rb', line 49

def resolve(selection, invocation: nil, context: nil, profiles: nil, **options)
  base, role = normalize_selection(selection)
  if role
    base = resolved_role(role)
    profile_overlays = profiles || {}
    override_names(role).each do |profile|
      base = merge(base, profile_overlays[profile] || profile_overlays[profile.to_sym])
    end
  end
  target = Models::Target.parse(base.fetch(:target))
  provider_config = @connections.fetch(target.provider) do
    raise ConfigurationError, "Model target references unknown provider #{target.provider}"
  end
  provider_config = provider_config.merge(resolved_credentials(target.provider, provider_config)) if @credential_resolver
  details = details_for(target, base[:details])
  settings = clamp_output_tokens(base.fetch(:settings), details)
  adapter = provider_config.fetch("adapter")
  provider = @provider_registry.build(
    adapter:,
    model: target.model_id,
    configuration: provider_config,
    request: base.fetch(:request),
    role:,
    settings:,
    details:,
    invocation:,
    context:,
    **options
  )
  Model.new(provider:, target:, settings:, details:, role:)
end