little_ghost 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +50 -47
- data/docs/guides/{Core Concepts.md → core_concepts.md} +40 -20
- data/docs/guides/getting_started.md +164 -0
- data/lib/little_ghost/ag_ui/adapter.rb +1 -1
- data/lib/little_ghost/agent/context_management.rb +1 -4
- data/lib/little_ghost/agent.rb +94 -26
- data/lib/little_ghost/agent_builder.rb +1 -1
- data/lib/little_ghost/configuration.rb +210 -29
- data/lib/little_ghost/data/model_catalog.json +19178 -0
- data/lib/little_ghost/errors.rb +6 -0
- data/lib/little_ghost/invocation.rb +2 -17
- data/lib/little_ghost/model.rb +53 -33
- data/lib/little_ghost/model_capabilities.rb +6 -5
- data/lib/little_ghost/model_resolver.rb +316 -0
- data/lib/little_ghost/models/catalog/models_dev_source.rb +62 -0
- data/lib/little_ghost/models/catalog/source.rb +30 -0
- data/lib/little_ghost/models/catalog.rb +154 -0
- data/lib/little_ghost/models/catalog_snapshot.rb +44 -0
- data/lib/little_ghost/models/configuration.rb +43 -0
- data/lib/little_ghost/models/details.rb +48 -0
- data/lib/little_ghost/models/target.rb +29 -0
- data/lib/little_ghost/provider_registry.rb +75 -0
- data/lib/little_ghost/providers/anthropic/catalog_source.rb +31 -0
- data/lib/little_ghost/providers/anthropic.rb +222 -0
- data/lib/little_ghost/providers/base.rb +46 -0
- data/lib/little_ghost/providers/bedrock/aws_protocol.rb +132 -0
- data/lib/little_ghost/providers/bedrock/catalog_source.rb +200 -0
- data/lib/little_ghost/providers/bedrock/credential_resolver.rb +123 -0
- data/lib/little_ghost/providers/bedrock/http_client.rb +78 -0
- data/lib/little_ghost/providers/bedrock.rb +23 -15
- data/lib/little_ghost/providers/configuration.rb +79 -0
- data/lib/little_ghost/providers/gemini/catalog_source.rb +35 -0
- data/lib/little_ghost/providers/gemini.rb +204 -0
- data/lib/little_ghost/providers/open_router/catalog_source.rb +42 -0
- data/lib/little_ghost/providers/open_router.rb +6 -2
- data/lib/little_ghost/providers/openai_compatible.rb +19 -23
- data/lib/little_ghost/providers/vertex_ai/credential_resolver.rb +90 -0
- data/lib/little_ghost/providers/vertex_ai.rb +38 -0
- data/lib/little_ghost/run.rb +7 -7
- data/lib/little_ghost/runtime.rb +10 -7
- data/lib/little_ghost/sandbox.rb +5 -5
- data/lib/little_ghost/session_store.rb +3 -3
- data/lib/little_ghost/structured_output.rb +2 -8
- data/lib/little_ghost/support/http_client.rb +186 -0
- data/lib/little_ghost/{providers → support}/sse_parser.rb +1 -1
- data/lib/little_ghost/tool.rb +5 -1
- data/lib/little_ghost/tool_registry.rb +2 -3
- data/lib/little_ghost/version.rb +1 -1
- data/lib/little_ghost/workflow.rb +1 -1
- data/lib/little_ghost.rb +21 -15
- metadata +28 -7
- data/docs/guides/Getting Started.md +0 -187
- data/lib/little_ghost/default_model_registry.rb +0 -71
- data/lib/little_ghost/model_registry.rb +0 -173
- data/lib/little_ghost/providers/http_transport.rb +0 -149
data/lib/little_ghost/agent.rb
CHANGED
|
@@ -35,12 +35,12 @@ module LittleGhost
|
|
|
35
35
|
# Capabilities such as skills, context management, loop detection, and
|
|
36
36
|
# delegation remain inactive until their DSL methods are called.
|
|
37
37
|
#
|
|
38
|
-
# The class-level +ask+
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
38
|
+
# The class-level +ask+ and +stream_ask+ helpers create fresh standalone
|
|
39
|
+
# entrypoints. Create an instance explicitly to reuse one Runtime across
|
|
40
|
+
# calls. Runtimes build bound instances internally; their +call+ method
|
|
41
|
+
# returns a RunResult and their +stream+ method follows the owning run's
|
|
42
|
+
# single-execution lifecycle. Closing an agent closes owned tools and any
|
|
43
|
+
# standalone workspace and sandbox.
|
|
44
44
|
# LittleGhost::Agent.ask uses <tt>You are a helpful agent.</tt> as its system
|
|
45
45
|
# prompt. Subclasses continue to use their inline or conventional prompts.
|
|
46
46
|
#
|
|
@@ -89,6 +89,19 @@ module LittleGhost
|
|
|
89
89
|
new.ask(message, **options)
|
|
90
90
|
end
|
|
91
91
|
|
|
92
|
+
# Streams +message+ through a fresh standalone entrypoint and returns an
|
|
93
|
+
# Enumerator of LittleGhost::StreamEvent objects. Invocation +options+
|
|
94
|
+
# are forwarded to #stream_ask.
|
|
95
|
+
#
|
|
96
|
+
# Create an instance explicitly when reusing one Runtime across calls.
|
|
97
|
+
def stream_ask(message, **options)
|
|
98
|
+
stream = nil
|
|
99
|
+
Enumerator.new do |events|
|
|
100
|
+
stream ||= new.stream_ask(message, **options)
|
|
101
|
+
stream.each { |event| events << event }
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
92
105
|
# :call-seq:
|
|
93
106
|
# agent_id() -> String
|
|
94
107
|
# agent_id(value) -> String
|
|
@@ -121,23 +134,35 @@ module LittleGhost
|
|
|
121
134
|
end
|
|
122
135
|
|
|
123
136
|
# :call-seq:
|
|
124
|
-
# model() -> String, Proc, nil
|
|
125
|
-
# model(
|
|
137
|
+
# model() -> String, Symbol, Hash, Proc, nil
|
|
138
|
+
# model(role_or_target) -> String, Symbol
|
|
139
|
+
# model(provider:, model:, **settings) -> Hash
|
|
126
140
|
# model { |invocation| ... } -> Proc
|
|
127
141
|
#
|
|
128
|
-
#
|
|
142
|
+
# Selects this agent's model by logical role, canonical
|
|
143
|
+
# <tt>provider:model-id</tt> target, or an inline mapping with +provider+,
|
|
144
|
+
# +model+, and trusted model settings. The provider names a configured
|
|
145
|
+
# connection, not necessarily its adapter.
|
|
129
146
|
#
|
|
130
|
-
# Pass a block to choose
|
|
147
|
+
# Pass a block to choose any supported form from each Invocation at run
|
|
148
|
+
# time. Inline mappings use flat settings, for example:
|
|
149
|
+
#
|
|
150
|
+
# model(provider: "openai", model: "gpt-5.6-luna", reasoning_effort: "high")
|
|
131
151
|
def model(*values, &block)
|
|
132
152
|
return model_value if values.empty? && !block
|
|
153
|
+
if block && !values.empty?
|
|
154
|
+
raise ArgumentError, "model accepts either one selection or a block"
|
|
155
|
+
end
|
|
156
|
+
if values.length > 1
|
|
157
|
+
raise ArgumentError, "model accepts one selection"
|
|
158
|
+
end
|
|
133
159
|
|
|
134
|
-
self.model_value = block || values.fetch(0)
|
|
160
|
+
self.model_value = block || copy_model_selection(values.fetch(0))
|
|
135
161
|
end
|
|
136
162
|
|
|
137
|
-
def
|
|
163
|
+
def model_selection(invocation) # :nodoc:
|
|
138
164
|
value = model_value
|
|
139
|
-
|
|
140
|
-
resolved&.to_s
|
|
165
|
+
value.is_a?(Proc) ? value.call(invocation) : value
|
|
141
166
|
end
|
|
142
167
|
|
|
143
168
|
# :call-seq:
|
|
@@ -350,6 +375,32 @@ module LittleGhost
|
|
|
350
375
|
|
|
351
376
|
private
|
|
352
377
|
|
|
378
|
+
def copy_model_selection(value)
|
|
379
|
+
case value
|
|
380
|
+
when String
|
|
381
|
+
value.dup.freeze
|
|
382
|
+
when Symbol
|
|
383
|
+
value
|
|
384
|
+
when Hash
|
|
385
|
+
value.to_h { |key, child| [key, copy_model_selection_value(child)] }.freeze
|
|
386
|
+
else
|
|
387
|
+
raise ConfigurationError, "model must be a role, provider:model target, or configuration mapping"
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def copy_model_selection_value(value)
|
|
392
|
+
case value
|
|
393
|
+
when Hash
|
|
394
|
+
value.to_h { |key, child| [key, copy_model_selection_value(child)] }.freeze
|
|
395
|
+
when Array
|
|
396
|
+
value.map { |child| copy_model_selection_value(child) }.freeze
|
|
397
|
+
when String
|
|
398
|
+
value.dup.freeze
|
|
399
|
+
else
|
|
400
|
+
value
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
|
|
353
404
|
def validate_result_schema_keywords!(schema, path = "$")
|
|
354
405
|
unsupported = schema.keys - RESULT_SCHEMA_KEYWORDS
|
|
355
406
|
unless unsupported.empty?
|
|
@@ -455,9 +506,9 @@ module LittleGhost
|
|
|
455
506
|
|
|
456
507
|
# Creates either a standalone entrypoint or a run-scoped agent.
|
|
457
508
|
#
|
|
458
|
-
# Calling <tt>new</tt> without +model+ and +run+ creates the
|
|
459
|
-
#
|
|
460
|
-
# apply class-level limits and declarations.
|
|
509
|
+
# Calling <tt>new</tt> without +model+ and +run+ creates the standalone form
|
|
510
|
+
# used by +ask+ and +stream_ask+. Runtime builders supply the remaining
|
|
511
|
+
# dependencies and apply class-level limits and declarations.
|
|
461
512
|
def initialize(
|
|
462
513
|
model: nil,
|
|
463
514
|
runtime: nil,
|
|
@@ -563,7 +614,7 @@ module LittleGhost
|
|
|
563
614
|
result
|
|
564
615
|
end
|
|
565
616
|
|
|
566
|
-
#
|
|
617
|
+
# Runs +message+ to completion through the standalone or run-scoped agent.
|
|
567
618
|
def ask(message, **options)
|
|
568
619
|
call(message, **options)
|
|
569
620
|
end
|
|
@@ -690,7 +741,13 @@ module LittleGhost
|
|
|
690
741
|
if @standalone
|
|
691
742
|
raise ArgumentError, "input is required" if input.nil?
|
|
692
743
|
|
|
693
|
-
return build_run(entrypoint_payload(input, {
|
|
744
|
+
return build_run(entrypoint_payload(input, {
|
|
745
|
+
history:,
|
|
746
|
+
context:,
|
|
747
|
+
settings:,
|
|
748
|
+
template_paths:,
|
|
749
|
+
deadline_at: deadline
|
|
750
|
+
}.compact)).each
|
|
694
751
|
end
|
|
695
752
|
|
|
696
753
|
raise ArgumentError, "input is required" if input.nil?
|
|
@@ -744,8 +801,19 @@ module LittleGhost
|
|
|
744
801
|
end
|
|
745
802
|
end
|
|
746
803
|
|
|
747
|
-
#
|
|
804
|
+
# Streams +message+ through the standalone or run-scoped agent.
|
|
805
|
+
# Standalone +options+ become Invocation fields; run-scoped options are
|
|
806
|
+
# forwarded to #stream.
|
|
748
807
|
def stream_ask(message, **options)
|
|
808
|
+
if @standalone
|
|
809
|
+
options[:deadline_at] = options.delete(:deadline) if options.key?(:deadline)
|
|
810
|
+
stream = nil
|
|
811
|
+
return Enumerator.new do |events|
|
|
812
|
+
stream ||= build_run(entrypoint_payload(message, options)).each
|
|
813
|
+
stream.each { |event| events << event }
|
|
814
|
+
end
|
|
815
|
+
end
|
|
816
|
+
|
|
749
817
|
stream(message, **options)
|
|
750
818
|
end
|
|
751
819
|
|
|
@@ -1146,7 +1214,7 @@ module LittleGhost
|
|
|
1146
1214
|
outcome: :completed,
|
|
1147
1215
|
turn: turn + 1
|
|
1148
1216
|
)
|
|
1149
|
-
metadata = model.
|
|
1217
|
+
metadata = model.details.to_h.merge(model_role: model.role)
|
|
1150
1218
|
finish_instrumentation(
|
|
1151
1219
|
agent_handle,
|
|
1152
1220
|
outcome: :completed,
|
|
@@ -1228,7 +1296,7 @@ module LittleGhost
|
|
|
1228
1296
|
diagnostic: {exception: diagnostic_exception(error)},
|
|
1229
1297
|
**usage_attributes(context.usage)
|
|
1230
1298
|
)
|
|
1231
|
-
metadata = model.
|
|
1299
|
+
metadata = model.details.to_h.merge(model_role: model.role)
|
|
1232
1300
|
emit(events, :invocation_error, error:, usage: context.usage, metadata:)
|
|
1233
1301
|
raise
|
|
1234
1302
|
end
|
|
@@ -1874,7 +1942,7 @@ module LittleGhost
|
|
|
1874
1942
|
outcome: :completed,
|
|
1875
1943
|
turn:
|
|
1876
1944
|
)
|
|
1877
|
-
metadata = model.
|
|
1945
|
+
metadata = model.details.to_h.merge(model_role: model.role)
|
|
1878
1946
|
finish_instrumentation(
|
|
1879
1947
|
agent_handle,
|
|
1880
1948
|
outcome: :completed,
|
|
@@ -1994,9 +2062,9 @@ module LittleGhost
|
|
|
1994
2062
|
|
|
1995
2063
|
def model_attributes
|
|
1996
2064
|
{
|
|
1997
|
-
model_id: model.
|
|
1998
|
-
model_role: model.
|
|
1999
|
-
model_provider: model.
|
|
2065
|
+
model_id: model.model_id,
|
|
2066
|
+
model_role: model.role,
|
|
2067
|
+
model_provider: model.target.provider
|
|
2000
2068
|
}.compact
|
|
2001
2069
|
end
|
|
2002
2070
|
|
|
@@ -8,7 +8,6 @@ module LittleGhost
|
|
|
8
8
|
# instrumentation, and runtime hooks for an application.
|
|
9
9
|
#
|
|
10
10
|
# LittleGhost.configure do |config|
|
|
11
|
-
# config.models CustomerSupportModels
|
|
12
11
|
# config.default_model :customer_support
|
|
13
12
|
# config.service_name "support-api"
|
|
14
13
|
# end
|
|
@@ -30,7 +29,7 @@ module LittleGhost
|
|
|
30
29
|
# not from an unverified request field.
|
|
31
30
|
class Configuration
|
|
32
31
|
FILE_LOAD_MUTEX = Mutex.new # :nodoc:
|
|
33
|
-
CONFIGURATION_KEYS = %i[invocation
|
|
32
|
+
CONFIGURATION_KEYS = %i[invocation service_name].freeze # :nodoc:
|
|
34
33
|
DEFAULT_PROMPT_PATHS = ["app/prompts"].freeze # :nodoc:
|
|
35
34
|
DEFAULT_SKILL_PATHS = ["app/skills"].freeze # :nodoc:
|
|
36
35
|
|
|
@@ -43,24 +42,6 @@ module LittleGhost
|
|
|
43
42
|
# invocation(value) -> value
|
|
44
43
|
|
|
45
44
|
##
|
|
46
|
-
# The model registry class or instance used to resolve logical roles. By
|
|
47
|
-
# default, +DefaultModelRegistry+ selects a built-in provider from supported
|
|
48
|
-
# environment variables.
|
|
49
|
-
#
|
|
50
|
-
# :method: models
|
|
51
|
-
# :call-seq:
|
|
52
|
-
# models() -> value
|
|
53
|
-
# models(value) -> value
|
|
54
|
-
|
|
55
|
-
##
|
|
56
|
-
# The fallback logical model role for agents without an explicit role.
|
|
57
|
-
# Values are normalized to strings.
|
|
58
|
-
#
|
|
59
|
-
# :method: default_model
|
|
60
|
-
# :call-seq:
|
|
61
|
-
# default_model() -> String, nil
|
|
62
|
-
# default_model(value) -> String
|
|
63
|
-
|
|
64
45
|
##
|
|
65
46
|
# The low-cardinality service name attached to instrumentation.
|
|
66
47
|
#
|
|
@@ -72,7 +53,7 @@ module LittleGhost
|
|
|
72
53
|
define_method(name) do |value = :__read__|
|
|
73
54
|
return configuration_values[name] if value == :__read__
|
|
74
55
|
|
|
75
|
-
configuration_values[name] =
|
|
56
|
+
configuration_values[name] = value
|
|
76
57
|
end
|
|
77
58
|
end
|
|
78
59
|
|
|
@@ -83,17 +64,12 @@ module LittleGhost
|
|
|
83
64
|
# invocation=(value) -> value
|
|
84
65
|
|
|
85
66
|
##
|
|
86
|
-
# Replaces the model
|
|
87
|
-
# :method:
|
|
67
|
+
# Replaces the model resolver declaration for subsequently built runtimes.
|
|
68
|
+
# :method: model_resolver=
|
|
88
69
|
# :call-seq:
|
|
89
|
-
#
|
|
70
|
+
# model_resolver=(value) -> value
|
|
90
71
|
|
|
91
72
|
##
|
|
92
|
-
# Replaces the fallback logical model role and normalizes it to a String.
|
|
93
|
-
# :method: default_model=
|
|
94
|
-
# :call-seq:
|
|
95
|
-
# default_model=(value) -> String
|
|
96
|
-
|
|
97
73
|
##
|
|
98
74
|
# Replaces the service name attached to telemetry from new runtimes.
|
|
99
75
|
# :method: service_name=
|
|
@@ -124,6 +100,9 @@ module LittleGhost
|
|
|
124
100
|
@configuration_values[:instrumentation_subscribers]
|
|
125
101
|
).dup
|
|
126
102
|
@configuration_values[:runtime_hooks] = Array(@configuration_values[:runtime_hooks]).dup
|
|
103
|
+
@configuration_values[:provider_adapters] = @configuration_values.fetch(:provider_adapters, {}).dup
|
|
104
|
+
@configuration_values[:catalog_sources] = Array(@configuration_values[:catalog_sources]).dup
|
|
105
|
+
@configuration_values[:provider_credentials] ||= nil
|
|
127
106
|
end
|
|
128
107
|
|
|
129
108
|
# Yields this builder for setup and returns the same instance.
|
|
@@ -139,6 +118,153 @@ module LittleGhost
|
|
|
139
118
|
# Session-store declaration used for subsequently built runtimes.
|
|
140
119
|
def session_store = configuration_values[:session_store]
|
|
141
120
|
|
|
121
|
+
# Trusted provider connections for the default or custom resolver.
|
|
122
|
+
def providers(value = :__read__)
|
|
123
|
+
return configuration_values[:providers] if value == :__read__
|
|
124
|
+
|
|
125
|
+
unless value.is_a?(Hash) || value.is_a?(Providers::Configuration)
|
|
126
|
+
raise ArgumentError, "providers must be a Hash or LittleGhost::Providers::Configuration"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
configuration_values[:providers] = value
|
|
130
|
+
reset_model_resolver
|
|
131
|
+
value
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Replaces trusted provider connections for subsequently built runtimes.
|
|
135
|
+
def providers=(value)
|
|
136
|
+
providers(value)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Logical model profiles for the default resolver. Role names cannot contain
|
|
140
|
+
# a colon because that syntax identifies a canonical model target.
|
|
141
|
+
def models(value = :__read__)
|
|
142
|
+
return configuration_values[:models] if value == :__read__
|
|
143
|
+
raise ArgumentError, "models must be a Hash" unless value.is_a?(Hash)
|
|
144
|
+
|
|
145
|
+
configuration_values[:models] = value
|
|
146
|
+
reset_model_resolver
|
|
147
|
+
value
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Replaces logical model profiles for subsequently built runtimes.
|
|
151
|
+
def models=(value)
|
|
152
|
+
models(value)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Fallback logical role for the default resolver.
|
|
156
|
+
def default_model(value = :__read__)
|
|
157
|
+
return configuration_values[:default_model] if value == :__read__
|
|
158
|
+
|
|
159
|
+
configuration_values[:default_model] = value.to_s
|
|
160
|
+
reset_model_resolver
|
|
161
|
+
value.to_s
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Replaces the fallback logical role and normalizes it to a String.
|
|
165
|
+
def default_model=(value)
|
|
166
|
+
default_model(value)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Provider YAML path. The conventional path is optional; an explicitly set
|
|
170
|
+
# path must exist when a runtime is built.
|
|
171
|
+
def providers_path(value = :__read__) = configuration_path(:providers_path, "providers.yml", value)
|
|
172
|
+
|
|
173
|
+
# Replaces the provider YAML path for subsequently built runtimes.
|
|
174
|
+
def providers_path=(value)
|
|
175
|
+
providers_path(value)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Model YAML path. The conventional path is optional; an explicitly set
|
|
179
|
+
# path must exist when a runtime is built.
|
|
180
|
+
def models_path(value = :__read__) = configuration_path(:models_path, "models.yml", value)
|
|
181
|
+
|
|
182
|
+
# Replaces the model YAML path for subsequently built runtimes.
|
|
183
|
+
def models_path=(value)
|
|
184
|
+
models_path(value)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Installs a complete resolver override for subsequently built runtimes.
|
|
188
|
+
def model_resolver(value = :__read__)
|
|
189
|
+
if value != :__read__
|
|
190
|
+
validate_model_resolver_class!(value)
|
|
191
|
+
|
|
192
|
+
configuration_values[:model_resolver] = value
|
|
193
|
+
@resolved_model_resolver = nil
|
|
194
|
+
return value
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
@model_resolver_mutex ||= Mutex.new
|
|
198
|
+
@model_resolver_mutex.synchronize do
|
|
199
|
+
@resolved_model_resolver ||= begin
|
|
200
|
+
providers = resolved_providers
|
|
201
|
+
credential_resolver = configuration_values[:provider_credentials] || providers&.method(:credentials)
|
|
202
|
+
configured = configuration_values[:model_resolver]
|
|
203
|
+
if configured
|
|
204
|
+
validate_model_resolver_configuration!
|
|
205
|
+
configured.new(
|
|
206
|
+
providers:,
|
|
207
|
+
provider_adapters: configuration_values[:provider_adapters],
|
|
208
|
+
catalog_sources: configuration_values[:catalog_sources],
|
|
209
|
+
credential_resolver:
|
|
210
|
+
)
|
|
211
|
+
else
|
|
212
|
+
profiles, file_default = resolved_models
|
|
213
|
+
ModelResolver.new(
|
|
214
|
+
providers:,
|
|
215
|
+
profiles:,
|
|
216
|
+
default_model: configuration_values.fetch(:default_model, file_default),
|
|
217
|
+
provider_adapters: configuration_values[:provider_adapters],
|
|
218
|
+
catalog_sources: configuration_values[:catalog_sources],
|
|
219
|
+
credential_resolver:
|
|
220
|
+
)
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def model_resolver=(value)
|
|
227
|
+
model_resolver(value)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Registers a provider adapter factory under +name+.
|
|
231
|
+
def provider_adapter(name, callable = nil, &factory)
|
|
232
|
+
implementation = factory || callable
|
|
233
|
+
if implementation.is_a?(Class)
|
|
234
|
+
unless implementation <= Providers::Base
|
|
235
|
+
raise ArgumentError, "provider adapter class must inherit LittleGhost::Providers::Base"
|
|
236
|
+
end
|
|
237
|
+
elsif !implementation.respond_to?(:call)
|
|
238
|
+
raise ArgumentError, "provider adapter must be a Providers::Base class or callable factory"
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
configuration_values[:provider_adapters][name.to_s] = implementation
|
|
242
|
+
@resolved_model_resolver = nil
|
|
243
|
+
implementation
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Adds an explicit catalog source. Sources refresh only when callers invoke
|
|
247
|
+
# ModelResolver#refresh!.
|
|
248
|
+
def catalog_source(source)
|
|
249
|
+
raise ArgumentError, "catalog source must be a Models::Catalog::Source" unless source.is_a?(Models::Catalog::Source)
|
|
250
|
+
|
|
251
|
+
configuration_values[:catalog_sources] << source
|
|
252
|
+
@resolved_model_resolver = nil
|
|
253
|
+
source
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Installs a trusted callable that returns credential options for a named
|
|
257
|
+
# provider connection when each executable model is constructed.
|
|
258
|
+
def provider_credentials(callable = nil, &resolver)
|
|
259
|
+
value = resolver || callable
|
|
260
|
+
return configuration_values[:provider_credentials] unless value
|
|
261
|
+
raise ArgumentError, "provider credential resolver must be callable" unless value.respond_to?(:call)
|
|
262
|
+
|
|
263
|
+
configuration_values[:provider_credentials] = value
|
|
264
|
+
@resolved_model_resolver = nil
|
|
265
|
+
value
|
|
266
|
+
end
|
|
267
|
+
|
|
142
268
|
# Selects the Workspace subclass instantiated for each run.
|
|
143
269
|
def workspace=(value)
|
|
144
270
|
@configuration_values[:workspace] = component_class(value, Workspace, :workspace)
|
|
@@ -286,6 +412,8 @@ module LittleGhost
|
|
|
286
412
|
values[:skill_paths] = Array(values[:skill_paths]).dup
|
|
287
413
|
values[:instrumentation_subscribers] = Array(values[:instrumentation_subscribers]).dup
|
|
288
414
|
values[:runtime_hooks] = Array(values[:runtime_hooks]).dup
|
|
415
|
+
values[:model_resolver] = model_resolver
|
|
416
|
+
values[:default_model] = values[:model_resolver].default_model
|
|
289
417
|
values[:root] = requested_root || values[:root] || self.root
|
|
290
418
|
values
|
|
291
419
|
end
|
|
@@ -313,6 +441,59 @@ module LittleGhost
|
|
|
313
441
|
|
|
314
442
|
attr_reader :configuration_values
|
|
315
443
|
|
|
444
|
+
def validate_model_resolver_class!(value)
|
|
445
|
+
return if value.is_a?(Class) && value <= ModelResolver
|
|
446
|
+
|
|
447
|
+
raise ArgumentError, "model_resolver must be a LittleGhost::ModelResolver subclass"
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
def configuration_path(key, filename, value)
|
|
451
|
+
if value != :__read__
|
|
452
|
+
configuration_values[key] = value
|
|
453
|
+
reset_model_resolver
|
|
454
|
+
return value
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
configuration_values.fetch(key) { root.join("config/little_ghost", filename) }
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
def resolved_providers
|
|
461
|
+
return unless configuration_values.key?(:providers) || (path = resolved_configuration_file(:providers_path, "providers.yml"))
|
|
462
|
+
|
|
463
|
+
configured = configuration_values[:providers]
|
|
464
|
+
return configured if configured.is_a?(Providers::Configuration)
|
|
465
|
+
return Providers::Configuration.new(configured) if configuration_values.key?(:providers)
|
|
466
|
+
|
|
467
|
+
Models::Configuration.providers(path)
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
def resolved_models
|
|
471
|
+
return [configuration_values[:models], nil] if configuration_values.key?(:models)
|
|
472
|
+
|
|
473
|
+
path = resolved_configuration_file(:models_path, "models.yml")
|
|
474
|
+
path ? Models::Configuration.models(path) : [nil, nil]
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
def resolved_configuration_file(key, filename)
|
|
478
|
+
explicit = configuration_values.key?(key)
|
|
479
|
+
path = Pathname(configuration_values.fetch(key) { root.join("config/little_ghost", filename) })
|
|
480
|
+
path = root.join(path) unless path.absolute?
|
|
481
|
+
return path if path.file?
|
|
482
|
+
raise ConfigurationError, "LittleGhost configuration file does not exist: #{path}" if explicit
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
def validate_model_resolver_configuration!
|
|
486
|
+
conflicting = %i[models models_path default_model].select { |key| configuration_values.key?(key) }
|
|
487
|
+
return if conflicting.empty?
|
|
488
|
+
|
|
489
|
+
raise ConfigurationError,
|
|
490
|
+
"custom model_resolver cannot be combined with #{conflicting.join(", ")}"
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
def reset_model_resolver
|
|
494
|
+
@resolved_model_resolver = nil
|
|
495
|
+
end
|
|
496
|
+
|
|
316
497
|
def component_class(value, base_class, name)
|
|
317
498
|
unless value.is_a?(Class) && value <= base_class
|
|
318
499
|
raise ArgumentError, "#{name} must be a #{base_class} class"
|