class LittleGhost::CodeMode::Broker
Keeps tool authorization and execution in the trusted parent process.
Public Class Methods
# 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
Public Instance Methods
# 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.message) end
Source
# 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