lemans 1.3.2 → 1.3.3
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 +7 -0
- data/exe/lemans-remote +68 -37
- data/lib/lemans/environments/daytona/retries.rb +2 -0
- data/lib/lemans/environments/daytona.rb +65 -1
- data/lib/lemans/version.rb +1 -1
- data/lib/miniswen/agent.rb +1 -1
- data/lib/miniswen/local.rb +3 -1
- data/lib/miniswen/ruby_llm.rb +9 -8
- data/lib/miniswen/version.rb +1 -1
- metadata +15 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '08d62b21b79425a5c1e99867270b4cf2dd41ccb07f8ab7f5e5381a416170907f'
|
|
4
|
+
data.tar.gz: 802e7e3f665913b95a5409882f10b067f4118efe1e2de650a07dd93382f035af
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fabad5219961a7514c44f888bd79713db444a1dc5b8b20c6153ae1e55c4252cc4065939bb6671a4e6511a1809f4b89da00ff2a642825348c184969fdf49a7c15
|
|
7
|
+
data.tar.gz: '08a08843cb79dcb1da14e27b1b11d2126fe0d8049dc7cdbcccb573c0c93d81f44613ed6f4e905ec23ab3e9f41e2f6eac44a49e9d520c50ebfca3f82a6d9f4f5f'
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.3.3] - 2026-09-11
|
|
4
|
+
|
|
5
|
+
- Daytona: retry sandbox creation when the SDK gives up on a stalled start (the half-made sandbox is adopted or deleted first).
|
|
6
|
+
- Miniswen: run local commands through `sh -c` (a missing command is exit 127, not a harness crash).
|
|
7
|
+
- Miniswen: retry model calls for ~5 min instead of ~1, truncated responses included.
|
|
8
|
+
- `lemans-remote run --launch-interval N` (default 3s) spaces sandbox launches; `--concurrency` is now a no-op.
|
|
9
|
+
|
|
3
10
|
## [1.3.2] - 2026-09-08
|
|
4
11
|
|
|
5
12
|
- Miniswen: increase provider error max retry window to ~1 min.
|
data/exe/lemans-remote
CHANGED
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
# per task × model (--run-in-band for a single sandbox, --attempts N to
|
|
17
17
|
# repeat each task in N sandboxes); results are archived to the
|
|
18
18
|
# lemans-remote-runs volume. Add --sync to wait for a single sandbox
|
|
19
|
-
# and download into ./runs directly instead
|
|
19
|
+
# and download into ./runs directly instead. Launches go out one at a
|
|
20
|
+
# time, --launch-interval seconds apart (default 3):
|
|
20
21
|
#
|
|
21
22
|
# exe/lemans-remote run --bench ../ai-evals --task hello-world
|
|
22
23
|
# exe/lemans-remote run --bench ../ai-evals --model openrouter/z-ai/glm-5.2 --model openrouter/qwen/qwen3.5-coder --args="-k 2 -c 8"
|
|
@@ -32,7 +33,7 @@
|
|
|
32
33
|
#
|
|
33
34
|
# exe/lemans-remote status [--history] [--running | --complete] [-W [INTERVAL]]
|
|
34
35
|
# exe/lemans-remote logs RUN_ID [--tail N]
|
|
35
|
-
# exe/lemans-remote pull-runs [RUN_ID ...] [--all] [--dry-run]
|
|
36
|
+
# exe/lemans-remote pull-runs [RUN_ID ...] [--match SUBSTR] [--all] [--dry-run]
|
|
36
37
|
# exe/lemans-remote drop-orphans [--min-age 10m]
|
|
37
38
|
# exe/lemans-remote clobber RUN_ID ... | --all
|
|
38
39
|
# exe/lemans-remote snapshots
|
|
@@ -440,10 +441,43 @@ module LemansRemote # :nodoc: all
|
|
|
440
441
|
end
|
|
441
442
|
end
|
|
442
443
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
444
|
+
# The vault is an S3-backed FUSE mount: listing /vault is one request,
|
|
445
|
+
# but every /vault/<run>/manifest.json is another round-trip, so a plain
|
|
446
|
+
# glob over hundreds of runs outlives the exec timeout. List once, then
|
|
447
|
+
# read the manifests concurrently (and only the requested ones, if any).
|
|
448
|
+
MANIFEST_READERS = 32
|
|
449
|
+
MANIFESTS_TIMEOUT_SEC = 120
|
|
450
|
+
|
|
451
|
+
MANIFESTS_CODE = <<~'RUBY'
|
|
452
|
+
require "json"
|
|
453
|
+
only = JSON.parse(ARGV[0])
|
|
454
|
+
match = ARGV[1]
|
|
455
|
+
ids = Dir.children("/vault")
|
|
456
|
+
ids &= only if only
|
|
457
|
+
ids.select! { it.include?(match) } if match && !match.empty?
|
|
458
|
+
queue = Queue.new
|
|
459
|
+
ids.each { queue << it }
|
|
460
|
+
queue.close
|
|
461
|
+
found = Queue.new
|
|
462
|
+
readers = Array.new([ids.size, ENV.fetch("LEMANS_MANIFEST_READERS", "32").to_i].min) do
|
|
463
|
+
Thread.new do
|
|
464
|
+
while (id = queue.pop)
|
|
465
|
+
found << JSON.parse(File.read("/vault/#{id}/manifest.json")) rescue nil
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
readers.each(&:join)
|
|
470
|
+
list = []
|
|
471
|
+
list << found.pop until found.empty?
|
|
472
|
+
puts list.to_json
|
|
473
|
+
RUBY
|
|
474
|
+
|
|
475
|
+
def manifests(sandbox, only: nil, match: nil)
|
|
476
|
+
args = [ only&.to_a.to_json, match.to_s ].map { Shellwords.escape(it) }.join(" ")
|
|
477
|
+
response = sandbox.process.exec(
|
|
478
|
+
command: "LEMANS_MANIFEST_READERS=#{MANIFEST_READERS} ruby -e #{Shellwords.escape(MANIFESTS_CODE)} #{args}",
|
|
479
|
+
timeout: MANIFESTS_TIMEOUT_SEC
|
|
480
|
+
)
|
|
447
481
|
raise "could not read the vault manifests: #{response.result}" unless response.exit_code.zero?
|
|
448
482
|
|
|
449
483
|
body = response.result.to_s.strip
|
|
@@ -513,7 +547,7 @@ module LemansRemote # :nodoc: all
|
|
|
513
547
|
CREATE_ATTEMPTS = 3
|
|
514
548
|
|
|
515
549
|
def initialize(bench:, snapshot:, run_id:, tasks:, models:, extra_args:, extra_env:,
|
|
516
|
-
timeout_sec:, runs_dir:, keep:, sync:, shell:)
|
|
550
|
+
timeout_sec:, runs_dir:, keep:, sync:, shell:, launch_interval: 0)
|
|
517
551
|
@bench = bench
|
|
518
552
|
@snapshot = snapshot
|
|
519
553
|
@run_id = run_id
|
|
@@ -526,6 +560,7 @@ module LemansRemote # :nodoc: all
|
|
|
526
560
|
@keep = keep
|
|
527
561
|
@sync = sync
|
|
528
562
|
@shell = shell
|
|
563
|
+
@launch_interval = launch_interval
|
|
529
564
|
@started_at = Time.now.utc.iso8601
|
|
530
565
|
end
|
|
531
566
|
|
|
@@ -564,7 +599,7 @@ module LemansRemote # :nodoc: all
|
|
|
564
599
|
|
|
565
600
|
# Daytona sometimes never starts a sandbox it accepted; the SDK gives up
|
|
566
601
|
# after a minute and the half-made sandbox stays behind under this run's
|
|
567
|
-
# labels
|
|
602
|
+
# labels. A retry waits the launch interval first.
|
|
568
603
|
def create_sandbox(volume = nil)
|
|
569
604
|
attempt = 0
|
|
570
605
|
begin
|
|
@@ -575,6 +610,7 @@ module LemansRemote # :nodoc: all
|
|
|
575
610
|
raise if attempt >= CREATE_ATTEMPTS
|
|
576
611
|
|
|
577
612
|
say :retry, "sandbox did not start (#{e.message}), attempt #{attempt + 1}/#{CREATE_ATTEMPTS}", :yellow
|
|
613
|
+
sleep @launch_interval if @launch_interval.positive?
|
|
578
614
|
retry
|
|
579
615
|
end
|
|
580
616
|
end
|
|
@@ -872,7 +908,9 @@ module LemansRemote # :nodoc: all
|
|
|
872
908
|
option :runs_dir, default: "runs", desc: "Sync mode: local directory to sync the results into"
|
|
873
909
|
option :env, repeatable: true, desc: "Forward an extra host ENV variable by name"
|
|
874
910
|
option :concurrency, type: :numeric, default: 4, aliases: "-C",
|
|
875
|
-
desc: "
|
|
911
|
+
desc: "Ignored: launches go out one at a time, --launch-interval apart (kept so older invocations still parse)"
|
|
912
|
+
option :launch_interval, type: :numeric, default: 3,
|
|
913
|
+
desc: "Async mode: seconds between consecutive launches (0 to disable)"
|
|
876
914
|
def run_bench
|
|
877
915
|
bench = Lemans::Config.load_file(options[:bench])
|
|
878
916
|
|
|
@@ -899,7 +937,7 @@ module LemansRemote # :nodoc: all
|
|
|
899
937
|
attempts = options[:attempts].to_i
|
|
900
938
|
raise Thor::Error, "lemans-remote: --attempts must be at least 1" if attempts < 1
|
|
901
939
|
raise Thor::Error, "lemans-remote: --attempts needs async mode — drop --sync" if options[:sync] && attempts > 1
|
|
902
|
-
raise Thor::Error, "lemans-remote: --
|
|
940
|
+
raise Thor::Error, "lemans-remote: --launch-interval must not be negative" if options[:launch_interval].to_f.negative?
|
|
903
941
|
|
|
904
942
|
models = options[:model] || []
|
|
905
943
|
jobs =
|
|
@@ -928,7 +966,7 @@ module LemansRemote # :nodoc: all
|
|
|
928
966
|
elsif attempts > 1 then "#{jobs.size / attempts} task × model batch(es) × #{attempts} attempts"
|
|
929
967
|
else "one per task × model"
|
|
930
968
|
end
|
|
931
|
-
say_status :fanout, "#{jobs.size} sandboxes, #{fanout}, #{options[:
|
|
969
|
+
say_status :fanout, "#{jobs.size} sandboxes, #{fanout}, one every #{options[:launch_interval]}s"
|
|
932
970
|
failures, retried = launch_batches(bench, jobs, provisioner)
|
|
933
971
|
finish_retry(planner, retried) if retry_groups
|
|
934
972
|
say_status :detached, "`lemans-remote status` to watch, `lemans-remote pull-runs` to fetch results"
|
|
@@ -995,10 +1033,11 @@ module LemansRemote # :nodoc: all
|
|
|
995
1033
|
option :all, type: :boolean, default: false, desc: "Pull every completed run, even ones already pulled"
|
|
996
1034
|
option :dry_run, type: :boolean, default: false, desc: "Preview what would be downloaded without pulling anything"
|
|
997
1035
|
option :runs_dir, default: "runs", desc: "Local directory to sync the results into"
|
|
1036
|
+
option :match, desc: "Only consider runs whose id contains this substring (e.g. a task name)"
|
|
998
1037
|
def pull_runs(*run_ids)
|
|
999
1038
|
pulled = []
|
|
1000
1039
|
with_vault do |vault, sandbox|
|
|
1001
|
-
manifests = vault.manifests(sandbox)
|
|
1040
|
+
manifests = vault.manifests(sandbox, only: (run_ids if run_ids.any?), match: options[:match])
|
|
1002
1041
|
known = manifests.map { it["run_id"] }
|
|
1003
1042
|
targets =
|
|
1004
1043
|
if run_ids.any?
|
|
@@ -1216,39 +1255,31 @@ module LemansRemote # :nodoc: all
|
|
|
1216
1255
|
runs_dir: options[:runs_dir],
|
|
1217
1256
|
keep: options[:keep],
|
|
1218
1257
|
sync: options[:sync],
|
|
1219
|
-
shell: shell
|
|
1258
|
+
shell: shell,
|
|
1259
|
+
launch_interval: options[:launch_interval].to_f
|
|
1220
1260
|
).call
|
|
1221
1261
|
end
|
|
1222
1262
|
|
|
1263
|
+
# One at a time, `interval` apart: a burst of creations stalls Daytona's
|
|
1264
|
+
# scheduler.
|
|
1223
1265
|
def launch_batches(bench, jobs, provisioner)
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
launched = Queue.new
|
|
1266
|
+
interval = options[:launch_interval].to_f
|
|
1267
|
+
failures = []
|
|
1268
|
+
launched = []
|
|
1228
1269
|
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
begin
|
|
1239
|
-
launch_batch(bench, batch, models, provisioner, attempt:)
|
|
1240
|
-
launched << group if group
|
|
1241
|
-
rescue StandardError => e
|
|
1242
|
-
label = batch.empty? ? "all" : batch.join(",")
|
|
1243
|
-
label += "/a#{attempt}" if attempt
|
|
1244
|
-
failures << "#{label} (#{e.message})"
|
|
1245
|
-
end
|
|
1246
|
-
end
|
|
1270
|
+
jobs.each_with_index do |(batch, models, attempt, group), index|
|
|
1271
|
+
sleep interval if index.positive? && interval.positive?
|
|
1272
|
+
begin
|
|
1273
|
+
launch_batch(bench, batch, models, provisioner, attempt:)
|
|
1274
|
+
launched << group if group
|
|
1275
|
+
rescue StandardError => e
|
|
1276
|
+
label = batch.empty? ? "all" : batch.join(",")
|
|
1277
|
+
label += "/a#{attempt}" if attempt
|
|
1278
|
+
failures << "#{label} (#{e.message})"
|
|
1247
1279
|
end
|
|
1248
1280
|
end
|
|
1249
|
-
threads.each(&:join)
|
|
1250
1281
|
|
|
1251
|
-
[
|
|
1282
|
+
[ failures, launched ]
|
|
1252
1283
|
end
|
|
1253
1284
|
|
|
1254
1285
|
def build_run_id(bench, tasks, models, attempt = nil)
|
|
@@ -7,6 +7,8 @@ module Lemans
|
|
|
7
7
|
class Daytona
|
|
8
8
|
# Another try for calls whose repeat is free. Only reads qualify: a
|
|
9
9
|
# mutation may have landed server-side before its failure surfaced.
|
|
10
|
+
# Sandbox creation is the exception (Daytona#create_sandbox): what it
|
|
11
|
+
# half-made is found by label and dealt with before the repeat.
|
|
10
12
|
module Retries
|
|
11
13
|
# The snapshot service leaks the generated client's own error classes
|
|
12
14
|
# instead of wrapping them, so both dialects have to be caught.
|
|
@@ -9,12 +9,18 @@ module Lemans
|
|
|
9
9
|
# Daytona sandboxes. Daytona builds images server-side into reusable content-named
|
|
10
10
|
# snapshots and enforces the network policy itself.
|
|
11
11
|
class Daytona < Environment
|
|
12
|
+
include Retries
|
|
13
|
+
|
|
12
14
|
DEFAULT_BUILD_TIMEOUT = 600
|
|
13
15
|
|
|
14
16
|
# Workspace tarballs ride uploads/downloads, so transfers get their own
|
|
15
17
|
# budget through the SDK's streaming API instead of the global HTTP cap.
|
|
16
18
|
TRANSFER_TIMEOUT = 900
|
|
17
19
|
|
|
20
|
+
CREATE_ATTEMPTS = 3
|
|
21
|
+
CREATE_RETRY_DELAY = 5
|
|
22
|
+
ADOPT_TIMEOUT = 180
|
|
23
|
+
|
|
18
24
|
SDKTweaks.apply!
|
|
19
25
|
# The SDK's typhoeus/libcurl transfers segfault the VM under concurrent
|
|
20
26
|
# easy_perform calls; Faraday/Net::HTTP is pure Ruby, so transfers need
|
|
@@ -47,7 +53,7 @@ module Lemans
|
|
|
47
53
|
end
|
|
48
54
|
|
|
49
55
|
def start
|
|
50
|
-
@sandbox =
|
|
56
|
+
@sandbox = create_sandbox
|
|
51
57
|
@started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
52
58
|
@shell = Shell.new(sandbox)
|
|
53
59
|
self
|
|
@@ -116,6 +122,64 @@ module Lemans
|
|
|
116
122
|
|
|
117
123
|
def client = self.class.client
|
|
118
124
|
|
|
125
|
+
# Under load Daytona accepts a sandbox, then stalls past the SDK's fixed
|
|
126
|
+
# minute. The one it half-made carries this trial's labels: adopt it if
|
|
127
|
+
# it comes up, delete it otherwise, and only then try again. Without
|
|
128
|
+
# labels there is nothing to find it by, so the failure stands.
|
|
129
|
+
def create_sandbox
|
|
130
|
+
attempt = 0
|
|
131
|
+
begin
|
|
132
|
+
attempt += 1
|
|
133
|
+
client.create(create_params, on_snapshot_create_logs: @logger)
|
|
134
|
+
rescue *Retries::SDK_ERRORS => e
|
|
135
|
+
raise if labels.empty? || !retryable?(e)
|
|
136
|
+
|
|
137
|
+
if (leftover = half_created(e))
|
|
138
|
+
return leftover if await_start(leftover)
|
|
139
|
+
|
|
140
|
+
reap!(leftover, e)
|
|
141
|
+
end
|
|
142
|
+
raise if attempt >= CREATE_ATTEMPTS
|
|
143
|
+
|
|
144
|
+
log "sandbox did not start (#{e.message}), attempt #{attempt + 1}/#{CREATE_ATTEMPTS}"
|
|
145
|
+
sleep CREATE_RETRY_DELAY
|
|
146
|
+
retry
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# One create makes one sandbox; two under the same labels is not ours to sort out.
|
|
151
|
+
def half_created(error)
|
|
152
|
+
found = client.list(::Daytona::ListSandboxesQuery.new(labels: labels)).to_a
|
|
153
|
+
if found.size > 1
|
|
154
|
+
raise InfrastructureError, "#{could_not_start(error)}; #{found.map(&:id).join(", ")} all carry #{labels.inspect}"
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
found.first
|
|
158
|
+
rescue *Retries::SDK_ERRORS => e
|
|
159
|
+
raise InfrastructureError, "#{could_not_start(error)}; a sandbox may still be running under #{labels.inspect}, " \
|
|
160
|
+
"and listing them failed: #{e.message}"
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# The SDK's wait fails at once on a dead sandbox.
|
|
164
|
+
def await_start(sandbox)
|
|
165
|
+
sandbox.wait_for_sandbox_start(ADOPT_TIMEOUT)
|
|
166
|
+
log "adopted sandbox #{sandbox.id}, which came up after the SDK gave up on it"
|
|
167
|
+
true
|
|
168
|
+
rescue *Retries::SDK_ERRORS => e
|
|
169
|
+
log "sandbox #{sandbox.id} did not come up on its own: #{e.message}"
|
|
170
|
+
false
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def reap!(sandbox, error)
|
|
174
|
+
sandbox.delete
|
|
175
|
+
rescue *Retries::SDK_ERRORS => e
|
|
176
|
+
raise InfrastructureError, "#{could_not_start(error)}; #{sandbox.id} may still be running and could not be deleted: #{e.message}"
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def could_not_start(error) = "daytona: could not start sandbox: #{error.message}"
|
|
180
|
+
|
|
181
|
+
def log(message) = @logger&.call("lemans: #{message}\n")
|
|
182
|
+
|
|
119
183
|
# A sandbox inherits the snapshot's resources, so the profile's are
|
|
120
184
|
# stamped into the snapshot at build time.
|
|
121
185
|
def create_params
|
data/lib/lemans/version.rb
CHANGED
data/lib/miniswen/agent.rb
CHANGED
|
@@ -513,7 +513,7 @@ module Miniswen
|
|
|
513
513
|
body = e.response&.body.to_s
|
|
514
514
|
detail = body.empty? ? e.message : "#{e.message}: #{body[0, 1000]}"
|
|
515
515
|
raise InfrastructureError, "miniswen: the model call failed: #{detail}"
|
|
516
|
-
rescue Faraday::SSLError, Faraday::ConnectionFailed, Faraday::TimeoutError => e
|
|
516
|
+
rescue Faraday::SSLError, Faraday::ConnectionFailed, Faraday::TimeoutError, Faraday::ParsingError => e
|
|
517
517
|
raise InfrastructureError, "miniswen: the model call failed: #{e.class}: #{e.message}"
|
|
518
518
|
end
|
|
519
519
|
|
data/lib/miniswen/local.rb
CHANGED
|
@@ -9,8 +9,10 @@ module Miniswen
|
|
|
9
9
|
class Local < Environment
|
|
10
10
|
TIMEOUT_EXIT_CODE = 124
|
|
11
11
|
|
|
12
|
+
# Always through a shell: Ruby execs a metacharacter-free string directly,
|
|
13
|
+
# and a missing binary would then raise ENOENT here instead of exiting 127.
|
|
12
14
|
def exec(command, timeout: nil, env: nil)
|
|
13
|
-
Open3.popen2e(env || {}, command, pgroup: true) do |stdin, io, wait_thr|
|
|
15
|
+
Open3.popen2e(env || {}, "sh", "-c", command, pgroup: true) do |stdin, io, wait_thr|
|
|
14
16
|
stdin.close
|
|
15
17
|
reader = Thread.new { io.read }
|
|
16
18
|
|
data/lib/miniswen/ruby_llm.rb
CHANGED
|
@@ -8,9 +8,10 @@ RubyLLM.configure do |config|
|
|
|
8
8
|
config.logger = Logger.new(IO::NULL) unless ENV["MINISWEN_DEBUG"] == "1"
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
# About
|
|
11
|
+
# About five minutes of retries (1, 2, 4, ... 128s plus jitter): provider
|
|
12
|
+
# outages and rate-limit windows outlast the minute this used to allow.
|
|
12
13
|
RubyLLM.configure do |config|
|
|
13
|
-
config.max_retries =
|
|
14
|
+
config.max_retries = 8
|
|
14
15
|
config.retry_interval = 1
|
|
15
16
|
end
|
|
16
17
|
|
|
@@ -36,13 +37,13 @@ module Miniswen
|
|
|
36
37
|
end
|
|
37
38
|
end
|
|
38
39
|
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
module
|
|
43
|
-
def retry_exceptions = super + [ Faraday::SSLError ]
|
|
40
|
+
# ruby_llm lists SSL errors as fatal, but a handshake reset never sent the
|
|
41
|
+
# request; a 200 with a truncated JSON body is the same dropped connection
|
|
42
|
+
# one step later.
|
|
43
|
+
module RetryTransientFailures
|
|
44
|
+
def retry_exceptions = super + [ Faraday::SSLError, Faraday::ParsingError ]
|
|
44
45
|
end
|
|
45
46
|
end
|
|
46
47
|
|
|
47
48
|
RubyLLM::Providers::OpenRouter.prepend(Miniswen::VerbatimReasoningDetails)
|
|
48
|
-
RubyLLM::Connection.prepend(Miniswen::
|
|
49
|
+
RubyLLM::Connection.prepend(Miniswen::RetryTransientFailures)
|
data/lib/miniswen/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lemans
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.3.
|
|
4
|
+
version: 1.3.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Svyatoslav Kryukov
|
|
@@ -53,6 +53,20 @@ dependencies:
|
|
|
53
53
|
- - "~>"
|
|
54
54
|
- !ruby/object:Gem::Version
|
|
55
55
|
version: '0.203'
|
|
56
|
+
- !ruby/object:Gem::Dependency
|
|
57
|
+
name: json
|
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
|
59
|
+
requirements:
|
|
60
|
+
- - "<"
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: '3'
|
|
63
|
+
type: :runtime
|
|
64
|
+
prerelease: false
|
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
66
|
+
requirements:
|
|
67
|
+
- - "<"
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '3'
|
|
56
70
|
- !ruby/object:Gem::Dependency
|
|
57
71
|
name: faraday
|
|
58
72
|
requirement: !ruby/object:Gem::Requirement
|