Module: OllamaAgent::ExternalAgents::Runner

Defined in:
lib/ollama_agent/external_agents/runner.rb

Overview

Delegates tasks to Anthropic via LLM::AnthropicClient (HTTPS only; no host shell-out).

Constant Summary collapse

DEFAULT_MAX_OUTPUT =
100_000

Class Method Summary collapse

Class Method Details

.build_handoff(task, context_summary, paths) ⇒ Object



64
65
66
67
68
69
70
71
72
73
# File 'lib/ollama_agent/external_agents/runner.rb', line 64

def build_handoff(task, context_summary, paths)
  parts = []
  parts << "Task:\n#{task.to_s.strip}"
  parts << "\nContext:\n#{context_summary.to_s.strip}" unless context_summary.to_s.strip.empty?
  unless paths.empty?
    path_lines = paths.map { |p| "- #{p}" }.join("\n")
    parts << "\nRelevant paths (under project root):\n#{path_lines}"
  end
  parts.join
end

.delegate_env_keys(agent_def) ⇒ Object



82
83
84
85
86
87
88
89
# File 'lib/ollama_agent/external_agents/runner.rb', line 82

def delegate_env_keys(agent_def)
  keys = []
  env_key = agent_def["env_path"].to_s
  keys << env_key unless env_key.empty?
  keys << "ANTHROPIC_API_KEY"
  keys.concat(ENV.keys.grep(/\AOLLAMA_AGENT_/))
  keys.uniq.sort
end

.elapsed_ms(started_at) ⇒ Object



91
92
93
# File 'lib/ollama_agent/external_agents/runner.rb', line 91

def elapsed_ms(started_at)
  ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at) * 1000.0).round
end

.interpolate_argv(tokens, subs) ⇒ Object

rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/ParameterLists, Lint/UnusedMethodArgument



60
61
62
# File 'lib/ollama_agent/external_agents/runner.rb', line 60

def interpolate_argv(tokens, subs)
  ArgvInterp.expand(tokens, subs)
end

.run(agent_def:, root:, executable:, task:, context_summary:, paths:, timeout_sec:, max_output_bytes: nil) ⇒ Object

rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/ParameterLists, Lint/UnusedMethodArgument



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/ollama_agent/external_agents/runner.rb', line 19

def run(agent_def:, root:, executable:, task:, context_summary:, paths:, timeout_sec:, max_output_bytes: nil)
  api_key = ENV.fetch("ANTHROPIC_API_KEY", "").strip
  if api_key.empty?
    raise AnthropicAPIError,
          "ANTHROPIC_API_KEY is not set; external agent delegation requires a configured API key."
  end

  started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  paths = Array(paths).compact.map(&:to_s)
  PathValidator.validate_within_root!(root, paths)

  max_b = max_output_bytes || EnvHelpers.env_positive_int(
    "OLLAMA_AGENT_DELEGATE_MAX_OUTPUT_BYTES",
    DEFAULT_MAX_OUTPUT
  )

  handoff = build_handoff(task, context_summary, paths)
  model = agent_def["model"] || ENV.fetch("OLLAMA_AGENT_ANTHROPIC_MODEL", "claude-opus-4-7")
  client = LLM::AnthropicClient.new(api_key: api_key, model: model.to_s, timeout_seconds: timeout_sec)
  reply = client.chat(messages: [{ role: "user", content: handoff }], max_tokens: 8192)

  DelegateLogger.log_delegate_event(
    {
      event: "delegate_to_agent",
      agent_id: agent_def["id"].to_s,
      cwd: root.to_s,
      argv: ["POST", LLM::AnthropicClient::API_URL, model.to_s],
      timeout_seconds: timeout_sec,
      exit_code: 0,
      duration_ms: elapsed_ms(started_at),
      env_keys: delegate_env_keys(agent_def)
    }
  )

  combined = +"exit:0\n"
  combined << "stdout:\n#{truncate(reply[:content].to_s, max_b)}\n"
  combined << "stderr:\n\n"
  combined
end

.truncate(str, max_bytes) ⇒ Object



75
76
77
78
79
80
# File 'lib/ollama_agent/external_agents/runner.rb', line 75

def truncate(str, max_bytes)
  s = str.to_s
  return s if s.bytesize <= max_bytes

  "#{s.byteslice(0, max_bytes)}…\n[truncated: output exceeded #{max_bytes} bytes]"
end