Module: Legion::TTY::DaemonClient

Extended by:
Logging::Helper
Defined in:
lib/legion/tty/daemon_client.rb

Constant Summary collapse

SUCCESS_CODES =
[200, 201, 202].freeze

Class Method Summary collapse

Class Method Details

.available?Boolean

Returns:

  • (Boolean)


26
27
28
29
30
31
32
33
34
35
# File 'lib/legion/tty/daemon_client.rb', line 26

def available?
  uri = URI("#{daemon_url}/api/health")
  response = Net::HTTP.start(uri.hostname, uri.port, open_timeout: @timeout, read_timeout: @timeout) do |http|
    http.get(uri.path)
  end
  response.code.to_i == 200
rescue StandardError => e
  handle_exception(e, level: :debug, operation: 'tty.daemon_client.available?', daemon_url: daemon_url)
  false
end

.cached_manifestObject



50
51
52
53
54
55
56
57
58
59
# File 'lib/legion/tty/daemon_client.rb', line 50

def cached_manifest
  return @manifest if @manifest

  return nil unless @cache_file && File.exist?(@cache_file)

  @manifest = Legion::JSON.load(File.read(@cache_file))
rescue StandardError => e
  handle_exception(e, level: :warn, operation: 'tty.daemon_client.cached_manifest', cache_file: @cache_file)
  nil
end

.chat(message:, model: nil, provider: nil) ⇒ Object



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/legion/tty/daemon_client.rb', line 79

def chat(message:, model: nil, provider: nil)
  return nil unless available?

  uri = URI("#{daemon_url}/api/llm/chat")
  payload = Legion::JSON.dump({ message: message, model: model, provider: provider })
  log.debug { "TTY chat request model=#{model} provider=#{provider} message_length=#{message.to_s.length}" }
  response = post_json(uri, payload)

  return nil unless response && SUCCESS_CODES.include?(response.code.to_i)

  Legion::JSON.load(response.body)
rescue StandardError => e
  handle_exception(e, level: :warn, operation: 'tty.daemon_client.chat',
                      daemon_url: daemon_url, model: model, provider: provider)
  nil
end

.configure(daemon_url: 'http://127.0.0.1:4567', cache_file: nil, timeout: 5) ⇒ Object



18
19
20
21
22
23
24
# File 'lib/legion/tty/daemon_client.rb', line 18

def configure(daemon_url: 'http://127.0.0.1:4567', cache_file: nil, timeout: 5)
  @daemon_url = daemon_url
  @cache_file = cache_file || File.expand_path('~/.legionio/catalog.json')
  @timeout = timeout
  @manifest = nil
  log.info { "TTY daemon client configured daemon_url=#{@daemon_url} timeout=#{@timeout}" }
end

.fetch_manifestObject



37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/legion/tty/daemon_client.rb', line 37

def fetch_manifest
  uri = URI("#{daemon_url}/api/catalog")
  response = Net::HTTP.start(uri.hostname, uri.port, open_timeout: @timeout, read_timeout: @timeout) do |http|
    http.get(uri.path)
  end
  return nil unless response.code.to_i == 200

  store_manifest(Legion::JSON.load(response.body)[:data])
rescue StandardError => e
  handle_exception(e, level: :warn, operation: 'tty.daemon_client.fetch_manifest', daemon_url: daemon_url)
  nil
end

.inference(messages:, tools: [], model: nil, provider: nil, timeout: 120) ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/legion/tty/daemon_client.rb', line 96

def inference(messages:, tools: [], model: nil, provider: nil, timeout: 120)
  log.debug { "TTY inference model=#{model} provider=#{provider} msgs=#{Array(messages).size}" }
  response = post_inference(messages: messages, tools: tools, model: model,
                            provider: provider, timeout: timeout)
  return inference_error_result(response) unless SUCCESS_CODES.include?(response.code.to_i)

  parse_inference_response(response)
rescue StandardError => e
  handle_exception(e, level: :warn, operation: 'tty.daemon_client.inference',
                      daemon_url: daemon_url, model: model, provider: provider, timeout: timeout)
  { status: :unavailable, error: { message: e.message } }
end

.manifestObject



61
62
63
# File 'lib/legion/tty/daemon_client.rb', line 61

def manifest
  @manifest || cached_manifest
end

.match_intent(intent_text) ⇒ Object



65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/legion/tty/daemon_client.rb', line 65

def match_intent(intent_text)
  return nil unless manifest

  normalized = intent_text.downcase.strip
  manifest.each do |ext|
    next unless ext[:known_intents]

    ext[:known_intents].each do |ki|
      return ki if ki[:intent]&.downcase&.strip == normalized && ki[:confidence] >= 0.8
    end
  end
  nil
end

.reset!Object



125
126
127
128
129
130
# File 'lib/legion/tty/daemon_client.rb', line 125

def reset!
  @daemon_url = nil
  @cache_file = nil
  @timeout = nil
  @manifest = nil
end

.run_tool(name:, args: {}) ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/legion/tty/daemon_client.rb', line 109

def run_tool(name:, args: {})
  uri = URI("#{daemon_url}/api/tools/run")
  payload = Legion::JSON.dump({ name: name, args: args })
  response = post_json(uri, payload)
  if SUCCESS_CODES.include?(response.code.to_i)
    body = Legion::JSON.load(response.body)
    { status: :ok, data: body[:data] || body }
  else
    { status: :error, error: "HTTP #{response.code}" }
  end
rescue StandardError => e
  handle_exception(e, level: :warn, operation: 'tty.daemon_client.run_tool',
                      daemon_url: daemon_url, name: name)
  { status: :unavailable }
end