Class: LittleGhost::CodeMode::Broker
- Inherits:
-
Object
- Object
- LittleGhost::CodeMode::Broker
- Defined in:
- lib/little_ghost/code_mode/broker.rb
Overview
Keeps tool authorization and execution in the trusted parent process.
Instance Attribute Summary collapse
-
#context ⇒ Object
writeonly
:nodoc:.
-
#task_runner ⇒ Object
readonly
:nodoc:.
Instance Method Summary collapse
-
#bind(context:, events:, parent_operation_id:, parent_trace_context: nil) ⇒ Object
:nodoc:.
-
#call(name, arguments = {}, id: SecureRandom.uuid) ⇒ Object
Calls an available Tool by model-visible
name. -
#catalog ⇒ Object
Returns the Tool specifications available to model-authored code.
-
#drain_delivery ⇒ Object
:nodoc:.
-
#initialize(agent: nil, registry: nil, context: RunContext.new, events: [], parent_operation_id: nil, parent_trace_context: nil, except: nil, dispatch: nil, max_calls: nil) ⇒ Broker
constructor
Builds a broker for an Agent's Tool registry.
Constructor Details
#initialize(agent: nil, registry: nil, context: RunContext.new, events: [], parent_operation_id: nil, parent_trace_context: nil, except: nil, dispatch: nil, max_calls: nil) ⇒ Broker
Builds a broker for an Agent's Tool registry. except removes names from
the code-mode catalog. dispatch is a trusted adapter hook used by
custom hosts; ordinary Agent integrations should let the broker dispatch
through agent.
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# File 'lib/little_ghost/code_mode/broker.rb', line 14 def initialize(agent: nil, registry: nil, context: RunContext.new, events: [], parent_operation_id: nil, parent_trace_context: nil, except: nil, dispatch: nil, max_calls: nil) @agent = agent runtime = agent&.runtime @task_runner = runtime ? runtime.task_runner : Support::TaskRunner.new @registry = registry || agent&.tool_registry @context = context @events = events @parent_operation_id = parent_operation_id @parent_trace_context = parent_trace_context @except = Array(except).map(&:to_s).freeze @dispatch = dispatch @max_calls = max_calls && Integer(max_calls) @call_count = 0 @call_mutex = Mutex.new @delivery_mutex = Mutex.new @presentation_content = [] @artifacts = [] @fallback_references = [] @presentation_budget = Artifacts::PresentationBudget.new end |
Instance Attribute Details
#context=(value) ⇒ Object (writeonly)
:nodoc:
48 49 50 |
# File 'lib/little_ghost/code_mode/broker.rb', line 48 def context=(value) @context = value end |
#task_runner ⇒ Object (readonly)
:nodoc:
50 51 52 |
# File 'lib/little_ghost/code_mode/broker.rb', line 50 def task_runner @task_runner end |
Instance Method Details
#bind(context:, events:, parent_operation_id:, parent_trace_context: nil) ⇒ Object
:nodoc:
52 53 54 55 56 57 58 |
# File 'lib/little_ghost/code_mode/broker.rb', line 52 def bind(context:, events:, parent_operation_id:, parent_trace_context: nil) # :nodoc: @context = context @events = events @parent_operation_id = parent_operation_id @parent_trace_context = parent_trace_context self end |
#call(name, arguments = {}, id: SecureRandom.uuid) ⇒ Object
Calls an available Tool by model-visible name. The returned CallResult
has id, decoded value, and model-safe error fields. arguments
remains untrusted until the ordinary Tool path validates and authorizes
it.
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/code_mode/broker.rb', line 64 def call(name, arguments = {}, id: SecureRandom.uuid) @call_mutex.synchronize do @call_count += 1 raise ToolError, "Code-mode tool call budget exceeded" if @max_calls && @call_count > @max_calls end name = String(name).delete_prefix("tools.") available = catalog.any? { |specification| specification.fetch(:name, specification["name"]).to_s == name } raise ToolError, "Tool is not available in code mode: #{name}" unless available raise ToolError, "Tool arguments must be an object" unless arguments.is_a?(Hash) execution_result = nil result = if @dispatch dispatched = @dispatch.call(Call.new(id:, name:, arguments:)) if dispatched.respond_to?(:presentation_content) && dispatched.respond_to?(:artifacts) record_delivery(dispatched.presentation_content, dispatched.artifacts) end dispatched elsif @agent use = Content::ToolUse.new(id:, name:, input: arguments) emit_brokered_tool_call(use) executed = @agent.dispatch_tools( [use], context: @context, events: @events, parent_operation_id: @parent_operation_id, parent_trace_context: @parent_trace_context ).first record_delivery(executed.presentation_content, executed.artifacts) execution_result = executed.execution_result else tool = @registry.fetch(name) execution = tool.execute(arguments, context: @context) record_delivery(execution.presentation_content, execution.artifacts) return CallResult.new(id:, value: execution.value, error: execution.error? ? execution.content : nil) end return result if result.is_a?(CallResult) execution_result ||= result content = execution_result.respond_to?(:content) ? execution_result.content : execution_result value = execution_result.respond_to?(:value) ? execution_result.value : decode(content) status = execution_result.respond_to?(:status) ? execution_result.status : :success CallResult.new(id:, value:, error: (content if status == :error)) rescue ToolError => error CallResult.new(id:, value: nil, error: error.) end |
#catalog ⇒ Object
Returns the Tool specifications available to model-authored code. Excluded Tools, code-mode controls, and subagent controls stay outside this catalog.
39 40 41 42 43 44 45 46 |
# File 'lib/little_ghost/code_mode/broker.rb', line 39 def catalog specifications = @registry&.specifications || [] specifications.reject do |specification| name = specification.fetch(:name, specification["name"]).to_s tool = @registry.fetch(name) @except.include?(name) || %w[exec wait stop].include?(name) || subagent_control?(tool) end end |
#drain_delivery ⇒ Object
:nodoc:
111 112 113 114 115 116 117 118 119 120 |
# File 'lib/little_ghost/code_mode/broker.rb', line 111 def drain_delivery # :nodoc: @delivery_mutex.synchronize do delivery = [@presentation_content.freeze, @artifacts.freeze, @fallback_references.uniq.freeze] @presentation_content = [] @artifacts = [] @fallback_references = [] @presentation_budget = Artifacts::PresentationBudget.new delivery end end |