agent-harness 0.10.0 → 0.11.1
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/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +18 -0
- data/lib/agent_harness/configuration.rb +137 -0
- data/lib/agent_harness/conversation.rb +326 -0
- data/lib/agent_harness/mcp_server.rb +32 -0
- data/lib/agent_harness/openai_compatible_transport.rb +391 -0
- data/lib/agent_harness/provider_runtime.rb +40 -4
- data/lib/agent_harness/providers/adapter.rb +62 -3
- data/lib/agent_harness/providers/anthropic.rb +30 -0
- data/lib/agent_harness/providers/base.rb +173 -0
- data/lib/agent_harness/providers/github_copilot.rb +61 -0
- data/lib/agent_harness/sub_agent_config.rb +118 -0
- data/lib/agent_harness/sub_agent_file_loader.rb +55 -0
- data/lib/agent_harness/sub_agent_translator.rb +243 -0
- data/lib/agent_harness/text_transport.rb +320 -13
- data/lib/agent_harness/version.rb +1 -1
- data/lib/agent_harness.rb +22 -0
- metadata +6 -1
|
@@ -25,13 +25,61 @@ module AgentHarness
|
|
|
25
25
|
DEFAULT_MAX_TOKENS = 4096
|
|
26
26
|
DEFAULT_TIMEOUT = 300
|
|
27
27
|
|
|
28
|
+
# @param base_url [String] Anthropic Messages API URL
|
|
28
29
|
# @param api_key [String] Anthropic API key
|
|
29
30
|
# @param logger [Logger, nil] optional logger
|
|
30
|
-
def initialize(api_key:, logger: nil)
|
|
31
|
+
def initialize(api_key:, base_url: ANTHROPIC_API_URL, logger: nil)
|
|
32
|
+
@base_url = base_url
|
|
31
33
|
@api_key = api_key
|
|
32
34
|
@logger = logger
|
|
33
35
|
end
|
|
34
36
|
|
|
37
|
+
# Send a multi-turn chat completion request via the Anthropic Messages API.
|
|
38
|
+
#
|
|
39
|
+
# @param messages [Array<Hash>] conversation messages with :role and :content
|
|
40
|
+
# @param tools [Array<Hash>, nil] tool definitions (Anthropic tool format)
|
|
41
|
+
# @param stream [Boolean] whether to stream the response
|
|
42
|
+
# @param max_tokens [Integer, nil] maximum tokens in the response
|
|
43
|
+
# @param temperature [Float, nil] sampling temperature
|
|
44
|
+
# @yield [Hash] streaming chunks when stream: true
|
|
45
|
+
# @return [Response] the response
|
|
46
|
+
def chat(messages:, tools: nil, stream: false, max_tokens: nil, temperature: nil,
|
|
47
|
+
model: nil, on_chat_chunk: nil, observer: nil, &on_chunk)
|
|
48
|
+
model ||= DEFAULT_MODEL
|
|
49
|
+
timeout = DEFAULT_TIMEOUT
|
|
50
|
+
max_tokens ||= DEFAULT_MAX_TOKENS
|
|
51
|
+
|
|
52
|
+
uri = URI(@base_url)
|
|
53
|
+
|
|
54
|
+
system_messages = messages.select { |m| m[:role] == "system" || m["role"] == "system" }
|
|
55
|
+
non_system = messages.reject { |m| m[:role] == "system" || m["role"] == "system" }
|
|
56
|
+
has_stream_receiver = on_chunk || on_chat_chunk || observer_responds_to?(observer, :on_chat_chunk)
|
|
57
|
+
request_stream = stream && has_stream_receiver
|
|
58
|
+
|
|
59
|
+
body = build_chat_request_body(
|
|
60
|
+
model: model,
|
|
61
|
+
max_tokens: max_tokens,
|
|
62
|
+
messages: non_system,
|
|
63
|
+
system_messages: system_messages,
|
|
64
|
+
tools: tools,
|
|
65
|
+
temperature: temperature,
|
|
66
|
+
stream: request_stream
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
start_time = Time.now
|
|
70
|
+
|
|
71
|
+
if request_stream
|
|
72
|
+
combined = build_chat_chunk_callback(on_chunk, on_chat_chunk, observer)
|
|
73
|
+
result = make_streaming_request(uri, body, timeout: timeout, &combined)
|
|
74
|
+
duration = Time.now - start_time
|
|
75
|
+
build_streaming_response(result, duration: duration, model: model)
|
|
76
|
+
else
|
|
77
|
+
http_response = make_request(uri, body, timeout: timeout)
|
|
78
|
+
duration = Time.now - start_time
|
|
79
|
+
parse_response(http_response, duration: duration, model: model)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
35
83
|
# Send a text-only message via the Anthropic Messages API.
|
|
36
84
|
#
|
|
37
85
|
# @param prompt [String] the user prompt
|
|
@@ -48,7 +96,7 @@ module AgentHarness
|
|
|
48
96
|
timeout ||= DEFAULT_TIMEOUT
|
|
49
97
|
max_tokens ||= DEFAULT_MAX_TOKENS
|
|
50
98
|
|
|
51
|
-
uri = URI(
|
|
99
|
+
uri = URI(@base_url)
|
|
52
100
|
body = {
|
|
53
101
|
model: model,
|
|
54
102
|
max_tokens: max_tokens,
|
|
@@ -64,25 +112,160 @@ module AgentHarness
|
|
|
64
112
|
|
|
65
113
|
private
|
|
66
114
|
|
|
115
|
+
def build_chat_request_body(model:, max_tokens:, messages:, system_messages:, tools:, temperature:, stream:)
|
|
116
|
+
body = {
|
|
117
|
+
model: model,
|
|
118
|
+
max_tokens: max_tokens,
|
|
119
|
+
messages: messages.map { |m| {role: m[:role] || m["role"], content: m[:content] || m["content"]} }
|
|
120
|
+
}
|
|
121
|
+
body[:system] = system_messages.map { |m| m[:content] || m["content"] }.join("\n") if system_messages.any?
|
|
122
|
+
body[:tools] = tools if tools
|
|
123
|
+
body[:temperature] = temperature if temperature
|
|
124
|
+
body[:stream] = true if stream
|
|
125
|
+
body
|
|
126
|
+
end
|
|
127
|
+
|
|
67
128
|
def make_request(uri, body, timeout:)
|
|
129
|
+
http = build_http(uri, timeout: timeout)
|
|
130
|
+
request = build_post_request(uri, body)
|
|
131
|
+
|
|
132
|
+
@logger&.debug("[AgentHarness::TextTransport] POST #{uri} model=#{body[:model]}")
|
|
133
|
+
|
|
134
|
+
http.request(request)
|
|
135
|
+
rescue Net::OpenTimeout, Net::ReadTimeout => e
|
|
136
|
+
raise TimeoutError.new(e.message, original_error: e)
|
|
137
|
+
rescue SocketError, Errno::ECONNREFUSED, Errno::ECONNRESET, IOError => e
|
|
138
|
+
raise ProviderError.new("HTTP connection error: #{e.message}", original_error: e)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def make_streaming_request(uri, body, timeout:, &on_chunk)
|
|
142
|
+
http = build_http(uri, timeout: timeout)
|
|
143
|
+
request = build_post_request(uri, body)
|
|
144
|
+
|
|
145
|
+
@logger&.debug("[AgentHarness::TextTransport] POST #{uri} model=#{body[:model]} stream=true")
|
|
146
|
+
|
|
147
|
+
accumulated = {content: +"", model: nil, usage: nil, tool_calls: []}
|
|
148
|
+
|
|
149
|
+
http.request(request) do |http_response|
|
|
150
|
+
status_code = http_response.code.to_i
|
|
151
|
+
unless status_code == 200
|
|
152
|
+
response_body = http_response.read_body
|
|
153
|
+
handle_error_response_raw(response_body, status_code)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
parse_sse_stream(http_response, accumulated, &on_chunk)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
accumulated
|
|
160
|
+
rescue Net::OpenTimeout, Net::ReadTimeout => e
|
|
161
|
+
raise TimeoutError.new(e.message, original_error: e)
|
|
162
|
+
rescue SocketError, Errno::ECONNREFUSED, Errno::ECONNRESET, IOError => e
|
|
163
|
+
raise ProviderError.new("HTTP connection error: #{e.message}", original_error: e)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def build_http(uri, timeout:)
|
|
68
167
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
69
|
-
http.use_ssl =
|
|
168
|
+
http.use_ssl = (uri.scheme == "https")
|
|
70
169
|
http.open_timeout = [timeout, 30].min
|
|
71
170
|
http.read_timeout = timeout
|
|
171
|
+
http
|
|
172
|
+
end
|
|
72
173
|
|
|
174
|
+
def build_post_request(uri, body)
|
|
73
175
|
request = Net::HTTP::Post.new(uri)
|
|
74
176
|
request["Content-Type"] = "application/json"
|
|
75
177
|
request["x-api-key"] = @api_key
|
|
76
178
|
request["anthropic-version"] = ANTHROPIC_API_VERSION
|
|
77
179
|
request.body = JSON.generate(body)
|
|
180
|
+
request
|
|
181
|
+
end
|
|
78
182
|
|
|
79
|
-
|
|
183
|
+
def parse_sse_stream(http_response, accumulated, &on_chunk)
|
|
184
|
+
buffer = +""
|
|
185
|
+
event_name = nil
|
|
186
|
+
data_lines = []
|
|
80
187
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
188
|
+
http_response.read_body do |chunk|
|
|
189
|
+
buffer << chunk.delete("\r")
|
|
190
|
+
|
|
191
|
+
while (line_end = buffer.index("\n"))
|
|
192
|
+
line = buffer.slice!(0, line_end + 1).chomp("\n")
|
|
193
|
+
|
|
194
|
+
if line.empty?
|
|
195
|
+
process_sse_event(event_name, data_lines.join("\n"), accumulated, &on_chunk)
|
|
196
|
+
event_name = nil
|
|
197
|
+
data_lines = []
|
|
198
|
+
next
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
if line.start_with?("event:")
|
|
202
|
+
event_name = line[6..].strip
|
|
203
|
+
elsif line.start_with?("data:")
|
|
204
|
+
data_lines << line[5..].lstrip
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
process_sse_event(event_name, data_lines.join("\n"), accumulated, &on_chunk) unless data_lines.empty?
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def process_sse_event(event_name, raw_data, accumulated, &on_chunk)
|
|
213
|
+
return if raw_data.nil? || raw_data.empty?
|
|
214
|
+
return if event_name == "ping"
|
|
215
|
+
|
|
216
|
+
payload = JSON.parse(raw_data)
|
|
217
|
+
type = payload["type"] || event_name
|
|
218
|
+
|
|
219
|
+
case type
|
|
220
|
+
when "message_start"
|
|
221
|
+
message = payload["message"] || {}
|
|
222
|
+
accumulated[:model] ||= message["model"]
|
|
223
|
+
merge_usage!(accumulated, message["usage"])
|
|
224
|
+
when "content_block_start"
|
|
225
|
+
process_content_block_start(payload, accumulated, &on_chunk)
|
|
226
|
+
when "content_block_delta"
|
|
227
|
+
process_content_block_delta(payload, accumulated, &on_chunk)
|
|
228
|
+
when "content_block_stop"
|
|
229
|
+
process_content_block_stop(payload, accumulated, &on_chunk)
|
|
230
|
+
when "message_delta"
|
|
231
|
+
merge_usage!(accumulated, payload["usage"])
|
|
232
|
+
when "message_stop"
|
|
233
|
+
emit_usage_and_done(accumulated, &on_chunk)
|
|
234
|
+
when "error"
|
|
235
|
+
message = payload.dig("error", "message") || payload.dig("error", "type") || raw_data
|
|
236
|
+
raise ProviderError, message
|
|
237
|
+
end
|
|
238
|
+
rescue JSON::ParserError => e
|
|
239
|
+
@logger&.warn("[AgentHarness::TextTransport] Skipping malformed SSE event: #{e.message}")
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def emit_text_delta(text, accumulated, &on_chunk)
|
|
243
|
+
return if text.nil? || text.empty?
|
|
244
|
+
|
|
245
|
+
accumulated[:content] << text
|
|
246
|
+
on_chunk.call({type: :text, content: text})
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def merge_usage!(accumulated, usage)
|
|
250
|
+
return unless usage
|
|
251
|
+
|
|
252
|
+
current = accumulated[:usage] || {input: 0, output: 0, total: 0}
|
|
253
|
+
current[:input] = usage["input_tokens"] unless usage["input_tokens"].nil?
|
|
254
|
+
current[:output] = usage["output_tokens"] unless usage["output_tokens"].nil?
|
|
255
|
+
current[:total] = current[:input].to_i + current[:output].to_i
|
|
256
|
+
accumulated[:usage] = current
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def emit_usage_and_done(accumulated, &on_chunk)
|
|
260
|
+
usage = accumulated[:usage]
|
|
261
|
+
if usage
|
|
262
|
+
on_chunk.call({
|
|
263
|
+
type: :usage,
|
|
264
|
+
input_tokens: usage[:input],
|
|
265
|
+
output_tokens: usage[:output]
|
|
266
|
+
})
|
|
267
|
+
end
|
|
268
|
+
on_chunk.call({type: :done})
|
|
86
269
|
end
|
|
87
270
|
|
|
88
271
|
def parse_response(http_response, duration:, model:)
|
|
@@ -95,6 +278,10 @@ module AgentHarness
|
|
|
95
278
|
body = JSON.parse(http_response.body)
|
|
96
279
|
output = extract_text_content(body)
|
|
97
280
|
tokens = extract_tokens(body)
|
|
281
|
+
tool_calls = extract_tool_calls(body)
|
|
282
|
+
|
|
283
|
+
metadata = {transport: :http}
|
|
284
|
+
metadata[:tool_calls] = tool_calls if tool_calls
|
|
98
285
|
|
|
99
286
|
Response.new(
|
|
100
287
|
output: output,
|
|
@@ -103,7 +290,7 @@ module AgentHarness
|
|
|
103
290
|
provider: :claude,
|
|
104
291
|
model: body["model"] || model,
|
|
105
292
|
tokens: tokens,
|
|
106
|
-
metadata:
|
|
293
|
+
metadata: metadata
|
|
107
294
|
)
|
|
108
295
|
rescue JSON::ParserError => e
|
|
109
296
|
raise ProviderError.new(
|
|
@@ -112,6 +299,22 @@ module AgentHarness
|
|
|
112
299
|
)
|
|
113
300
|
end
|
|
114
301
|
|
|
302
|
+
def build_streaming_response(accumulated, duration:, model:)
|
|
303
|
+
tool_calls = accumulated[:tool_calls].compact
|
|
304
|
+
metadata = {transport: :http, stream: true}
|
|
305
|
+
metadata[:tool_calls] = tool_calls unless tool_calls.empty?
|
|
306
|
+
|
|
307
|
+
Response.new(
|
|
308
|
+
output: accumulated[:content],
|
|
309
|
+
exit_code: 0,
|
|
310
|
+
duration: duration,
|
|
311
|
+
provider: :claude,
|
|
312
|
+
model: accumulated[:model] || model,
|
|
313
|
+
tokens: accumulated[:usage],
|
|
314
|
+
metadata: metadata
|
|
315
|
+
)
|
|
316
|
+
end
|
|
317
|
+
|
|
115
318
|
def extract_text_content(body)
|
|
116
319
|
content = body["content"]
|
|
117
320
|
return "" unless content.is_a?(Array)
|
|
@@ -122,6 +325,23 @@ module AgentHarness
|
|
|
122
325
|
.join
|
|
123
326
|
end
|
|
124
327
|
|
|
328
|
+
def extract_tool_calls(body)
|
|
329
|
+
content = body["content"]
|
|
330
|
+
return nil unless content.is_a?(Array)
|
|
331
|
+
|
|
332
|
+
tool_calls = content.filter_map do |block|
|
|
333
|
+
next unless block["type"] == "tool_use"
|
|
334
|
+
|
|
335
|
+
{
|
|
336
|
+
id: block["id"],
|
|
337
|
+
name: block["name"],
|
|
338
|
+
arguments: JSON.generate(block["input"] || {})
|
|
339
|
+
}
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
tool_calls.empty? ? nil : tool_calls
|
|
343
|
+
end
|
|
344
|
+
|
|
125
345
|
def extract_tokens(body)
|
|
126
346
|
usage = body["usage"]
|
|
127
347
|
return nil unless usage
|
|
@@ -133,11 +353,15 @@ module AgentHarness
|
|
|
133
353
|
end
|
|
134
354
|
|
|
135
355
|
def handle_error_response(http_response, status_code)
|
|
356
|
+
handle_error_response_raw(http_response.body, status_code)
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def handle_error_response_raw(body_string, status_code)
|
|
136
360
|
message = begin
|
|
137
|
-
body = JSON.parse(
|
|
138
|
-
body.dig("error", "message") || body.dig("error", "type") ||
|
|
361
|
+
body = JSON.parse(body_string)
|
|
362
|
+
body.dig("error", "message") || body.dig("error", "type") || body_string
|
|
139
363
|
rescue JSON::ParserError
|
|
140
|
-
|
|
364
|
+
body_string
|
|
141
365
|
end
|
|
142
366
|
|
|
143
367
|
case status_code
|
|
@@ -164,5 +388,88 @@ module AgentHarness
|
|
|
164
388
|
raise ProviderError.new("HTTP #{status_code}: #{message}")
|
|
165
389
|
end
|
|
166
390
|
end
|
|
391
|
+
|
|
392
|
+
def build_chat_chunk_callback(on_chunk, on_chat_chunk, observer)
|
|
393
|
+
proc do |chunk|
|
|
394
|
+
on_chunk&.call(chunk)
|
|
395
|
+
on_chat_chunk&.call(chunk)
|
|
396
|
+
observer.on_chat_chunk(chunk) if observer_responds_to?(observer, :on_chat_chunk)
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
def process_content_block_start(payload, accumulated, &on_chunk)
|
|
401
|
+
content_block = payload["content_block"] || {}
|
|
402
|
+
|
|
403
|
+
case content_block["type"]
|
|
404
|
+
when "text"
|
|
405
|
+
emit_text_delta(content_block["text"], accumulated, &on_chunk)
|
|
406
|
+
when "tool_use"
|
|
407
|
+
index = payload["index"] || 0
|
|
408
|
+
accumulated[:tool_calls][index] = {
|
|
409
|
+
id: content_block["id"],
|
|
410
|
+
name: content_block["name"],
|
|
411
|
+
arguments: +"",
|
|
412
|
+
structured_input: content_block["input"],
|
|
413
|
+
saw_delta: false
|
|
414
|
+
}
|
|
415
|
+
on_chunk.call({
|
|
416
|
+
type: :tool_call_start,
|
|
417
|
+
id: content_block["id"],
|
|
418
|
+
name: content_block["name"]
|
|
419
|
+
})
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
def process_content_block_delta(payload, accumulated, &on_chunk)
|
|
424
|
+
delta = payload["delta"] || {}
|
|
425
|
+
|
|
426
|
+
case delta["type"]
|
|
427
|
+
when "text_delta"
|
|
428
|
+
emit_text_delta(delta["text"], accumulated, &on_chunk)
|
|
429
|
+
when "input_json_delta"
|
|
430
|
+
index = payload["index"] || 0
|
|
431
|
+
tool_call = accumulated[:tool_calls][index]
|
|
432
|
+
return unless tool_call
|
|
433
|
+
|
|
434
|
+
partial_json = delta["partial_json"]
|
|
435
|
+
return if partial_json.nil? || partial_json.empty?
|
|
436
|
+
|
|
437
|
+
tool_call[:saw_delta] = true
|
|
438
|
+
tool_call[:arguments] << partial_json
|
|
439
|
+
on_chunk.call({
|
|
440
|
+
type: :tool_call_delta,
|
|
441
|
+
id: tool_call[:id],
|
|
442
|
+
arguments: partial_json
|
|
443
|
+
})
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def process_content_block_stop(payload, accumulated, &on_chunk)
|
|
448
|
+
index = payload["index"] || 0
|
|
449
|
+
tool_call = accumulated[:tool_calls][index]
|
|
450
|
+
return unless tool_call
|
|
451
|
+
|
|
452
|
+
arguments = finalized_tool_call_arguments(tool_call)
|
|
453
|
+
tool_call[:arguments] = arguments
|
|
454
|
+
tool_call.delete(:structured_input)
|
|
455
|
+
tool_call.delete(:saw_delta)
|
|
456
|
+
|
|
457
|
+
on_chunk.call({
|
|
458
|
+
type: :tool_call_complete,
|
|
459
|
+
id: tool_call[:id],
|
|
460
|
+
name: tool_call[:name],
|
|
461
|
+
arguments: arguments
|
|
462
|
+
})
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
def finalized_tool_call_arguments(tool_call)
|
|
466
|
+
return tool_call[:arguments] if tool_call[:saw_delta]
|
|
467
|
+
|
|
468
|
+
JSON.generate(tool_call[:structured_input] || {})
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
def observer_responds_to?(observer, method_name)
|
|
472
|
+
observer&.respond_to?(method_name)
|
|
473
|
+
end
|
|
167
474
|
end
|
|
168
475
|
end
|
data/lib/agent_harness.rb
CHANGED
|
@@ -77,6 +77,23 @@ module AgentHarness
|
|
|
77
77
|
conductor.send_message(prompt, provider: provider, executor: executor, **options)
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
+
# Resolve a canonical sub-agent definition by name or inline payload.
|
|
81
|
+
#
|
|
82
|
+
# @param reference [Symbol, String, Hash, SubAgentConfig]
|
|
83
|
+
# @return [SubAgentConfig]
|
|
84
|
+
def sub_agent(reference)
|
|
85
|
+
configuration.resolve_sub_agent(reference)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Translate a canonical sub-agent definition into a provider-specific format.
|
|
89
|
+
#
|
|
90
|
+
# @param reference [Symbol, String, Hash, SubAgentConfig] sub-agent reference
|
|
91
|
+
# @param provider [Symbol, String] target provider
|
|
92
|
+
# @return [Hash] provider-specific sub-agent definition
|
|
93
|
+
def translate_sub_agent(reference, provider:)
|
|
94
|
+
SubAgentTranslator.for_provider(provider, sub_agent(reference))
|
|
95
|
+
end
|
|
96
|
+
|
|
80
97
|
# Get a provider instance
|
|
81
98
|
# @param name [Symbol] the provider name
|
|
82
99
|
# @return [Providers::Base] the provider instance
|
|
@@ -261,6 +278,9 @@ end
|
|
|
261
278
|
# Core components
|
|
262
279
|
require_relative "agent_harness/errors"
|
|
263
280
|
require_relative "agent_harness/mcp_server"
|
|
281
|
+
require_relative "agent_harness/sub_agent_config"
|
|
282
|
+
require_relative "agent_harness/sub_agent_file_loader"
|
|
283
|
+
require_relative "agent_harness/sub_agent_translator"
|
|
264
284
|
require_relative "agent_harness/provider_runtime"
|
|
265
285
|
require_relative "agent_harness/execution_preparation"
|
|
266
286
|
require_relative "agent_harness/configuration"
|
|
@@ -270,6 +290,8 @@ require_relative "agent_harness/response"
|
|
|
270
290
|
require_relative "agent_harness/token_tracker"
|
|
271
291
|
require_relative "agent_harness/error_taxonomy"
|
|
272
292
|
require_relative "agent_harness/text_transport"
|
|
293
|
+
require_relative "agent_harness/openai_compatible_transport"
|
|
294
|
+
require_relative "agent_harness/conversation"
|
|
273
295
|
require_relative "agent_harness/authentication"
|
|
274
296
|
require_relative "agent_harness/provider_health_check"
|
|
275
297
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: agent-harness
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.11.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Bart Agapinan
|
|
@@ -102,11 +102,13 @@ files:
|
|
|
102
102
|
- lib/agent_harness/authentication.rb
|
|
103
103
|
- lib/agent_harness/command_executor.rb
|
|
104
104
|
- lib/agent_harness/configuration.rb
|
|
105
|
+
- lib/agent_harness/conversation.rb
|
|
105
106
|
- lib/agent_harness/docker_command_executor.rb
|
|
106
107
|
- lib/agent_harness/error_taxonomy.rb
|
|
107
108
|
- lib/agent_harness/errors.rb
|
|
108
109
|
- lib/agent_harness/execution_preparation.rb
|
|
109
110
|
- lib/agent_harness/mcp_server.rb
|
|
111
|
+
- lib/agent_harness/openai_compatible_transport.rb
|
|
110
112
|
- lib/agent_harness/orchestration/circuit_breaker.rb
|
|
111
113
|
- lib/agent_harness/orchestration/conductor.rb
|
|
112
114
|
- lib/agent_harness/orchestration/health_monitor.rb
|
|
@@ -130,6 +132,9 @@ files:
|
|
|
130
132
|
- lib/agent_harness/providers/registry.rb
|
|
131
133
|
- lib/agent_harness/providers/token_usage_parsing.rb
|
|
132
134
|
- lib/agent_harness/response.rb
|
|
135
|
+
- lib/agent_harness/sub_agent_config.rb
|
|
136
|
+
- lib/agent_harness/sub_agent_file_loader.rb
|
|
137
|
+
- lib/agent_harness/sub_agent_translator.rb
|
|
133
138
|
- lib/agent_harness/text_transport.rb
|
|
134
139
|
- lib/agent_harness/token_tracker.rb
|
|
135
140
|
- lib/agent_harness/version.rb
|