llm_gateway 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +255 -1
- data/docs/migration_guide_0.7.0.md +193 -0
- data/lib/llm_gateway/adapters/adapter.rb +1 -1
- data/lib/llm_gateway/adapters/anthropic/input_mapper.rb +24 -0
- data/lib/llm_gateway/adapters/anthropic/stream_mapper.rb +31 -8
- data/lib/llm_gateway/adapters/anthropic_option_mapper.rb +1 -1
- data/lib/llm_gateway/adapters/groq/option_mapper.rb +1 -1
- data/lib/llm_gateway/adapters/input_message_sanitizer.rb +98 -7
- data/lib/llm_gateway/adapters/normalized_stream_accumulator.rb +48 -16
- data/lib/llm_gateway/adapters/openai/chat_completions/option_mapper.rb +1 -1
- data/lib/llm_gateway/adapters/openai/responses/input_mapper.rb +47 -31
- data/lib/llm_gateway/adapters/openai/responses/option_mapper.rb +1 -1
- data/lib/llm_gateway/adapters/openai/responses/stream_mapper.rb +131 -3
- data/lib/llm_gateway/adapters/structs.rb +45 -10
- data/lib/llm_gateway/agents/event.rb +105 -0
- data/lib/llm_gateway/agents/file_session_manager.rb +100 -0
- data/lib/llm_gateway/agents/harness.rb +176 -0
- data/lib/llm_gateway/agents/in_memory_session_manager.rb +222 -0
- data/lib/llm_gateway/agents/tools/bash_tool.rb +132 -0
- data/lib/llm_gateway/agents/tools/edit_tool.rb +215 -0
- data/lib/llm_gateway/agents/tools/read_tool.rb +143 -0
- data/lib/llm_gateway/agents/tools/tool_utils.rb +164 -0
- data/lib/llm_gateway/agents/tools/write_tool.rb +34 -0
- data/lib/llm_gateway/base_client.rb +3 -3
- data/lib/llm_gateway/clients/anthropic.rb +5 -5
- data/lib/llm_gateway/clients/claude_code/oauth_flow.rb +2 -2
- data/lib/llm_gateway/clients/openai.rb +2 -2
- data/lib/llm_gateway/clients/openai_codex/oauth_flow.rb +4 -4
- data/lib/llm_gateway/prompt.rb +105 -68
- data/lib/llm_gateway/utils.rb +116 -13
- data/lib/llm_gateway/version.rb +1 -1
- data/lib/llm_gateway.rb +4 -0
- metadata +12 -2
|
@@ -41,7 +41,7 @@ module LlmGateway
|
|
|
41
41
|
request.set_form(form_data, "multipart/form-data")
|
|
42
42
|
|
|
43
43
|
# Headers (excluding Content-Type because set_form already sets it)
|
|
44
|
-
multipart_headers = build_headers.
|
|
44
|
+
multipart_headers = build_headers.except("content-type", "Content-Type")
|
|
45
45
|
multipart_headers.each { |key, value| request[key] = value }
|
|
46
46
|
|
|
47
47
|
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
|
|
@@ -112,7 +112,7 @@ module LlmGateway
|
|
|
112
112
|
next if data_str == "[DONE]"
|
|
113
113
|
|
|
114
114
|
data = begin
|
|
115
|
-
|
|
115
|
+
JSON.parse(data_str).deep_symbolize_keys
|
|
116
116
|
rescue JSON::ParserError
|
|
117
117
|
{ raw: data_str }
|
|
118
118
|
end
|
|
@@ -141,7 +141,7 @@ module LlmGateway
|
|
|
141
141
|
when 200
|
|
142
142
|
content_type = response["content-type"]
|
|
143
143
|
if content_type&.include?("application/json")
|
|
144
|
-
|
|
144
|
+
JSON.parse(response.body).deep_symbolize_keys
|
|
145
145
|
else
|
|
146
146
|
response.body
|
|
147
147
|
end
|
|
@@ -54,19 +54,19 @@ module LlmGateway
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
tools = apply_tools_cache_control(tools, cache_retention)
|
|
57
|
-
body.merge!(tools: tools) if
|
|
57
|
+
body.merge!(tools: tools) if tools.present?
|
|
58
58
|
|
|
59
59
|
system = prepend_claude_code_identity(system) if claude_code_oauth_api_key?
|
|
60
60
|
system = apply_system_cache_control(system, cache_retention)
|
|
61
61
|
|
|
62
|
-
body.merge!(system: system) if
|
|
62
|
+
body.merge!(system: system) if system.present?
|
|
63
63
|
body.merge!(cache_control: cache_control) unless cache_control.nil?
|
|
64
64
|
body.merge!(options)
|
|
65
65
|
body
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
def apply_system_cache_control(system, cache_retention)
|
|
69
|
-
return system if system.
|
|
69
|
+
return system if system.blank? || !system.is_a?(Array)
|
|
70
70
|
|
|
71
71
|
cache_control = anthropic_cache_control_for(cache_retention)
|
|
72
72
|
return system if cache_control.nil?
|
|
@@ -84,7 +84,7 @@ module LlmGateway
|
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
def apply_tools_cache_control(tools, cache_retention)
|
|
87
|
-
return tools if tools.
|
|
87
|
+
return tools if tools.blank? || !tools.is_a?(Array)
|
|
88
88
|
|
|
89
89
|
cache_control = anthropic_cache_control_for(cache_retention)
|
|
90
90
|
return tools if cache_control.nil?
|
|
@@ -149,7 +149,7 @@ module LlmGateway
|
|
|
149
149
|
text: "You are Claude Code, Anthropic's official CLI for Claude."
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
if system.
|
|
152
|
+
if system.blank?
|
|
153
153
|
[ identity ]
|
|
154
154
|
else
|
|
155
155
|
[ identity ] + system
|
|
@@ -105,7 +105,7 @@ module LlmGateway
|
|
|
105
105
|
code = uri.query && URI.decode_www_form(uri.query).to_h["code"]
|
|
106
106
|
state = uri.query && URI.decode_www_form(uri.query).to_h["state"]
|
|
107
107
|
|
|
108
|
-
raise ArgumentError, "Callback URL is missing code parameter" if code.
|
|
108
|
+
raise ArgumentError, "Callback URL is missing code parameter" if code.blank?
|
|
109
109
|
|
|
110
110
|
{ code: code, state: state }
|
|
111
111
|
rescue URI::InvalidURIError => e
|
|
@@ -116,7 +116,7 @@ module LlmGateway
|
|
|
116
116
|
|
|
117
117
|
def extract_code_and_state(auth_code_or_callback, state)
|
|
118
118
|
value = auth_code_or_callback.to_s.strip
|
|
119
|
-
raise ArgumentError, "Authorization code is required" if value.
|
|
119
|
+
raise ArgumentError, "Authorization code is required" if value.blank?
|
|
120
120
|
|
|
121
121
|
if looks_like_url?(value)
|
|
122
122
|
callback = parse_callback(value)
|
|
@@ -114,7 +114,7 @@ module LlmGateway
|
|
|
114
114
|
|
|
115
115
|
def build_codex_body(messages, system, tools, model:, **options)
|
|
116
116
|
instructions = Array(system).filter_map { |s| s.is_a?(Hash) ? s[:content] : s }.join("\n")
|
|
117
|
-
instructions = "You are a helpful assistant."
|
|
117
|
+
instructions = instructions.presence || "You are a helpful assistant."
|
|
118
118
|
|
|
119
119
|
body = {
|
|
120
120
|
model: model,
|
|
@@ -196,7 +196,7 @@ module LlmGateway
|
|
|
196
196
|
end
|
|
197
197
|
# If we get here, we didn't handle it specifically
|
|
198
198
|
fallback_body = response.body.to_s.strip
|
|
199
|
-
fallback_message = if fallback_body.
|
|
199
|
+
fallback_message = if fallback_body.blank?
|
|
200
200
|
"OpenAI request failed with status #{response.code}"
|
|
201
201
|
else
|
|
202
202
|
"OpenAI request failed with status #{response.code}: #{fallback_body}"
|
|
@@ -96,7 +96,7 @@ module LlmGateway
|
|
|
96
96
|
uri = URI.parse(callback_url)
|
|
97
97
|
params = URI.decode_www_form(uri.query.to_s).to_h
|
|
98
98
|
code = params["code"]
|
|
99
|
-
raise ArgumentError, "Callback URL is missing code parameter" if code.
|
|
99
|
+
raise ArgumentError, "Callback URL is missing code parameter" if code.blank?
|
|
100
100
|
|
|
101
101
|
{ code: code, state: params["state"] }
|
|
102
102
|
rescue URI::InvalidURIError => e
|
|
@@ -120,7 +120,7 @@ module LlmGateway
|
|
|
120
120
|
input = tty.gets&.strip
|
|
121
121
|
tty.close
|
|
122
122
|
|
|
123
|
-
raise "No authorization code provided" if input.
|
|
123
|
+
raise "No authorization code provided" if input.blank?
|
|
124
124
|
|
|
125
125
|
exchange_code(input, flow[:code_verifier], expected_state: flow[:state])
|
|
126
126
|
end
|
|
@@ -183,7 +183,7 @@ module LlmGateway
|
|
|
183
183
|
auth = payload[JWT_CLAIM_PATH]
|
|
184
184
|
account_id = auth&.dig("chatgpt_account_id")
|
|
185
185
|
|
|
186
|
-
account_id.is_a?(String)
|
|
186
|
+
account_id.is_a?(String) ? account_id.presence : nil
|
|
187
187
|
rescue StandardError
|
|
188
188
|
nil
|
|
189
189
|
end
|
|
@@ -214,7 +214,7 @@ module LlmGateway
|
|
|
214
214
|
end
|
|
215
215
|
|
|
216
216
|
def parse_authorization_input(input, expected_state = nil)
|
|
217
|
-
return nil if input.
|
|
217
|
+
return nil if input.blank?
|
|
218
218
|
|
|
219
219
|
value = input.to_s.strip
|
|
220
220
|
|
data/lib/llm_gateway/prompt.rb
CHANGED
|
@@ -2,112 +2,149 @@
|
|
|
2
2
|
|
|
3
3
|
module LlmGateway
|
|
4
4
|
class Prompt
|
|
5
|
-
|
|
5
|
+
class_attribute :provider, :model, :reasoning
|
|
6
|
+
class_attribute :before_execute_callbacks, :after_execute_callbacks, instance_accessor: false, default: []
|
|
7
|
+
attr_accessor :cache_key, :cache_retention
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
def self.before_execute(*methods, &block)
|
|
10
|
+
self.before_execute_callbacks += methods
|
|
11
|
+
self.before_execute_callbacks += [ block ] if block_given?
|
|
12
|
+
end
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
end
|
|
14
|
+
def self.after_execute(*methods, &block)
|
|
15
|
+
self.after_execute_callbacks += methods
|
|
16
|
+
self.after_execute_callbacks += [ block ] if block_given?
|
|
17
|
+
end
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
def initialize(provider: nil, model: nil, reasoning: nil, cache_key: nil, cache_retention: nil)
|
|
20
|
+
@provider = provider || self.class.provider
|
|
21
|
+
@model = model || self.class.model
|
|
22
|
+
@reasoning = reasoning || self.class.reasoning
|
|
23
|
+
@cache_key = cache_key
|
|
24
|
+
@cache_retention = cache_retention
|
|
25
|
+
end
|
|
18
26
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
27
|
+
def run(provider: nil, model: nil, reasoning: nil, **options, &block)
|
|
28
|
+
# Resolve the prompt once so dynamic or expensive prompt builders are not
|
|
29
|
+
# evaluated multiple times during a single run.
|
|
30
|
+
input = prompt
|
|
23
31
|
|
|
24
|
-
|
|
25
|
-
@model = value
|
|
26
|
-
end
|
|
27
|
-
end
|
|
32
|
+
run_callbacks(:before_execute, input)
|
|
28
33
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
response = run_tool_loop(input, provider: resolved_provider(provider), model: model, reasoning: reasoning, **options, &block)
|
|
35
|
+
|
|
36
|
+
run_callbacks(:after_execute, response)
|
|
37
|
+
|
|
38
|
+
response
|
|
32
39
|
end
|
|
33
40
|
|
|
34
|
-
def
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
def stream(input = prompt, provider: nil, model: nil, reasoning: nil, **options, &block)
|
|
42
|
+
stream_provider = resolved_provider(provider)
|
|
43
|
+
stream_options = default_stream_options(model: model, reasoning: reasoning).merge(options)
|
|
44
|
+
|
|
45
|
+
stream_provider.stream(input, **stream_options, &block)
|
|
37
46
|
end
|
|
38
47
|
|
|
39
|
-
def self.
|
|
40
|
-
|
|
48
|
+
def self.tools
|
|
49
|
+
const_defined?(:TOOLS, false) ? self::TOOLS : []
|
|
41
50
|
end
|
|
42
51
|
|
|
43
|
-
def self.
|
|
44
|
-
|
|
52
|
+
def self.find_tool(name)
|
|
53
|
+
tools.find { |tool| tool.tool_name == name }
|
|
45
54
|
end
|
|
46
55
|
|
|
47
|
-
def
|
|
48
|
-
|
|
49
|
-
subclass.instance_variable_set(:@before_execute_callbacks, before_execute_callbacks.dup)
|
|
50
|
-
subclass.instance_variable_set(:@after_execute_callbacks, after_execute_callbacks.dup)
|
|
51
|
-
subclass.provider = provider
|
|
52
|
-
subclass.model = model
|
|
56
|
+
def tools
|
|
57
|
+
self.class.tools.map(&:definition)
|
|
53
58
|
end
|
|
54
59
|
|
|
55
|
-
def
|
|
56
|
-
|
|
57
|
-
@model = model || self.class.model
|
|
60
|
+
def system_prompt
|
|
61
|
+
nil
|
|
58
62
|
end
|
|
59
63
|
|
|
60
|
-
|
|
61
|
-
run_callbacks(:before_execute, prompt)
|
|
64
|
+
private
|
|
62
65
|
|
|
63
|
-
|
|
66
|
+
def find_and_execute_tool(tool_request)
|
|
67
|
+
tool_name = tool_request.name
|
|
68
|
+
tool_input = tool_request.input
|
|
69
|
+
tool_class = self.class.find_tool(tool_name)
|
|
64
70
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
result = begin
|
|
72
|
+
if tool_class
|
|
73
|
+
execute_tool(tool_class, tool_input)
|
|
74
|
+
else
|
|
75
|
+
"Unknown tool: #{tool_name}"
|
|
76
|
+
end
|
|
77
|
+
rescue StandardError => e
|
|
78
|
+
"Error executing tool: #{e.message}"
|
|
69
79
|
end
|
|
80
|
+
ToolResult.new(
|
|
81
|
+
type: "tool_result",
|
|
82
|
+
tool_use_id: tool_request.id,
|
|
83
|
+
content: result,
|
|
84
|
+
)
|
|
85
|
+
end
|
|
70
86
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
87
|
+
def execute_tool(tool_class, tool_input)
|
|
88
|
+
tool_class.new.execute(tool_input)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def run_tool_loop(input, provider: nil, model: nil, reasoning: nil, **options, &block)
|
|
92
|
+
response = stream(input, provider: provider, model: model, reasoning: reasoning, **options, &block)
|
|
76
93
|
|
|
77
|
-
|
|
94
|
+
while tool_requests(response).any?
|
|
95
|
+
input = prompt_with_tool_results(input, response, tool_requests(response))
|
|
96
|
+
response = stream(input, provider: provider, model: model, reasoning: reasoning, **options, &block)
|
|
97
|
+
end
|
|
78
98
|
|
|
79
|
-
|
|
99
|
+
response
|
|
80
100
|
end
|
|
81
101
|
|
|
82
|
-
def
|
|
83
|
-
|
|
84
|
-
stream_model = model || self.model
|
|
85
|
-
options[:model] = stream_model if stream_model
|
|
102
|
+
def tool_requests(response)
|
|
103
|
+
return [] unless response.respond_to?(:content)
|
|
86
104
|
|
|
87
|
-
|
|
105
|
+
response.content.select { |content| content.respond_to?(:type) && content.type == "tool_use" }
|
|
88
106
|
end
|
|
89
107
|
|
|
90
|
-
def
|
|
91
|
-
|
|
108
|
+
def prompt_with_tool_results(input, response, requests)
|
|
109
|
+
messages = input.is_a?(Array) ? input.dup : [ { role: "user", content: input } ]
|
|
110
|
+
messages << response.to_h
|
|
111
|
+
messages << {
|
|
112
|
+
role: "user",
|
|
113
|
+
content: requests.map { |request| find_and_execute_tool(request).to_h }
|
|
114
|
+
}
|
|
115
|
+
messages
|
|
92
116
|
end
|
|
93
117
|
|
|
94
|
-
def
|
|
95
|
-
|
|
118
|
+
def default_stream_options(model: nil, reasoning: nil)
|
|
119
|
+
{
|
|
120
|
+
tools: tools,
|
|
121
|
+
system: system_prompt,
|
|
122
|
+
model: resolved_model(model),
|
|
123
|
+
reasoning: resolved_reasoning(reasoning),
|
|
124
|
+
cache_key: cache_key,
|
|
125
|
+
cache_retention: cache_retention
|
|
126
|
+
}.compact
|
|
96
127
|
end
|
|
97
128
|
|
|
98
|
-
def
|
|
99
|
-
|
|
129
|
+
def resolved_provider(provider)
|
|
130
|
+
provider || self.provider
|
|
100
131
|
end
|
|
101
132
|
|
|
102
|
-
|
|
133
|
+
def resolved_model(model)
|
|
134
|
+
model || self.model
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def resolved_reasoning(reasoning)
|
|
138
|
+
reasoning || self.reasoning
|
|
139
|
+
end
|
|
103
140
|
|
|
104
141
|
def run_callbacks(callback_type, *args)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
142
|
+
self.class.public_send("#{callback_type}_callbacks").each do |callback|
|
|
143
|
+
case callback
|
|
144
|
+
when Proc
|
|
108
145
|
instance_exec(*args, &callback)
|
|
109
|
-
|
|
110
|
-
|
|
146
|
+
when Symbol, String
|
|
147
|
+
public_send(callback, *args)
|
|
111
148
|
end
|
|
112
149
|
end
|
|
113
150
|
end
|
data/lib/llm_gateway/utils.rb
CHANGED
|
@@ -4,16 +4,20 @@ module LlmGateway
|
|
|
4
4
|
module Utils
|
|
5
5
|
module_function
|
|
6
6
|
|
|
7
|
-
def
|
|
8
|
-
|
|
7
|
+
def symbolize_keys(hash)
|
|
8
|
+
hash.to_h.transform_keys { |key| key.respond_to?(:to_sym) ? key.to_sym : key }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def deep_symbolize_keys(value)
|
|
12
|
+
case value
|
|
9
13
|
when Hash
|
|
10
|
-
|
|
11
|
-
result[key
|
|
14
|
+
value.each_with_object({}) do |(key, nested_value), result|
|
|
15
|
+
result[symbolize_key(key)] = deep_symbolize_keys(nested_value)
|
|
12
16
|
end
|
|
13
17
|
when Array
|
|
14
|
-
|
|
18
|
+
value.map { |item| deep_symbolize_keys(item) }
|
|
15
19
|
else
|
|
16
|
-
|
|
20
|
+
value
|
|
17
21
|
end
|
|
18
22
|
end
|
|
19
23
|
|
|
@@ -21,19 +25,118 @@ module LlmGateway
|
|
|
21
25
|
!blank?(value)
|
|
22
26
|
end
|
|
23
27
|
|
|
28
|
+
def presence(value)
|
|
29
|
+
present?(value) ? value : nil
|
|
30
|
+
end
|
|
31
|
+
|
|
24
32
|
def blank?(value)
|
|
25
33
|
case value
|
|
26
|
-
when nil
|
|
34
|
+
when nil, false
|
|
27
35
|
true
|
|
28
|
-
when
|
|
29
|
-
value.strip.empty?
|
|
30
|
-
when Array, Hash
|
|
31
|
-
value.empty?
|
|
32
|
-
when Numeric
|
|
36
|
+
when true, Numeric
|
|
33
37
|
false
|
|
38
|
+
when String
|
|
39
|
+
value.match?(/\A[[:space:]]*\z/)
|
|
34
40
|
else
|
|
35
|
-
value.respond_to?(:empty?) ? value.empty? : false
|
|
41
|
+
value.respond_to?(:empty?) ? !!value.empty? : false
|
|
36
42
|
end
|
|
37
43
|
end
|
|
44
|
+
|
|
45
|
+
def symbolize_key(key)
|
|
46
|
+
key.respond_to?(:to_sym) ? key.to_sym : key
|
|
47
|
+
rescue StandardError
|
|
48
|
+
key
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
unless Class.method_defined?(:class_attribute)
|
|
54
|
+
class Class
|
|
55
|
+
def class_attribute(*names, instance_accessor: true, instance_reader: instance_accessor, instance_writer: instance_accessor, instance_predicate: true, default: nil)
|
|
56
|
+
names.each do |name|
|
|
57
|
+
ivar = :"@#{name}"
|
|
58
|
+
instance_variable_set(ivar, default)
|
|
59
|
+
|
|
60
|
+
unset = Object.new
|
|
61
|
+
|
|
62
|
+
define_singleton_method(name) do |value = unset|
|
|
63
|
+
unless value.equal?(unset)
|
|
64
|
+
instance_variable_set(ivar, value)
|
|
65
|
+
next value
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
if instance_variable_defined?(ivar)
|
|
69
|
+
instance_variable_get(ivar)
|
|
70
|
+
elsif superclass.respond_to?(name)
|
|
71
|
+
superclass.public_send(name)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
define_singleton_method("#{name}=") do |value|
|
|
76
|
+
instance_variable_set(ivar, value)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
define_singleton_method("#{name}?") { !!public_send(name) } if instance_predicate
|
|
80
|
+
|
|
81
|
+
if instance_reader
|
|
82
|
+
define_method(name) do
|
|
83
|
+
if instance_variable_defined?(ivar)
|
|
84
|
+
instance_variable_get(ivar)
|
|
85
|
+
else
|
|
86
|
+
self.class.public_send(name)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
define_method("#{name}=") { |value| instance_variable_set(ivar, value) } if instance_writer
|
|
92
|
+
|
|
93
|
+
define_method("#{name}?") { !!public_send(name) } if instance_predicate
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
class Object
|
|
100
|
+
def blank?
|
|
101
|
+
LlmGateway::Utils.blank?(self)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def present?
|
|
105
|
+
LlmGateway::Utils.present?(self)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def presence
|
|
109
|
+
LlmGateway::Utils.presence(self)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
class Hash
|
|
114
|
+
def symbolize_keys
|
|
115
|
+
transform_keys { |key| LlmGateway::Utils.symbolize_key(key) }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def symbolize_keys!
|
|
119
|
+
replace(symbolize_keys)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def deep_symbolize_keys
|
|
123
|
+
LlmGateway::Utils.deep_symbolize_keys(self)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def deep_symbolize_keys!
|
|
127
|
+
replace(deep_symbolize_keys)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
unless method_defined?(:except)
|
|
131
|
+
def except(*keys)
|
|
132
|
+
reject { |key, _| keys.include?(key) }
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
unless method_defined?(:except!)
|
|
137
|
+
def except!(*keys)
|
|
138
|
+
keys.each { |key| delete(key) }
|
|
139
|
+
self
|
|
140
|
+
end
|
|
38
141
|
end
|
|
39
142
|
end
|
data/lib/llm_gateway/version.rb
CHANGED
data/lib/llm_gateway.rb
CHANGED
|
@@ -7,6 +7,10 @@ require_relative "llm_gateway/base_client"
|
|
|
7
7
|
require_relative "llm_gateway/client"
|
|
8
8
|
require_relative "llm_gateway/prompt"
|
|
9
9
|
require_relative "llm_gateway/tool"
|
|
10
|
+
require_relative "llm_gateway/agents/event"
|
|
11
|
+
require_relative "llm_gateway/agents/in_memory_session_manager"
|
|
12
|
+
require_relative "llm_gateway/agents/file_session_manager"
|
|
13
|
+
require_relative "llm_gateway/agents/harness"
|
|
10
14
|
|
|
11
15
|
# Load clients - order matters for inheritance
|
|
12
16
|
require_relative "llm_gateway/clients/anthropic"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: llm_gateway
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- billybonks
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-06-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: dry-struct
|
|
@@ -43,6 +43,7 @@ files:
|
|
|
43
43
|
- Rakefile
|
|
44
44
|
- docs/migration-guide.md
|
|
45
45
|
- docs/migration_guide_0.6.0.md
|
|
46
|
+
- docs/migration_guide_0.7.0.md
|
|
46
47
|
- lib/llm_gateway.rb
|
|
47
48
|
- lib/llm_gateway/adapters/adapter.rb
|
|
48
49
|
- lib/llm_gateway/adapters/anthropic/acts_like_messages.rb
|
|
@@ -75,6 +76,15 @@ files:
|
|
|
75
76
|
- lib/llm_gateway/adapters/option_mapper.rb
|
|
76
77
|
- lib/llm_gateway/adapters/stream_mapper.rb
|
|
77
78
|
- lib/llm_gateway/adapters/structs.rb
|
|
79
|
+
- lib/llm_gateway/agents/event.rb
|
|
80
|
+
- lib/llm_gateway/agents/file_session_manager.rb
|
|
81
|
+
- lib/llm_gateway/agents/harness.rb
|
|
82
|
+
- lib/llm_gateway/agents/in_memory_session_manager.rb
|
|
83
|
+
- lib/llm_gateway/agents/tools/bash_tool.rb
|
|
84
|
+
- lib/llm_gateway/agents/tools/edit_tool.rb
|
|
85
|
+
- lib/llm_gateway/agents/tools/read_tool.rb
|
|
86
|
+
- lib/llm_gateway/agents/tools/tool_utils.rb
|
|
87
|
+
- lib/llm_gateway/agents/tools/write_tool.rb
|
|
78
88
|
- lib/llm_gateway/base_client.rb
|
|
79
89
|
- lib/llm_gateway/client.rb
|
|
80
90
|
- lib/llm_gateway/clients/anthropic.rb
|