Class: LittleGhost::Runtime::Hooks::Artifacts

Inherits:
LittleGhost::Runtime::Hook show all
Defined in:
lib/little_ghost/runtime/hooks/artifacts.rb

Overview

Implements the configured artifact lifecycle for input attachments and Tool results. Public construction goes through Configuration#artifacts.

Defined Under Namespace

Classes: InterjectionContext

Constant Summary collapse

RESOURCE_KEY =

:nodoc:

Object.new.freeze
MAX_BATCH_ARTIFACTS =
20
MAX_BATCH_BYTES =
LittleGhost::Artifacts::WorkspaceStore::DEFAULT_MAX_TOTAL_BYTES
RESULT_THRESHOLD_TOKENS =
10_000
RESULT_PREVIEW_TOKENS =
2_000

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from LittleGhost::Runtime::Hook

#error_message, #prepare_run, #session_history

Constructor Details

#initializeArtifacts

Returns a new instance of Artifacts.



40
41
42
43
44
# File 'lib/little_ghost/runtime/hooks/artifacts.rb', line 40

def initialize
  @resolver = self.class.resolver
  @direct_stores = {}
  @direct_stores_mutex = Mutex.new
end

Class Attribute Details

.resolverObject (readonly)

Returns the value of attribute resolver.



33
34
35
# File 'lib/little_ghost/runtime/hooks/artifacts.rb', line 33

def resolver
  @resolver
end

Class Method Details

.configured(resolver: nil) ⇒ Object



35
36
37
# File 'lib/little_ghost/runtime/hooks/artifacts.rb', line 35

def configured(resolver: nil)
  Class.new(self).tap { |hook| hook.instance_variable_set(:@resolver, resolver) }
end

Instance Method Details

#prepare_execution(run) ⇒ Object



46
47
48
49
# File 'lib/little_ghost/runtime/hooks/artifacts.rb', line 46

def prepare_execution(run)
  run.invocation.message = prepare_message(run, run.invocation.message, context: run.context)
  run
end

#prepare_interjection(run, payload) ⇒ Object



51
52
53
54
55
56
57
58
59
60
61
# File 'lib/little_ghost/runtime/hooks/artifacts.rb', line 51

def prepare_interjection(run, payload)
  unless payload.is_a?(Hash)
    return prepare_message(run, payload, context: run.context)
  end

  key = payload.key?(:message) ? :message : "message"
  return payload unless payload.key?(key)

  context = interjection_context(run, payload)
  payload.merge(key => prepare_message(run, payload.fetch(key), context:))
end

#prepare_tool_result(result, tool_use:, run:, workspace:, context:) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/little_ghost/runtime/hooks/artifacts.rb', line 63

def prepare_tool_result(result, tool_use:, run:, workspace:, context:)
  return result unless result.success?

  context&.check!
  explicit = result.artifacts
  oversized = oversized_result?(result)
  automatic = best_effort_oversized_artifact(result, tool_use:) if oversized
  return result if explicit.empty? && !oversized

  resolved, materializable = resolve_artifacts(explicit, run:, context:)
  enforce_batch_limits!(materializable)
  stored = materialize_batch(materializable, run:, workspace:, context:)
  descriptors = merge_descriptors(resolved, stored)
  automatic_descriptor = store_automatic(
    automatic,
    run:,
    workspace:,
    context:
  )
  return result if descriptors.empty? && !automatic_descriptor && !oversized

  all_descriptors = automatic_descriptor ? [*descriptors, automatic_descriptor] : descriptors
  presentations = presentation_content(
    resolved,
    descriptors
  )
  content = result_content(
    result,
    automatic: automatic_descriptor,
    oversized:
  )
  content = append_materialized_references(content, all_descriptors)
  Tool::ExecutionResult.new(
    value: result.value,
    content:,
    status: result.status,
    error: result.error,
    artifacts: all_descriptors,
    presentation_content: [*result.presentation_content, *presentations]
  )
rescue CancelledError, DeadlineExceededError, CleanupError
  raise
rescue => error
  raise ToolError, "Tool artifacts could not be prepared (#{error.class})" unless explicit.empty?

  result
end