little_ghost 0.2.1 → 0.4.0
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 +72 -74
- data/docs/guides/assemblies.md +286 -0
- data/docs/guides/core_concepts.md +159 -135
- data/docs/guides/getting_started.md +114 -83
- data/docs/guides/production.md +187 -0
- data/docs/guides/prompt_views.md +132 -0
- data/lib/little_ghost/ag_ui/adapter.rb +3 -3
- data/lib/little_ghost/agent/delegation.rb +35 -8
- data/lib/little_ghost/agent/tool_loop.rb +2 -1
- data/lib/little_ghost/agent.rb +280 -326
- data/lib/little_ghost/agent_builder.rb +20 -4
- data/lib/little_ghost/agent_factory.rb +3 -0
- data/lib/little_ghost/{agent_interruptions.rb → agent_interjections.rb} +12 -12
- data/lib/little_ghost/assembly.rb +345 -0
- data/lib/little_ghost/assembly_builder.rb +497 -0
- data/lib/little_ghost/assembly_execution.rb +535 -0
- data/lib/little_ghost/configuration.rb +263 -39
- data/lib/little_ghost/content.rb +5 -5
- data/lib/little_ghost/data_map.rb +209 -0
- data/lib/little_ghost/errors.rb +10 -2
- data/lib/little_ghost/execution.rb +206 -0
- data/lib/little_ghost/graph.rb +930 -0
- data/lib/little_ghost/message.rb +4 -4
- data/lib/little_ghost/model_resolver.rb +2 -2
- data/lib/little_ghost/prompt_resolver.rb +2 -0
- data/lib/little_ghost/run.rb +190 -64
- data/lib/little_ghost/run_context.rb +33 -20
- data/lib/little_ghost/run_result.rb +22 -11
- data/lib/little_ghost/runtime/hook.rb +9 -4
- data/lib/little_ghost/runtime.rb +134 -36
- data/lib/little_ghost/sandbox.rb +1 -1
- data/lib/little_ghost/session.rb +12 -23
- data/lib/little_ghost/session_store.rb +9 -5
- data/lib/little_ghost/session_stores/agent_core_memory.rb +64 -56
- data/lib/little_ghost/session_stores/filesystem.rb +261 -0
- data/lib/little_ghost/session_stores/memory.rb +7 -0
- data/lib/little_ghost/subagents/manager.rb +42 -42
- data/lib/little_ghost/support/executor.rb +14 -2
- data/lib/little_ghost/support/loader.rb +2 -2
- data/lib/little_ghost/support.rb +15 -3
- data/lib/little_ghost/swarm.rb +439 -0
- data/lib/little_ghost/tool.rb +88 -20
- data/lib/little_ghost/tools/write_todos.rb +6 -1
- data/lib/little_ghost/tracing/open_telemetry.rb +14 -3
- data/lib/little_ghost/unrestricted_sandbox.rb +1 -1
- data/lib/little_ghost/version.rb +1 -1
- data/lib/little_ghost/workflow.rb +224 -90
- data/lib/little_ghost.rb +36 -25
- metadata +17 -5
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "pathname"
|
|
4
|
+
require "monitor"
|
|
4
5
|
|
|
5
6
|
module LittleGhost
|
|
6
7
|
# Configure shared services and lookup rules before agents start.
|
|
@@ -19,10 +20,12 @@ module LittleGhost
|
|
|
19
20
|
# under the application root. Applications may append shared roots or replace
|
|
20
21
|
# the arrays entirely.
|
|
21
22
|
#
|
|
22
|
-
# Configuration is a mutable builder
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
23
|
+
# Configuration is a mutable application builder until its shared Runtime is
|
|
24
|
+
# first used. A successful #runtime call locks the builder so standalone
|
|
25
|
+
# Agents and Assemblies keep one stable setup. Configure the application
|
|
26
|
+
# before its first entrypoint call. Explicit Runtime construction remains an
|
|
27
|
+
# advanced way to take an independent snapshot without selecting the shared
|
|
28
|
+
# default.
|
|
26
29
|
#
|
|
27
30
|
# Session actor resolvers belong at an authentication boundary. Multi-tenant
|
|
28
31
|
# applications should derive actor identity from trusted authenticated state,
|
|
@@ -32,7 +35,6 @@ module LittleGhost
|
|
|
32
35
|
CONFIGURATION_KEYS = %i[invocation service_name].freeze # :nodoc:
|
|
33
36
|
DEFAULT_PROMPT_PATHS = ["app/prompts"].freeze # :nodoc:
|
|
34
37
|
DEFAULT_SKILL_PATHS = ["app/skills"].freeze # :nodoc:
|
|
35
|
-
|
|
36
38
|
##
|
|
37
39
|
# The request envelope class used to parse application payloads.
|
|
38
40
|
#
|
|
@@ -53,7 +55,7 @@ module LittleGhost
|
|
|
53
55
|
define_method(name) do |value = :__read__|
|
|
54
56
|
return configuration_values[name] if value == :__read__
|
|
55
57
|
|
|
56
|
-
configuration_values[name] = value
|
|
58
|
+
change_configuration { configuration_values[name] = value }
|
|
57
59
|
end
|
|
58
60
|
end
|
|
59
61
|
|
|
@@ -85,6 +87,8 @@ module LittleGhost
|
|
|
85
87
|
# Collection settings are copied so callers can safely reuse their input
|
|
86
88
|
# arrays after construction.
|
|
87
89
|
def initialize(values = {})
|
|
90
|
+
@lifecycle_monitor = Monitor.new
|
|
91
|
+
@runtime_condition = @lifecycle_monitor.new_cond
|
|
88
92
|
@configuration_values = {
|
|
89
93
|
prompt_paths: DEFAULT_PROMPT_PATHS.dup,
|
|
90
94
|
skill_paths: DEFAULT_SKILL_PATHS.dup,
|
|
@@ -107,10 +111,73 @@ module LittleGhost
|
|
|
107
111
|
|
|
108
112
|
# Yields this builder for setup and returns the same instance.
|
|
109
113
|
def configure
|
|
110
|
-
yield self if block_given?
|
|
114
|
+
change_configuration { yield self } if block_given?
|
|
111
115
|
self
|
|
112
116
|
end
|
|
113
117
|
|
|
118
|
+
# Returns the shared Runtime for this configuration, building it on first
|
|
119
|
+
# use. Once construction succeeds, the configuration is locked so every
|
|
120
|
+
# standalone entrypoint continues to use one stable application setup. The
|
|
121
|
+
# conventional configuration file may finish loading during construction;
|
|
122
|
+
# other writes are rejected. A failed build leaves the configuration
|
|
123
|
+
# editable for a later attempt.
|
|
124
|
+
def runtime
|
|
125
|
+
build_generation = @lifecycle_monitor.synchronize do
|
|
126
|
+
loop do
|
|
127
|
+
return @default_runtime if @default_runtime
|
|
128
|
+
if @runtime_building
|
|
129
|
+
if @runtime_builder_thread.equal?(Thread.current)
|
|
130
|
+
raise ConfigurationError, "LittleGhost.runtime cannot be called while the shared Runtime is starting"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
waiting_generation = @runtime_generation
|
|
134
|
+
@runtime_condition.wait
|
|
135
|
+
return @default_runtime if @default_runtime
|
|
136
|
+
if @runtime_failure_generation == waiting_generation
|
|
137
|
+
raise @runtime_failure
|
|
138
|
+
end
|
|
139
|
+
else
|
|
140
|
+
sealed_values = freeze_configuration_copy(@configuration_values)
|
|
141
|
+
@runtime_generation = @runtime_generation.to_i + 1
|
|
142
|
+
@runtime_building = true
|
|
143
|
+
@runtime_builder_thread = Thread.current
|
|
144
|
+
@configuration_values = sealed_values
|
|
145
|
+
break @runtime_generation
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
begin
|
|
151
|
+
runtime_root = root
|
|
152
|
+
load_file!(root: runtime_root)
|
|
153
|
+
runtime_settings = settings(root: runtime_root)
|
|
154
|
+
built = Runtime.new(configuration: self, settings: runtime_settings)
|
|
155
|
+
@lifecycle_monitor.synchronize do
|
|
156
|
+
configuration_values.freeze
|
|
157
|
+
@default_runtime = built
|
|
158
|
+
end
|
|
159
|
+
built
|
|
160
|
+
rescue => error
|
|
161
|
+
editable_values = if @configuration_values.frozen?
|
|
162
|
+
copy_configuration_value(@configuration_values)
|
|
163
|
+
else
|
|
164
|
+
@configuration_values
|
|
165
|
+
end
|
|
166
|
+
@lifecycle_monitor.synchronize do
|
|
167
|
+
@configuration_values = editable_values
|
|
168
|
+
@runtime_failure = error
|
|
169
|
+
@runtime_failure_generation = build_generation
|
|
170
|
+
end
|
|
171
|
+
raise
|
|
172
|
+
ensure
|
|
173
|
+
@lifecycle_monitor.synchronize do
|
|
174
|
+
@runtime_building = false
|
|
175
|
+
@runtime_builder_thread = nil
|
|
176
|
+
@runtime_condition.broadcast
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
114
181
|
# Workspace declaration used for subsequently built runtimes.
|
|
115
182
|
def workspace = configuration_values[:workspace]
|
|
116
183
|
# Sandbox declaration used for subsequently built runtimes.
|
|
@@ -122,12 +189,15 @@ module LittleGhost
|
|
|
122
189
|
def providers(value = :__read__)
|
|
123
190
|
return configuration_values[:providers] if value == :__read__
|
|
124
191
|
|
|
192
|
+
ensure_configuration_open!
|
|
125
193
|
unless value.is_a?(Hash) || value.is_a?(Providers::Configuration)
|
|
126
194
|
raise ArgumentError, "providers must be a Hash or LittleGhost::Providers::Configuration"
|
|
127
195
|
end
|
|
128
196
|
|
|
129
|
-
|
|
130
|
-
|
|
197
|
+
change_configuration do
|
|
198
|
+
configuration_values[:providers] = value
|
|
199
|
+
reset_model_resolver
|
|
200
|
+
end
|
|
131
201
|
value
|
|
132
202
|
end
|
|
133
203
|
|
|
@@ -140,10 +210,13 @@ module LittleGhost
|
|
|
140
210
|
# a colon because that syntax identifies a canonical model target.
|
|
141
211
|
def models(value = :__read__)
|
|
142
212
|
return configuration_values[:models] if value == :__read__
|
|
213
|
+
ensure_configuration_open!
|
|
143
214
|
raise ArgumentError, "models must be a Hash" unless value.is_a?(Hash)
|
|
144
215
|
|
|
145
|
-
|
|
146
|
-
|
|
216
|
+
change_configuration do
|
|
217
|
+
configuration_values[:models] = value
|
|
218
|
+
reset_model_resolver
|
|
219
|
+
end
|
|
147
220
|
value
|
|
148
221
|
end
|
|
149
222
|
|
|
@@ -156,8 +229,10 @@ module LittleGhost
|
|
|
156
229
|
def default_model(value = :__read__)
|
|
157
230
|
return configuration_values[:default_model] if value == :__read__
|
|
158
231
|
|
|
159
|
-
|
|
160
|
-
|
|
232
|
+
change_configuration do
|
|
233
|
+
configuration_values[:default_model] = value.to_s
|
|
234
|
+
reset_model_resolver
|
|
235
|
+
end
|
|
161
236
|
value.to_s
|
|
162
237
|
end
|
|
163
238
|
|
|
@@ -187,10 +262,13 @@ module LittleGhost
|
|
|
187
262
|
# Installs a complete resolver override for subsequently built runtimes.
|
|
188
263
|
def model_resolver(value = :__read__)
|
|
189
264
|
if value != :__read__
|
|
265
|
+
ensure_configuration_open!
|
|
190
266
|
validate_model_resolver_class!(value)
|
|
191
267
|
|
|
192
|
-
|
|
193
|
-
|
|
268
|
+
change_configuration do
|
|
269
|
+
configuration_values[:model_resolver] = value
|
|
270
|
+
@resolved_model_resolver = nil
|
|
271
|
+
end
|
|
194
272
|
return value
|
|
195
273
|
end
|
|
196
274
|
|
|
@@ -229,6 +307,7 @@ module LittleGhost
|
|
|
229
307
|
|
|
230
308
|
# Registers a provider adapter factory under +name+.
|
|
231
309
|
def provider_adapter(name, callable = nil, &factory)
|
|
310
|
+
ensure_configuration_open!
|
|
232
311
|
implementation = factory || callable
|
|
233
312
|
if implementation.is_a?(Class)
|
|
234
313
|
unless implementation <= Providers::Base
|
|
@@ -238,18 +317,23 @@ module LittleGhost
|
|
|
238
317
|
raise ArgumentError, "provider adapter must be a Providers::Base class or callable factory"
|
|
239
318
|
end
|
|
240
319
|
|
|
241
|
-
|
|
242
|
-
|
|
320
|
+
change_configuration do
|
|
321
|
+
configuration_values[:provider_adapters][name.to_s] = implementation
|
|
322
|
+
@resolved_model_resolver = nil
|
|
323
|
+
end
|
|
243
324
|
implementation
|
|
244
325
|
end
|
|
245
326
|
|
|
246
327
|
# Adds an explicit catalog source. Sources refresh only when callers invoke
|
|
247
328
|
# ModelResolver#refresh!.
|
|
248
329
|
def catalog_source(source)
|
|
330
|
+
ensure_configuration_open!
|
|
249
331
|
raise ArgumentError, "catalog source must be a Models::Catalog::Source" unless source.is_a?(Models::Catalog::Source)
|
|
250
332
|
|
|
251
|
-
|
|
252
|
-
|
|
333
|
+
change_configuration do
|
|
334
|
+
configuration_values[:catalog_sources] << source
|
|
335
|
+
@resolved_model_resolver = nil
|
|
336
|
+
end
|
|
253
337
|
source
|
|
254
338
|
end
|
|
255
339
|
|
|
@@ -258,21 +342,28 @@ module LittleGhost
|
|
|
258
342
|
def provider_credentials(callable = nil, &resolver)
|
|
259
343
|
value = resolver || callable
|
|
260
344
|
return configuration_values[:provider_credentials] unless value
|
|
345
|
+
ensure_configuration_open!
|
|
261
346
|
raise ArgumentError, "provider credential resolver must be callable" unless value.respond_to?(:call)
|
|
262
347
|
|
|
263
|
-
|
|
264
|
-
|
|
348
|
+
change_configuration do
|
|
349
|
+
configuration_values[:provider_credentials] = value
|
|
350
|
+
@resolved_model_resolver = nil
|
|
351
|
+
end
|
|
265
352
|
value
|
|
266
353
|
end
|
|
267
354
|
|
|
268
355
|
# Selects the Workspace subclass instantiated for each run.
|
|
269
356
|
def workspace=(value)
|
|
270
|
-
|
|
357
|
+
change_configuration do
|
|
358
|
+
@configuration_values[:workspace] = component_class(value, Workspace, :workspace)
|
|
359
|
+
end
|
|
271
360
|
end
|
|
272
361
|
|
|
273
362
|
# Selects the Sandbox subclass instantiated around each run's workspace.
|
|
274
363
|
def sandbox=(value)
|
|
275
|
-
|
|
364
|
+
change_configuration do
|
|
365
|
+
@configuration_values[:sandbox] = component_class(value, Sandbox, :sandbox)
|
|
366
|
+
end
|
|
276
367
|
end
|
|
277
368
|
|
|
278
369
|
# Selects session persistence with a +:provider+ and its constructor options.
|
|
@@ -280,12 +371,17 @@ module LittleGhost
|
|
|
280
371
|
# The provider must be a SessionStore subclass. Runtime construction creates
|
|
281
372
|
# and owns the store instance.
|
|
282
373
|
def session_store=(value)
|
|
374
|
+
ensure_configuration_open!
|
|
283
375
|
unless value.is_a?(Hash)
|
|
284
376
|
raise ArgumentError, "session_store must be a hash with a provider"
|
|
285
377
|
end
|
|
286
378
|
|
|
287
379
|
provider = value[:provider]
|
|
288
|
-
|
|
380
|
+
change_configuration do
|
|
381
|
+
@configuration_values[:session_store] = value.merge(
|
|
382
|
+
provider: component_class(provider, SessionStore, :session_store)
|
|
383
|
+
)
|
|
384
|
+
end
|
|
289
385
|
end
|
|
290
386
|
|
|
291
387
|
# Looks up an arbitrary setting by symbol or string-compatible name.
|
|
@@ -295,7 +391,7 @@ module LittleGhost
|
|
|
295
391
|
|
|
296
392
|
# Adds or replaces an arbitrary setting.
|
|
297
393
|
def []=(name, value)
|
|
298
|
-
configuration_values[name.to_sym] = value
|
|
394
|
+
change_configuration { configuration_values[name.to_sym] = value }
|
|
299
395
|
end
|
|
300
396
|
|
|
301
397
|
# Replaces the application root after resolving it to a stable real path.
|
|
@@ -305,11 +401,12 @@ module LittleGhost
|
|
|
305
401
|
|
|
306
402
|
# Adds an Instrumentation::Subscriber to each new runtime and returns it.
|
|
307
403
|
def instrument(subscriber)
|
|
404
|
+
ensure_configuration_open!
|
|
308
405
|
unless subscriber.is_a?(Instrumentation::Subscriber)
|
|
309
406
|
raise ArgumentError, "instrumentation subscriber must be a LittleGhost::Instrumentation::Subscriber"
|
|
310
407
|
end
|
|
311
408
|
|
|
312
|
-
configuration_values[:instrumentation_subscribers] << subscriber
|
|
409
|
+
change_configuration { configuration_values[:instrumentation_subscribers] << subscriber }
|
|
313
410
|
subscriber
|
|
314
411
|
end
|
|
315
412
|
|
|
@@ -326,7 +423,7 @@ module LittleGhost
|
|
|
326
423
|
def log_events_to(destination = :__read__)
|
|
327
424
|
return Events.console_output if destination == :__read__
|
|
328
425
|
|
|
329
|
-
Events.console_output = destination
|
|
426
|
+
change_configuration { Events.console_output = destination }
|
|
330
427
|
end
|
|
331
428
|
|
|
332
429
|
# Replaces the console destination for structured framework events.
|
|
@@ -336,11 +433,12 @@ module LittleGhost
|
|
|
336
433
|
|
|
337
434
|
# Adds a Runtime::Hook subclass to each new runtime and returns it.
|
|
338
435
|
def runtime_hook(hook_class)
|
|
436
|
+
ensure_configuration_open!
|
|
339
437
|
unless hook_class.is_a?(Class) && hook_class <= Runtime::Hook
|
|
340
438
|
raise ArgumentError, "runtime_hook must be a LittleGhost::Runtime::Hook class"
|
|
341
439
|
end
|
|
342
440
|
|
|
343
|
-
configuration_values[:runtime_hooks] << hook_class
|
|
441
|
+
change_configuration { configuration_values[:runtime_hooks] << hook_class }
|
|
344
442
|
hook_class
|
|
345
443
|
end
|
|
346
444
|
|
|
@@ -356,12 +454,13 @@ module LittleGhost
|
|
|
356
454
|
def session_actor(value = :__read__, &resolver)
|
|
357
455
|
return configuration_values[:session_actor] if value == :__read__ && !resolver
|
|
358
456
|
|
|
457
|
+
ensure_configuration_open!
|
|
359
458
|
raise ArgumentError, "Provide a session actor resolver or a block, not both" if value != :__read__ && resolver
|
|
360
459
|
|
|
361
460
|
configured = resolver || value
|
|
362
461
|
raise ArgumentError, "session_actor must be callable" unless configured.respond_to?(:call)
|
|
363
462
|
|
|
364
|
-
configuration_values[:session_actor] = configured
|
|
463
|
+
change_configuration { configuration_values[:session_actor] = configured }
|
|
365
464
|
end
|
|
366
465
|
|
|
367
466
|
# :call-seq:
|
|
@@ -374,27 +473,29 @@ module LittleGhost
|
|
|
374
473
|
# resolved so runtimes and lookup paths share one stable boundary.
|
|
375
474
|
def root(value = :__read__)
|
|
376
475
|
if value != :__read__
|
|
377
|
-
return configuration_values[:root] = canonical_root(value)
|
|
476
|
+
return change_configuration { configuration_values[:root] = canonical_root(value) }
|
|
378
477
|
end
|
|
379
478
|
|
|
380
479
|
configured = configuration_values[:root]
|
|
381
480
|
configured ? canonical_root(configured) : inferred_root
|
|
382
481
|
end
|
|
383
482
|
|
|
384
|
-
#
|
|
483
|
+
# Prompt lookup paths in precedence order. The Array is mutable until the
|
|
484
|
+
# shared Runtime is built.
|
|
385
485
|
def prompt_paths = configuration_values[:prompt_paths]
|
|
386
486
|
|
|
387
487
|
# Replaces prompt lookup paths with +value+ converted to an Array.
|
|
388
488
|
def prompt_paths=(value)
|
|
389
|
-
configuration_values[:prompt_paths] = Array(value)
|
|
489
|
+
change_configuration { configuration_values[:prompt_paths] = Array(value) }
|
|
390
490
|
end
|
|
391
491
|
|
|
392
|
-
#
|
|
492
|
+
# Skill lookup paths in precedence order. The Array is mutable until the
|
|
493
|
+
# shared Runtime is built.
|
|
393
494
|
def skill_paths = configuration_values[:skill_paths]
|
|
394
495
|
|
|
395
496
|
# Replaces skill lookup paths with +value+ converted to an Array.
|
|
396
497
|
def skill_paths=(value)
|
|
397
|
-
configuration_values[:skill_paths] = Array(value)
|
|
498
|
+
change_configuration { configuration_values[:skill_paths] = Array(value) }
|
|
398
499
|
end
|
|
399
500
|
|
|
400
501
|
# Optional trusted root exposed to skills for resource lookup.
|
|
@@ -402,7 +503,7 @@ module LittleGhost
|
|
|
402
503
|
|
|
403
504
|
# Replaces the trusted skill resource root for new runtimes.
|
|
404
505
|
def skill_resource_root=(value)
|
|
405
|
-
configuration_values[:skill_resource_root] = value
|
|
506
|
+
change_configuration { configuration_values[:skill_resource_root] = value }
|
|
406
507
|
end
|
|
407
508
|
|
|
408
509
|
def settings(root: nil) # :nodoc:
|
|
@@ -429,7 +530,7 @@ module LittleGhost
|
|
|
429
530
|
end
|
|
430
531
|
|
|
431
532
|
path = File.join(requested_root, "config/little_ghost.rb")
|
|
432
|
-
|
|
533
|
+
load_configuration_file(path) if File.file?(path)
|
|
433
534
|
@configuration_file_root = requested_root
|
|
434
535
|
end
|
|
435
536
|
end
|
|
@@ -439,7 +540,15 @@ module LittleGhost
|
|
|
439
540
|
|
|
440
541
|
private
|
|
441
542
|
|
|
442
|
-
|
|
543
|
+
def configuration_values
|
|
544
|
+
@lifecycle_monitor.synchronize do
|
|
545
|
+
if @runtime_building && !@runtime_builder_thread.equal?(Thread.current)
|
|
546
|
+
raise_locked_configuration!
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
@configuration_values
|
|
550
|
+
end
|
|
551
|
+
end
|
|
443
552
|
|
|
444
553
|
def validate_model_resolver_class!(value)
|
|
445
554
|
return if value.is_a?(Class) && value <= ModelResolver
|
|
@@ -449,8 +558,10 @@ module LittleGhost
|
|
|
449
558
|
|
|
450
559
|
def configuration_path(key, filename, value)
|
|
451
560
|
if value != :__read__
|
|
452
|
-
|
|
453
|
-
|
|
561
|
+
change_configuration do
|
|
562
|
+
configuration_values[key] = value
|
|
563
|
+
reset_model_resolver
|
|
564
|
+
end
|
|
454
565
|
return value
|
|
455
566
|
end
|
|
456
567
|
|
|
@@ -494,6 +605,119 @@ module LittleGhost
|
|
|
494
605
|
@resolved_model_resolver = nil
|
|
495
606
|
end
|
|
496
607
|
|
|
608
|
+
def change_configuration
|
|
609
|
+
@lifecycle_monitor.synchronize do
|
|
610
|
+
raise_locked_configuration! unless configuration_changes_allowed?
|
|
611
|
+
|
|
612
|
+
yield
|
|
613
|
+
end
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
def ensure_configuration_open!
|
|
617
|
+
@lifecycle_monitor.synchronize do
|
|
618
|
+
raise_locked_configuration! unless configuration_changes_allowed?
|
|
619
|
+
end
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
def raise_locked_configuration!
|
|
623
|
+
raise ConfigurationError,
|
|
624
|
+
"LittleGhost configuration is locked while its shared Runtime is starting or after it is ready; configure the application before its first Agent or Assembly call"
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
def configuration_changes_allowed?
|
|
628
|
+
return false if @default_runtime
|
|
629
|
+
return true unless @runtime_building
|
|
630
|
+
|
|
631
|
+
@configuration_file_loading && @runtime_builder_thread.equal?(Thread.current)
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
def load_configuration_file(path)
|
|
635
|
+
editable_values = copy_configuration_value(@configuration_values)
|
|
636
|
+
@lifecycle_monitor.synchronize do
|
|
637
|
+
@configuration_values = editable_values
|
|
638
|
+
@configuration_file_loading = true
|
|
639
|
+
end
|
|
640
|
+
LittleGhost.with_configuration(self) { Kernel.load(path) }
|
|
641
|
+
ensure
|
|
642
|
+
sealed_values = freeze_configuration_copy(@configuration_values)
|
|
643
|
+
@lifecycle_monitor.synchronize do
|
|
644
|
+
@configuration_values = sealed_values
|
|
645
|
+
@configuration_file_loading = false
|
|
646
|
+
end
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
def freeze_configuration_copy(value)
|
|
650
|
+
opaque_values = {}
|
|
651
|
+
copied = copy_configuration_value(value, {}, opaque_values)
|
|
652
|
+
freeze_configuration_value(copied, opaque_values)
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
def copy_configuration_value(value, ancestors = {}, opaque_values = {})
|
|
656
|
+
if value.is_a?(Hash) || value.is_a?(Array)
|
|
657
|
+
identity = value.object_id
|
|
658
|
+
raise ArgumentError, "cyclic values cannot be duplicated" if ancestors.key?(identity)
|
|
659
|
+
|
|
660
|
+
ancestors[identity] = true
|
|
661
|
+
end
|
|
662
|
+
case value
|
|
663
|
+
when Hash
|
|
664
|
+
supported_keys = true
|
|
665
|
+
Hash.instance_method(:each_key).bind_call(value) do |key|
|
|
666
|
+
supported_keys &&= configuration_declaration_key?(key)
|
|
667
|
+
end
|
|
668
|
+
unless supported_keys
|
|
669
|
+
opaque_values[value.object_id] = true
|
|
670
|
+
return value
|
|
671
|
+
end
|
|
672
|
+
copy = {}
|
|
673
|
+
Hash.instance_method(:each_pair).bind_call(value) do |key, child|
|
|
674
|
+
copy[copy_configuration_value(key, ancestors, opaque_values)] =
|
|
675
|
+
copy_configuration_value(child, ancestors, opaque_values)
|
|
676
|
+
end
|
|
677
|
+
copy
|
|
678
|
+
when Array
|
|
679
|
+
copy = []
|
|
680
|
+
Array.instance_method(:each).bind_call(value) do |child|
|
|
681
|
+
copy << copy_configuration_value(child, ancestors, opaque_values)
|
|
682
|
+
end
|
|
683
|
+
copy
|
|
684
|
+
when String
|
|
685
|
+
String.new(value)
|
|
686
|
+
else
|
|
687
|
+
value
|
|
688
|
+
end
|
|
689
|
+
ensure
|
|
690
|
+
ancestors.delete(identity) if identity
|
|
691
|
+
end
|
|
692
|
+
|
|
693
|
+
def configuration_declaration_key?(key)
|
|
694
|
+
key.instance_of?(String) ||
|
|
695
|
+
key.instance_of?(Symbol) ||
|
|
696
|
+
key.instance_of?(Integer) ||
|
|
697
|
+
key.instance_of?(Float) ||
|
|
698
|
+
key.instance_of?(Rational) ||
|
|
699
|
+
key.instance_of?(Complex) ||
|
|
700
|
+
key.nil? ||
|
|
701
|
+
key.equal?(true) ||
|
|
702
|
+
key.equal?(false)
|
|
703
|
+
end
|
|
704
|
+
|
|
705
|
+
def freeze_configuration_value(value, opaque_values = {})
|
|
706
|
+
return value if opaque_values.key?(value.object_id)
|
|
707
|
+
|
|
708
|
+
case value
|
|
709
|
+
when Hash
|
|
710
|
+
value.each do |key, child|
|
|
711
|
+
freeze_configuration_value(key, opaque_values)
|
|
712
|
+
freeze_configuration_value(child, opaque_values)
|
|
713
|
+
end
|
|
714
|
+
when Array
|
|
715
|
+
value.each { |child| freeze_configuration_value(child, opaque_values) }
|
|
716
|
+
end
|
|
717
|
+
value.freeze if value.is_a?(Hash) || value.is_a?(Array) || value.is_a?(String)
|
|
718
|
+
value
|
|
719
|
+
end
|
|
720
|
+
|
|
497
721
|
def component_class(value, base_class, name)
|
|
498
722
|
unless value.is_a?(Class) && value <= base_class
|
|
499
723
|
raise ArgumentError, "#{name} must be a #{base_class} class"
|
data/lib/little_ghost/content.rb
CHANGED
|
@@ -37,7 +37,7 @@ module LittleGhost
|
|
|
37
37
|
raise ArgumentError, "tool use name is required" if name.empty?
|
|
38
38
|
raise ArgumentError, "tool use input must be an object" unless input.is_a?(Hash)
|
|
39
39
|
|
|
40
|
-
super(id: id.to_s, name: name.to_s, input:)
|
|
40
|
+
super(id: id.to_s, name: name.to_s, input: DataMap.new(input))
|
|
41
41
|
rescue TypeError
|
|
42
42
|
raise ArgumentError, "tool use id and name must be strings"
|
|
43
43
|
end
|
|
@@ -82,7 +82,7 @@ module LittleGhost
|
|
|
82
82
|
text: text.freeze,
|
|
83
83
|
signature: signature&.freeze,
|
|
84
84
|
redacted_content: redacted_content&.freeze,
|
|
85
|
-
details:
|
|
85
|
+
details: details&.map { |detail| DataMap.new(detail) }
|
|
86
86
|
)
|
|
87
87
|
rescue TypeError
|
|
88
88
|
raise ArgumentError, "reasoning content is invalid"
|
|
@@ -166,7 +166,7 @@ module LittleGhost
|
|
|
166
166
|
|
|
167
167
|
##
|
|
168
168
|
# :attr_reader: input
|
|
169
|
-
# The object-shaped arguments supplied by the model.
|
|
169
|
+
# The object-shaped arguments supplied by the model as a DataMap.
|
|
170
170
|
end
|
|
171
171
|
|
|
172
172
|
# Carries the model-facing result for one ToolUse. A successful result uses
|
|
@@ -292,14 +292,14 @@ module LittleGhost
|
|
|
292
292
|
value["data"] = Base64.strict_encode64(block.redacted_content)
|
|
293
293
|
value["encoding"] = "base64"
|
|
294
294
|
end
|
|
295
|
-
value["details"] = block.details if block.details
|
|
295
|
+
value["details"] = block.details.map(&:to_h) if block.details
|
|
296
296
|
end
|
|
297
297
|
when Image
|
|
298
298
|
binary("image", block.data, media_type: block.media_type)
|
|
299
299
|
when Document
|
|
300
300
|
binary("document", block.data, media_type: block.media_type, name: block.name)
|
|
301
301
|
when ToolUse
|
|
302
|
-
{"type" => "tool_use", "id" => block.id, "name" => block.name, "input" => block.input}
|
|
302
|
+
{"type" => "tool_use", "id" => block.id, "name" => block.name, "input" => block.input.to_h}
|
|
303
303
|
when ToolResult
|
|
304
304
|
{
|
|
305
305
|
"type" => "tool_result", "tool_use_id" => block.tool_use_id,
|