lemans 1.3.5 → 1.4.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 +11 -0
- data/lib/lemans/agents/miniswen_installed.rb +7 -0
- data/lib/lemans/cli/templates/bench/bench.yml +7 -0
- data/lib/lemans/config/verifier.rb +10 -2
- data/lib/lemans/trial/setup.rb +1 -0
- data/lib/lemans/trial.rb +2 -2
- data/lib/lemans/version.rb +1 -1
- data/lib/miniswen/agent.rb +13 -2
- data/lib/miniswen/cli.rb +12 -1
- data/lib/miniswen/jail/proxy.rb +62 -0
- data/lib/miniswen/jail.rb +35 -8
- data/lib/miniswen/local.rb +5 -6
- data/lib/miniswen/testing.rb +7 -1
- data/lib/miniswen/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7ec0760a162ddb4b0fe13d4ddeb4027f934c1ffae9c7acbfda330da9ff68d91f
|
|
4
|
+
data.tar.gz: 85cd135708fade8a06610c4cd26e4f4556e8d90ec566d3a5e12e02d13e46d351
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: eb13268e0b98f9e8ca4f82f3e50b0a454366f00f0030fcaed4d03bee6d67eb214370e1317f9ffbba7701beefdb4d4e8e6c5f399cdaad90425208bdbc2b950fdc
|
|
7
|
+
data.tar.gz: f0a8acc7bc79be0fd546fdc0c59db8e0cfa354aba6806bb09f416bf2d61b898c895a0b94a71b27c122a6a7f65854f51d07fd32f2fd0ce66d91e9e4568163bdeb
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.4.0] - 2026-09-24
|
|
4
|
+
|
|
5
|
+
- miniswen: add built-in proxy and the `--allow-hosts` switch.
|
|
6
|
+
- miniswen: `--workdir`.
|
|
7
|
+
- Fix(miniswen): a background process holding a command's output no longer hangs the agent.
|
|
8
|
+
- Fix(miniswen): a turn that ends past the time limit runs no command.
|
|
9
|
+
- Fix(miniswen): allow free OpenRouter models.
|
|
10
|
+
- Add `verifier.environment.network` to allow grading access particular hosts, e.g. to repeat gem installations.
|
|
11
|
+
- Files get one mtime after `environment.patch` is applied, so mtimes do not reveal what it touched.
|
|
12
|
+
|
|
13
|
+
|
|
3
14
|
## [1.3.5] - 2026-09-15
|
|
4
15
|
|
|
5
16
|
- Docker: `--docker` no longer fails at exit.
|
|
@@ -59,6 +59,11 @@ module Lemans
|
|
|
59
59
|
raise ConfigError, "miniswen-installed: #{e.message}"
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
+
def allowed_hosts
|
|
63
|
+
policy = profile.environment.network
|
|
64
|
+
policy.mode == "allowlist" ? policy.domains : []
|
|
65
|
+
end
|
|
66
|
+
|
|
62
67
|
def command_for(task)
|
|
63
68
|
argv = [ "miniswen", "-q", "--no-refresh-registry", "--jail",
|
|
64
69
|
"-m", model.to_s, "-p", task.instruction,
|
|
@@ -67,6 +72,8 @@ module Lemans
|
|
|
67
72
|
"--exec-timeout", profile.exec_timeout.to_i,
|
|
68
73
|
"--max-output-tokens", profile.max_output_tokens ]
|
|
69
74
|
argv += [ "--max-cost", profile.cost_limit.to_i ] if profile.cost_limit
|
|
75
|
+
argv += [ "--workdir", task.environment.workdir ]
|
|
76
|
+
argv += [ "--allow-hosts", allowed_hosts.join(",") ] if allowed_hosts.any?
|
|
70
77
|
argv.map { Shellwords.escape(it.to_s) }.join(" ")
|
|
71
78
|
end
|
|
72
79
|
end
|
|
@@ -74,6 +74,7 @@ agent:
|
|
|
74
74
|
|
|
75
75
|
# The sandbox network while the agent works: just enough to reach the model
|
|
76
76
|
# Use mode: none when using a local agent (miniswen)
|
|
77
|
+
# With mode allowlist, agent commands reach these hosts through the jail's proxy
|
|
77
78
|
environment:
|
|
78
79
|
network:
|
|
79
80
|
mode: allowlist
|
|
@@ -87,6 +88,12 @@ verifier:
|
|
|
87
88
|
# bare command list). Runs after the agent finished, before the tests:
|
|
88
89
|
# setup: [gem install debug]
|
|
89
90
|
|
|
91
|
+
# Grading runs sealed unless the task names hosts, say to install the gems a solution adds:
|
|
92
|
+
# environment:
|
|
93
|
+
# network:
|
|
94
|
+
# mode: allowlist
|
|
95
|
+
# hosts: [index.rubygems.org, rubygems.org]
|
|
96
|
+
|
|
90
97
|
# A command that must pass before the graded checks run, e.g. the app's own
|
|
91
98
|
# test suite:
|
|
92
99
|
# preverify: bin/rails test
|
|
@@ -26,6 +26,10 @@ module Lemans
|
|
|
26
26
|
conf.logs_dir = absolute_path!(data["logs_dir"]) if data["logs_dir"]
|
|
27
27
|
conf.verification = root.join(data["verification"]) if data["verification"]
|
|
28
28
|
|
|
29
|
+
if (network_data = data.dig("environment", "network"))
|
|
30
|
+
conf.environment = Environment.new(network: NetworkPolicy.from_config(network_data))
|
|
31
|
+
end
|
|
32
|
+
|
|
29
33
|
conf
|
|
30
34
|
end
|
|
31
35
|
|
|
@@ -42,7 +46,9 @@ module Lemans
|
|
|
42
46
|
end
|
|
43
47
|
end
|
|
44
48
|
|
|
45
|
-
attr_accessor :timeout, :setup, :command, :preverify, :restore_paths, :logs_dir, :verification
|
|
49
|
+
attr_accessor :timeout, :setup, :command, :preverify, :restore_paths, :logs_dir, :verification, :environment
|
|
50
|
+
|
|
51
|
+
Environment = Struct.new(:network, keyword_init: true)
|
|
46
52
|
|
|
47
53
|
def initialize
|
|
48
54
|
@timeout = 10 * 60
|
|
@@ -52,6 +58,7 @@ module Lemans
|
|
|
52
58
|
@restore_paths = []
|
|
53
59
|
@logs_dir = "/logs/verifier"
|
|
54
60
|
@verification = nil
|
|
61
|
+
@environment = Environment.new(network: NetworkPolicy.new("none"))
|
|
55
62
|
end
|
|
56
63
|
|
|
57
64
|
def reward_path = "#{logs_dir.chomp("/")}/reward.txt"
|
|
@@ -64,7 +71,8 @@ module Lemans
|
|
|
64
71
|
"preverify" => preverify,
|
|
65
72
|
"restore" => restore_paths,
|
|
66
73
|
"logs_dir" => logs_dir,
|
|
67
|
-
"verification" => verification&.to_s
|
|
74
|
+
"verification" => verification&.to_s,
|
|
75
|
+
"environment" => { "network" => environment.network.to_h }
|
|
68
76
|
}.compact
|
|
69
77
|
end
|
|
70
78
|
|
data/lib/lemans/trial/setup.rb
CHANGED
|
@@ -52,6 +52,7 @@ module Lemans
|
|
|
52
52
|
workdir = Shellwords.escape(task.environment.workdir)
|
|
53
53
|
environment.exec!(
|
|
54
54
|
"cd #{workdir} && git apply --binary --whitespace=nowarn #{Shellwords.escape(seed)} && " \
|
|
55
|
+
"find . -path ./.git -prune -o -exec touch -h {} + && " \
|
|
55
56
|
"rm -rf .git && git init -q && git add -A && " \
|
|
56
57
|
"git -c user.name=lemans -c user.email=lemans@localhost commit -qm 'Initial commit'",
|
|
57
58
|
timeout:
|
data/lib/lemans/trial.rb
CHANGED
|
@@ -114,8 +114,8 @@ module Lemans
|
|
|
114
114
|
|
|
115
115
|
if result.scored? && step_task.verifiable?
|
|
116
116
|
phase(:verifier) do
|
|
117
|
-
# The sandbox is sealed before the tests arrive
|
|
118
|
-
environment.switch_network_policy!(
|
|
117
|
+
# The sandbox is sealed before the tests arrive, unless the task names hosts
|
|
118
|
+
environment.switch_network_policy!(step_task.verifier.environment.network)
|
|
119
119
|
|
|
120
120
|
verification = Verifier.new(step_task, environment, snapshot).verify! do |evidence, path|
|
|
121
121
|
store&.save_artifact(result, evidence, path: with_step_index(path))
|
data/lib/lemans/version.rb
CHANGED
data/lib/miniswen/agent.rb
CHANGED
|
@@ -294,6 +294,8 @@ module Miniswen
|
|
|
294
294
|
end
|
|
295
295
|
|
|
296
296
|
actions.each do |action|
|
|
297
|
+
return finish(:time_limit) if out_of_time?
|
|
298
|
+
|
|
297
299
|
reporter&.on_tool_call(action)
|
|
298
300
|
result = execute(action.fetch(:arguments).fetch("command"))
|
|
299
301
|
# The submit command's output is observed too, so the final tool
|
|
@@ -334,12 +336,14 @@ module Miniswen
|
|
|
334
336
|
# Checked before the model is asked, so the tripping step is never paid for.
|
|
335
337
|
def limit_reached
|
|
336
338
|
return :step_limit if max_steps.positive? && @steps >= max_steps
|
|
337
|
-
return :time_limit if
|
|
339
|
+
return :time_limit if out_of_time?
|
|
338
340
|
return :cost_limit if max_cost && @cost_known && @cost >= max_cost
|
|
339
341
|
|
|
340
342
|
nil
|
|
341
343
|
end
|
|
342
344
|
|
|
345
|
+
def out_of_time? = max_time.positive? && (@clock.call - @started_at) >= max_time
|
|
346
|
+
|
|
343
347
|
# One model turn. Returns the actions to run, or nil after appending a
|
|
344
348
|
# format-error message the model gets to react to on its next turn.
|
|
345
349
|
def next_actions
|
|
@@ -568,6 +572,11 @@ module Miniswen
|
|
|
568
572
|
registry: nil)
|
|
569
573
|
end
|
|
570
574
|
return nil unless info
|
|
575
|
+
if free?
|
|
576
|
+
return CostSource.new(name: :free_model, model: @model,
|
|
577
|
+
priced_as: "#{info.provider}/#{info.id} ($0.00, free)",
|
|
578
|
+
registry: Miniswen.registry_revision)
|
|
579
|
+
end
|
|
571
580
|
|
|
572
581
|
CostSource.new(name: :model_registry, model: @model,
|
|
573
582
|
priced_as: "#{info.provider}/#{info.id}",
|
|
@@ -668,6 +677,8 @@ module Miniswen
|
|
|
668
677
|
|
|
669
678
|
def local? = LOCAL_PROVIDERS.include?((@provider || info&.provider)&.to_sym)
|
|
670
679
|
|
|
680
|
+
def free? = info&.provider == "openrouter" && info.input_price_per_million.nil? && info.output_price_per_million.nil?
|
|
681
|
+
|
|
671
682
|
def info
|
|
672
683
|
return @info if defined?(@info)
|
|
673
684
|
|
|
@@ -681,7 +692,7 @@ module Miniswen
|
|
|
681
692
|
end
|
|
682
693
|
|
|
683
694
|
def price(response)
|
|
684
|
-
return 0.0 if local?
|
|
695
|
+
return 0.0 if local? || free?
|
|
685
696
|
|
|
686
697
|
input = info&.input_price_per_million
|
|
687
698
|
output = info&.output_price_per_million
|
data/lib/miniswen/cli.rb
CHANGED
|
@@ -23,10 +23,13 @@ module Miniswen
|
|
|
23
23
|
@refresh_registry = false
|
|
24
24
|
@skip_registry_refresh = false
|
|
25
25
|
@jail = false
|
|
26
|
+
@allowed_hosts = nil
|
|
27
|
+
@workdir = nil
|
|
26
28
|
end
|
|
27
29
|
|
|
28
30
|
def run
|
|
29
31
|
parse_args!
|
|
32
|
+
Dir.chdir(@workdir) if @workdir
|
|
30
33
|
|
|
31
34
|
# Require the core library after parsing options,
|
|
32
35
|
# so env flags kick in
|
|
@@ -45,7 +48,7 @@ module Miniswen
|
|
|
45
48
|
Environment::Docker.new(@docker_id)
|
|
46
49
|
elsif @jail
|
|
47
50
|
require "miniswen/jail"
|
|
48
|
-
Jail.new.start
|
|
51
|
+
Jail.new(allowed_hosts: @allowed_hosts).start
|
|
49
52
|
else
|
|
50
53
|
Local.new
|
|
51
54
|
end
|
|
@@ -161,6 +164,14 @@ module Miniswen
|
|
|
161
164
|
@jail = true
|
|
162
165
|
end
|
|
163
166
|
|
|
167
|
+
opts.on("--allow-hosts=HOSTS", String, "Let jailed commands reach these hosts through a proxy (comma-separated)") do |v|
|
|
168
|
+
@allowed_hosts = v.split(",").map(&:strip).reject(&:empty?)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
opts.on("--workdir=DIR", String, "Run commands in DIR instead of the current directory") do |v|
|
|
172
|
+
@workdir = v
|
|
173
|
+
end
|
|
174
|
+
|
|
164
175
|
opts.on("--refresh-registry", "Refresh the model registry, persist it, and exit") do
|
|
165
176
|
@refresh_registry = true
|
|
166
177
|
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "socket"
|
|
5
|
+
|
|
6
|
+
module Miniswen
|
|
7
|
+
class Jail < Local
|
|
8
|
+
# An HTTP CONNECT proxy that lets jailed commands reach the allowed hosts only.
|
|
9
|
+
class Proxy
|
|
10
|
+
def self.forward(port, socket)
|
|
11
|
+
server = TCPServer.new("127.0.0.1", port)
|
|
12
|
+
exit!(0) if fork
|
|
13
|
+
serve(server) { pump(it, UNIXSocket.new(socket)) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.serve(server)
|
|
17
|
+
Thread.report_on_exception = false
|
|
18
|
+
loop { Thread.new(server.accept) { yield it } }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.pump(client, upstream)
|
|
22
|
+
Thread.new do
|
|
23
|
+
IO.copy_stream(client, upstream)
|
|
24
|
+
upstream.close_write
|
|
25
|
+
end
|
|
26
|
+
IO.copy_stream(upstream, client)
|
|
27
|
+
ensure
|
|
28
|
+
client.close
|
|
29
|
+
upstream.close
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def initialize(hosts:, socket:)
|
|
33
|
+
@hosts = hosts
|
|
34
|
+
@socket = socket
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def start
|
|
38
|
+
FileUtils.rm_f(@socket)
|
|
39
|
+
server = UNIXServer.new(@socket)
|
|
40
|
+
@pid = fork { self.class.serve(server) { handle(it) } }
|
|
41
|
+
self
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def stop
|
|
45
|
+
Process.kill(:KILL, @pid)
|
|
46
|
+
Process.wait(@pid)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def handle(client)
|
|
50
|
+
_, target = client.gets("\r\n\r\n").split(" ", 3)
|
|
51
|
+
host, port = target.split(":", 2)
|
|
52
|
+
return client.write("HTTP/1.1 403 Forbidden\r\n\r\n") unless @hosts.include?(host)
|
|
53
|
+
|
|
54
|
+
upstream = Socket.tcp(host, port.to_i, connect_timeout: 10)
|
|
55
|
+
client.write("HTTP/1.1 200 Connection Established\r\n\r\n")
|
|
56
|
+
self.class.pump(client, upstream)
|
|
57
|
+
ensure
|
|
58
|
+
client.close
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
data/lib/miniswen/jail.rb
CHANGED
|
@@ -1,30 +1,39 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "open3"
|
|
3
4
|
require "shellwords"
|
|
4
5
|
|
|
5
6
|
require "miniswen/local"
|
|
7
|
+
require "miniswen/jail/proxy"
|
|
6
8
|
|
|
7
9
|
module Miniswen
|
|
8
10
|
# Runs every command in its own namespaces: none of the harness's environment,
|
|
9
|
-
# no network, read-only system, none of its files.
|
|
11
|
+
# no network, read-only system, none of its files. A task that may reach some
|
|
12
|
+
# hosts gets an HTTP proxy on loopback that allows only those.
|
|
10
13
|
class Jail < Local
|
|
11
|
-
|
|
14
|
+
PROXY_PORT = 3128
|
|
15
|
+
PROXY_SOCKET = "/var/lib/miniswen/run/miniswen-proxy.sock"
|
|
16
|
+
|
|
17
|
+
def initialize(workdir: Dir.pwd, allowed_hosts: nil)
|
|
12
18
|
@workdir = workdir
|
|
19
|
+
@allowed_hosts = allowed_hosts
|
|
13
20
|
end
|
|
14
21
|
|
|
15
22
|
def start
|
|
16
23
|
_, @stdout, @stderr, @holder = Open3.popen3(
|
|
17
|
-
|
|
24
|
+
container_env,
|
|
18
25
|
"unshare", "--net", "--mount", "--pid", "--fork", "--kill-child", "--mount-proc", "sh", "-c", setup,
|
|
19
26
|
pgroup: true, unsetenv_others: true
|
|
20
27
|
)
|
|
21
|
-
|
|
28
|
+
raise InfrastructureError, "jail did not start: #{@stderr.read}" unless @stdout.gets == "ready\n"
|
|
22
29
|
|
|
23
|
-
|
|
30
|
+
@proxy = Proxy.new(hosts: @allowed_hosts, socket: PROXY_SOCKET).start if @allowed_hosts
|
|
31
|
+
self
|
|
24
32
|
end
|
|
25
33
|
|
|
26
34
|
def stop
|
|
27
|
-
Process.kill(:KILL, -@holder.pid) if @holder
|
|
35
|
+
Process.kill(:KILL, -@holder.pid) if @holder&.alive?
|
|
36
|
+
@proxy&.stop
|
|
28
37
|
end
|
|
29
38
|
|
|
30
39
|
private
|
|
@@ -37,22 +46,40 @@ module Miniswen
|
|
|
37
46
|
mount --bind #{Shellwords.escape(@workdir)} #{Shellwords.escape(@workdir)}
|
|
38
47
|
# Make private temp dirs their own mounts
|
|
39
48
|
for dir in /tmp /run /root; do mkdir -p "/var/lib/miniswen$dir" && mount --bind "/var/lib/miniswen$dir" "$dir"; done
|
|
49
|
+
#{forwarder_command if @allowed_hosts}
|
|
40
50
|
# Make everything without its own mount read-only
|
|
41
51
|
mount -o remount,bind,ro /
|
|
42
52
|
echo ready
|
|
43
53
|
exec sleep infinity
|
|
44
54
|
SH
|
|
45
55
|
|
|
56
|
+
def forwarder_command
|
|
57
|
+
forward = "Miniswen::Jail::Proxy.forward(#{PROXY_PORT}, #{"/run/#{File.basename(PROXY_SOCKET)}".inspect})"
|
|
58
|
+
[ RbConfig.ruby, "-I", File.expand_path("..", __dir__), "-rminiswen/jail", "-e", forward ].map { Shellwords.escape(it) }.join(" ")
|
|
59
|
+
end
|
|
60
|
+
|
|
46
61
|
def spawn_arguments(command, env)
|
|
47
|
-
[
|
|
62
|
+
[ command_env(env),
|
|
48
63
|
"nsenter", "--target", @holder.pid.to_s, "--net", "--mount", "--pid=/proc/#{@holder.pid}/ns/pid_for_children", "--wd=#{@workdir}",
|
|
49
64
|
"--", "setpriv", "--bounding-set=-all", "--inh-caps=-all", "--no-new-privs", "--", "sh", "-c", command ]
|
|
50
65
|
end
|
|
51
66
|
|
|
52
67
|
def spawn_options = super.merge(unsetenv_others: true)
|
|
53
68
|
|
|
69
|
+
def command_env(env)
|
|
70
|
+
variables = container_env.merge(env.to_h).slice(*container_variables)
|
|
71
|
+
return variables unless @allowed_hosts
|
|
72
|
+
|
|
73
|
+
proxy = "http://127.0.0.1:#{PROXY_PORT}"
|
|
74
|
+
variables.merge("http_proxy" => proxy, "https_proxy" => proxy, "no_proxy" => "localhost,127.0.0.1")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def container_env
|
|
78
|
+
@container_env ||= File.read("/proc/1/environ").split("\0").to_h { it.split("=", 2) }
|
|
79
|
+
end
|
|
80
|
+
|
|
54
81
|
def container_variables
|
|
55
|
-
@container_variables ||=
|
|
82
|
+
@container_variables ||= container_env.keys | Agent::EXEC_ENV.keys
|
|
56
83
|
end
|
|
57
84
|
end
|
|
58
85
|
end
|
data/lib/miniswen/local.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "
|
|
3
|
+
require "tempfile"
|
|
4
4
|
|
|
5
5
|
require "miniswen/environment"
|
|
6
6
|
|
|
@@ -12,18 +12,17 @@ module Miniswen
|
|
|
12
12
|
# Always through a shell: Ruby execs a metacharacter-free string directly,
|
|
13
13
|
# and a missing binary would then raise ENOENT here instead of exiting 127.
|
|
14
14
|
def exec(command, timeout: nil, env: nil)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
reader = Thread.new { io.read }
|
|
15
|
+
Tempfile.create("miniswen") do |log|
|
|
16
|
+
wait_thr = Process.detach(Process.spawn(*spawn_arguments(command, env), in: File::NULL, %i[out err] => log, **spawn_options))
|
|
18
17
|
|
|
19
18
|
if timeout&.positive? && wait_thr.join(timeout).nil?
|
|
20
19
|
kill_group(wait_thr.pid)
|
|
21
20
|
wait_thr.join
|
|
22
|
-
output = "#{scrub(
|
|
21
|
+
output = "#{scrub(File.read(log))}\n<command timed out after #{timeout} seconds>"
|
|
23
22
|
return ExecResult.new(exit_code: TIMEOUT_EXIT_CODE, output:)
|
|
24
23
|
end
|
|
25
24
|
|
|
26
|
-
ExecResult.new(exit_code: exit_code(wait_thr.value), output: scrub(
|
|
25
|
+
ExecResult.new(exit_code: exit_code(wait_thr.value), output: scrub(File.read(log)))
|
|
27
26
|
end
|
|
28
27
|
end
|
|
29
28
|
|
data/lib/miniswen/testing.rb
CHANGED
|
@@ -36,7 +36,13 @@ module Miniswen
|
|
|
36
36
|
modalities: { input: %w[text], output: %w[text] }
|
|
37
37
|
)
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
FREE_MODEL = RubyLLM::Model::Info.new(
|
|
40
|
+
id: "test-free", name: "Test free", provider: "openrouter",
|
|
41
|
+
capabilities: %w[function_calling],
|
|
42
|
+
modalities: { input: %w[text], output: %w[text] }
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
MODELS = { PRICED_MODEL.id => PRICED_MODEL, UNPRICED_MODEL.id => UNPRICED_MODEL, FREE_MODEL.id => FREE_MODEL }.freeze
|
|
40
46
|
|
|
41
47
|
# Serves the test models from ruby_llm's registry lookups.
|
|
42
48
|
module FindTestModels
|
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.
|
|
4
|
+
version: 1.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Svyatoslav Kryukov
|
|
@@ -237,6 +237,7 @@ files:
|
|
|
237
237
|
- lib/miniswen/environment.rb
|
|
238
238
|
- lib/miniswen/environment/docker.rb
|
|
239
239
|
- lib/miniswen/jail.rb
|
|
240
|
+
- lib/miniswen/jail/proxy.rb
|
|
240
241
|
- lib/miniswen/local.rb
|
|
241
242
|
- lib/miniswen/ruby_llm.rb
|
|
242
243
|
- lib/miniswen/testing.rb
|