ruby_llm-codex 0.2.1 → 0.3.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 +9 -0
- data/README.md +11 -9
- data/UPGRADE.md +28 -0
- data/lib/ruby_llm/codex/version.rb +1 -1
- data/lib/ruby_llm/providers/codex.rb +71 -48
- data/lib/ruby_llm-codex.rb +0 -1
- metadata +7 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b423cd9ecec786bde4858cfd86a5f2d010853925f341668a9a27ee46ce1e13c6
|
|
4
|
+
data.tar.gz: 1e9b7ee463ee62cd2a916979e36df16d3cd2270ea26d521b6f5bc8c16a85a11d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5802a69971bc7798b5abe3317cc125db21f2bd7db1be86bf27cb4afaec3c4d57408d948d486268552a24d1726b3bddfc5f6b13ec24172d376b364d38e8b8883e
|
|
7
|
+
data.tar.gz: 877c1bb90fcf5e7c6a1f522a6adc163fcc6be5268e1baed14169dc40e0cd640f469a698a451b6e3e527198ef6cc098a1e0151ea86c9dacd4e4328bfe115932a6
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.0] - 2026-10-12
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### feat
|
|
12
|
+
|
|
13
|
+
- migrate Codex provider to RubyLLM 2.1
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
8
17
|
## [0.2.1] - 2026-07-17
|
|
9
18
|
|
|
10
19
|
|
data/README.md
CHANGED
|
@@ -19,8 +19,8 @@ supported mapping but not an identical wire-level request.
|
|
|
19
19
|
## Requirements
|
|
20
20
|
|
|
21
21
|
- Ruby 3.3 or newer
|
|
22
|
-
- RubyLLM 1.
|
|
23
|
-
- Codex CLI 0.
|
|
22
|
+
- RubyLLM 2.1.1 or newer, below 2.2
|
|
23
|
+
- Codex CLI 0.162.1 or newer, available as `codex`
|
|
24
24
|
- Linux or macOS (timeout cleanup relies on POSIX process groups)
|
|
25
25
|
- A completed ChatGPT login: `codex login`
|
|
26
26
|
|
|
@@ -34,6 +34,8 @@ codex login
|
|
|
34
34
|
Check the model list in your installed Codex version before a large run. Model
|
|
35
35
|
availability depends on the account and currently installed CLI.
|
|
36
36
|
|
|
37
|
+
For migration steps from RubyLLM 1.x, see [UPGRADE.md](UPGRADE.md).
|
|
38
|
+
|
|
37
39
|
## Installation
|
|
38
40
|
|
|
39
41
|
Add the gem to your application's `Gemfile`:
|
|
@@ -55,7 +57,7 @@ Require the gem and use Codex as an explicit RubyLLM provider:
|
|
|
55
57
|
```ruby
|
|
56
58
|
require "ruby_llm-codex"
|
|
57
59
|
|
|
58
|
-
class SummarySchema <
|
|
60
|
+
class SummarySchema < Schematist::Schema
|
|
59
61
|
string :summary
|
|
60
62
|
end
|
|
61
63
|
|
|
@@ -65,11 +67,11 @@ response = RubyLLM.chat(model: ENV.fetch("CODEX_MODEL"), provider: :codex)
|
|
|
65
67
|
.with_thinking(effort: :medium)
|
|
66
68
|
.ask("The text to summarize")
|
|
67
69
|
|
|
68
|
-
pp response.
|
|
70
|
+
pp response.parsed
|
|
69
71
|
```
|
|
70
72
|
|
|
71
|
-
`response.content`
|
|
72
|
-
|
|
73
|
+
`response.content` contains the JSON string. Read `response.parsed` for the
|
|
74
|
+
structured Hash, and `response.tokens` for token counts.
|
|
73
75
|
|
|
74
76
|
## Isolation and reproducibility
|
|
75
77
|
|
|
@@ -97,7 +99,7 @@ saved session history from quietly affecting a request.
|
|
|
97
99
|
To deliberately let Codex inspect a repository or use selected configuration:
|
|
98
100
|
|
|
99
101
|
```ruby
|
|
100
|
-
chat.
|
|
102
|
+
chat.with_provider_options(
|
|
101
103
|
codex: {
|
|
102
104
|
working_directory: "/path/to/project",
|
|
103
105
|
sandbox: "read-only",
|
|
@@ -126,8 +128,8 @@ RubyLLM.configure do |config|
|
|
|
126
128
|
end
|
|
127
129
|
```
|
|
128
130
|
|
|
129
|
-
Only the nested `codex` namespace is accepted by `
|
|
130
|
-
generic
|
|
131
|
+
Only the nested `codex` namespace is accepted by `with_provider_options`; unsupported
|
|
132
|
+
generic provider options raise an error instead of being silently ignored. Use
|
|
131
133
|
`with_thinking` for reasoning effort and `with_instructions` for developer
|
|
132
134
|
instructions. Those settings, plus `features.shell_snapshot` and
|
|
133
135
|
`shell_environment_policy.inherit`, are reserved from the arbitrary
|
data/UPGRADE.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Upgrade Guide
|
|
2
|
+
|
|
3
|
+
## RubyLLM 2.1
|
|
4
|
+
|
|
5
|
+
This provider now requires RubyLLM `~> 2.1.1` and Codex CLI 0.162.1 or newer.
|
|
6
|
+
RubyLLM 1.x and 2.0 are no longer supported. Upgrade the CLI with
|
|
7
|
+
`npm install -g @openai/codex` and check `codex --version`.
|
|
8
|
+
|
|
9
|
+
Host applications must:
|
|
10
|
+
|
|
11
|
+
- Replace `with_params(codex: ...)` with `with_provider_options(codex: ...)`.
|
|
12
|
+
- Replace `RubyLLM::Schema` with `Schematist::Schema`.
|
|
13
|
+
- Read structured results through `response.parsed`; `response.content` is
|
|
14
|
+
now a JSON string.
|
|
15
|
+
- Read the model ID from `response.model` and usage from `response.tokens`:
|
|
16
|
+
`input`, `output`, `cache_read`, and `thinking`. Input excludes cached tokens;
|
|
17
|
+
output includes thinking tokens. Unreported counts remain `nil`.
|
|
18
|
+
|
|
19
|
+
Plain Ruby consumers can update directly after adapting these calls. Rails
|
|
20
|
+
hosts using RubyLLM persistence must complete the
|
|
21
|
+
[2.0 upgrade](https://github.com/crmne/ruby_llm/blob/v2.0.0/docs/_reference/upgrading.md)
|
|
22
|
+
on 2.0, including cleanup, before following the
|
|
23
|
+
[2.1 upgrade](https://rubyllm.com/upgrading/). On Rails below 8.1.4, pin `json`
|
|
24
|
+
to `< 3` before updating.
|
|
25
|
+
|
|
26
|
+
Each Codex invocation records successful or failed usage attempts in RubyLLM.
|
|
27
|
+
Timeouts cover process exit and pipe draining, including descendants that keep
|
|
28
|
+
output pipes open after the CLI exits. Process-group cleanup remains bounded.
|
|
@@ -13,6 +13,8 @@ module RubyLLM
|
|
|
13
13
|
# This provider is deliberately narrow: non-streaming text generation and
|
|
14
14
|
# structured output are supported; RubyLLM tools and attachments are not.
|
|
15
15
|
class Codex < Provider
|
|
16
|
+
protocol :codex, ::RubyLLM::Protocol
|
|
17
|
+
|
|
16
18
|
class Error < RubyLLM::Error; end
|
|
17
19
|
class TimeoutError < Error; end
|
|
18
20
|
class UnsupportedFeatureError < Error; end
|
|
@@ -20,7 +22,7 @@ module RubyLLM
|
|
|
20
22
|
DEFAULT_SANDBOX = "read-only"
|
|
21
23
|
DEFAULT_TIMEOUT = 300
|
|
22
24
|
DEFAULT_SHELL_ENVIRONMENT_INHERIT = "none"
|
|
23
|
-
MINIMUM_CLI_VERSION = "0.
|
|
25
|
+
MINIMUM_CLI_VERSION = "0.162.1"
|
|
24
26
|
RESERVED_CONFIG_KEYS = %w[
|
|
25
27
|
developer_instructions
|
|
26
28
|
features.shell_snapshot
|
|
@@ -37,11 +39,23 @@ module RubyLLM
|
|
|
37
39
|
end
|
|
38
40
|
|
|
39
41
|
# rubocop:disable Metrics/ParameterLists
|
|
40
|
-
def complete(messages, tools:, temperature:, model:,
|
|
41
|
-
schema: nil, thinking: nil, tool_prefs: nil,
|
|
42
|
+
def complete(messages, tools:, temperature:, model:, provider_options: {}, headers: {},
|
|
43
|
+
schema: nil, thinking: nil, tool_prefs: nil, max_output_tokens: nil,
|
|
44
|
+
citations: false, caching: nil, protocol: nil, before_request: [],
|
|
45
|
+
usage_recorder: nil, provider_tools: [], compaction: nil, end_user: nil, &stream)
|
|
42
46
|
validate_request!(tools:, temperature:, headers:, stream:)
|
|
43
47
|
|
|
44
|
-
|
|
48
|
+
unsupported = {
|
|
49
|
+
max_output_tokens:, citations: citations || nil, caching:,
|
|
50
|
+
provider_tools: provider_tools.empty? ? nil : provider_tools,
|
|
51
|
+
compaction:, end_user:, tool_prefs: (tool_prefs if tool_prefs&.values&.any?)
|
|
52
|
+
}.compact
|
|
53
|
+
unless unsupported.empty?
|
|
54
|
+
raise UnsupportedFeatureError,
|
|
55
|
+
"Unsupported Codex request options: #{unsupported.keys.join(", ")}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
options = codex_options(provider_options)
|
|
45
59
|
developer_instructions, prompt = build_prompt(messages)
|
|
46
60
|
request = {
|
|
47
61
|
model: model.id,
|
|
@@ -51,9 +65,22 @@ module RubyLLM
|
|
|
51
65
|
thinking:
|
|
52
66
|
}
|
|
53
67
|
|
|
68
|
+
before_request.each { |hook| hook.call(request) }
|
|
69
|
+
tracker = Accounting::Usage::Tracker.new(
|
|
70
|
+
operation: :chat, provider: self, model:, config: @config, on_finish: usage_recorder
|
|
71
|
+
)
|
|
54
72
|
with_working_directory(options) do |working_directory|
|
|
55
|
-
|
|
73
|
+
ensure_cli_compatible!(working_directory, options)
|
|
74
|
+
tracker.start
|
|
75
|
+
response = run_codex(request, working_directory:, options:)
|
|
76
|
+
tracker.succeed(response)
|
|
56
77
|
end
|
|
78
|
+
rescue Errno::ENOENT => error
|
|
79
|
+
tracker&.fail_pending(error)
|
|
80
|
+
raise Error, "Could not execute Codex CLI: #{error.message}. Install it or set codex_cli_path."
|
|
81
|
+
rescue => error
|
|
82
|
+
tracker&.fail_pending(error)
|
|
83
|
+
raise
|
|
57
84
|
end
|
|
58
85
|
# rubocop:enable Metrics/ParameterLists
|
|
59
86
|
|
|
@@ -109,11 +136,11 @@ module RubyLLM
|
|
|
109
136
|
"Per-request HTTP headers do not apply to the local Codex provider"
|
|
110
137
|
end
|
|
111
138
|
|
|
112
|
-
def codex_options(
|
|
113
|
-
|
|
114
|
-
raw =
|
|
139
|
+
def codex_options(provider_options)
|
|
140
|
+
validate_provider_options!(provider_options)
|
|
141
|
+
raw = provider_options.is_a?(Hash) ? (provider_options[:codex] || provider_options["codex"] || {}) : {}
|
|
115
142
|
unless raw.is_a?(Hash)
|
|
116
|
-
raise ArgumentError, "Codex
|
|
143
|
+
raise ArgumentError, "Codex provider_options must be a Hash"
|
|
117
144
|
end
|
|
118
145
|
|
|
119
146
|
raw = raw.transform_keys(&:to_sym)
|
|
@@ -144,16 +171,16 @@ module RubyLLM
|
|
|
144
171
|
}
|
|
145
172
|
end
|
|
146
173
|
|
|
147
|
-
def
|
|
148
|
-
unless
|
|
149
|
-
raise ArgumentError, "RubyLLM
|
|
174
|
+
def validate_provider_options!(provider_options)
|
|
175
|
+
unless provider_options.is_a?(Hash)
|
|
176
|
+
raise ArgumentError, "RubyLLM provider_options must be a Hash"
|
|
150
177
|
end
|
|
151
178
|
|
|
152
|
-
unsupported =
|
|
179
|
+
unsupported = provider_options.keys.reject { |key| key.to_s == "codex" }
|
|
153
180
|
return if unsupported.empty?
|
|
154
181
|
|
|
155
182
|
raise UnsupportedFeatureError,
|
|
156
|
-
"Unsupported
|
|
183
|
+
"Unsupported provider_options for the local Codex provider: #{unsupported.map(&:inspect).join(", ")}"
|
|
157
184
|
end
|
|
158
185
|
|
|
159
186
|
def validate_config!(config)
|
|
@@ -168,7 +195,7 @@ module RubyLLM
|
|
|
168
195
|
value = raw.key?(:timeout) ? raw[:timeout] : @config.codex_timeout
|
|
169
196
|
value = DEFAULT_TIMEOUT if value.nil?
|
|
170
197
|
timeout = Float(value)
|
|
171
|
-
raise ArgumentError, "Codex timeout must be greater than zero" unless timeout.positive?
|
|
198
|
+
raise ArgumentError, "Codex timeout must be greater than zero" unless timeout.finite? && timeout.positive?
|
|
172
199
|
|
|
173
200
|
timeout
|
|
174
201
|
rescue TypeError, ArgumentError
|
|
@@ -200,8 +227,6 @@ module RubyLLM
|
|
|
200
227
|
end
|
|
201
228
|
|
|
202
229
|
def run_codex(request, working_directory:, options:)
|
|
203
|
-
ensure_cli_compatible!(working_directory, options)
|
|
204
|
-
|
|
205
230
|
Tempfile.create(["ruby-llm-codex-output-", ".txt"]) do |output_file|
|
|
206
231
|
with_schema_file(request[:schema]) do |schema_path|
|
|
207
232
|
output_file.close
|
|
@@ -295,7 +320,7 @@ module RubyLLM
|
|
|
295
320
|
chdir: working_directory,
|
|
296
321
|
pgroup: true
|
|
297
322
|
)
|
|
298
|
-
threads <<
|
|
323
|
+
threads << Thread.new do
|
|
299
324
|
stdin.write(prompt)
|
|
300
325
|
rescue Errno::EPIPE, IOError
|
|
301
326
|
nil
|
|
@@ -306,12 +331,15 @@ module RubyLLM
|
|
|
306
331
|
threads << stderr_reader = Thread.new { stderr.read }
|
|
307
332
|
threads.each { |thread| thread.report_on_exception = false }
|
|
308
333
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
334
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + options[:timeout]
|
|
335
|
+
[wait_thread, *threads].each do |thread|
|
|
336
|
+
remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
337
|
+
unless thread.join([remaining, 0].max)
|
|
338
|
+
terminate_process_group(wait_thread, threads)
|
|
339
|
+
raise TimeoutError, "Codex CLI timed out after #{options[:timeout]} seconds"
|
|
340
|
+
end
|
|
312
341
|
end
|
|
313
342
|
|
|
314
|
-
writer.join
|
|
315
343
|
[stdout_reader.value, stderr_reader.value, wait_thread.value]
|
|
316
344
|
ensure
|
|
317
345
|
[stdin, stdout, stderr].compact.each do |io|
|
|
@@ -322,10 +350,13 @@ module RubyLLM
|
|
|
322
350
|
threads.each { |thread| thread.kill if thread.alive? }
|
|
323
351
|
end
|
|
324
352
|
|
|
325
|
-
def terminate_process_group(wait_thread)
|
|
353
|
+
def terminate_process_group(wait_thread, threads)
|
|
326
354
|
Process.kill("TERM", -wait_thread.pid)
|
|
327
|
-
|
|
328
|
-
|
|
355
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 2
|
|
356
|
+
[wait_thread, *threads].each do |thread|
|
|
357
|
+
remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
358
|
+
thread.join([remaining, 0].max)
|
|
359
|
+
end
|
|
329
360
|
Process.kill("KILL", -wait_thread.pid)
|
|
330
361
|
wait_thread.join
|
|
331
362
|
rescue Errno::ESRCH, Errno::ECHILD
|
|
@@ -335,7 +366,7 @@ module RubyLLM
|
|
|
335
366
|
def add_reasoning_config(command, thinking)
|
|
336
367
|
return unless thinking
|
|
337
368
|
|
|
338
|
-
effort = thinking.
|
|
369
|
+
effort = thinking.effort
|
|
339
370
|
if effort
|
|
340
371
|
add_config(command, "model_reasoning_effort", effort.to_s)
|
|
341
372
|
return
|
|
@@ -387,7 +418,7 @@ module RubyLLM
|
|
|
387
418
|
end
|
|
388
419
|
|
|
389
420
|
def raise_invocation_error!(status, events, stderr)
|
|
390
|
-
event = events.
|
|
421
|
+
event = events.reverse_each.find { |item| %w[turn.failed error].include?(item["type"]) }
|
|
391
422
|
detail = event && (event["message"] || event["error"] || event).to_s
|
|
392
423
|
detail = stderr.strip if detail.to_s.strip.empty?
|
|
393
424
|
detail = "Codex exited with status #{status.exitstatus}" if detail.to_s.strip.empty?
|
|
@@ -395,14 +426,14 @@ module RubyLLM
|
|
|
395
426
|
end
|
|
396
427
|
|
|
397
428
|
def final_agent_message(events)
|
|
398
|
-
event = events.
|
|
429
|
+
event = events.reverse_each.find do |item|
|
|
399
430
|
item["type"] == "item.completed" && item.dig("item", "type") == "agent_message"
|
|
400
431
|
end
|
|
401
432
|
event&.dig("item", "text")
|
|
402
433
|
end
|
|
403
434
|
|
|
404
435
|
def build_message(content, model, events, stderr)
|
|
405
|
-
usage = events.
|
|
436
|
+
usage = events.reverse_each.find { |event| event["type"] == "turn.completed" }&.fetch("usage", {}) || {}
|
|
406
437
|
cached = integer_or_nil(usage["cached_input_tokens"])
|
|
407
438
|
total_input = integer_or_nil(usage["input_tokens"])
|
|
408
439
|
uncached_input = total_input && [total_input - cached.to_i, 0].max
|
|
@@ -410,11 +441,14 @@ module RubyLLM
|
|
|
410
441
|
Message.new(
|
|
411
442
|
role: :assistant,
|
|
412
443
|
content: content,
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
444
|
+
model:,
|
|
445
|
+
tokens: Tokens.new(
|
|
446
|
+
input: uncached_input,
|
|
447
|
+
cache_read: cached,
|
|
448
|
+
output: integer_or_nil(usage["output_tokens"]),
|
|
449
|
+
thinking: integer_or_nil(usage["reasoning_output_tokens"])
|
|
450
|
+
),
|
|
451
|
+
finish_reason: :stop,
|
|
418
452
|
raw: {events:, stderr: stderr.empty? ? nil : stderr}
|
|
419
453
|
)
|
|
420
454
|
end
|
|
@@ -449,23 +483,12 @@ module RubyLLM
|
|
|
449
483
|
end
|
|
450
484
|
|
|
451
485
|
def text_content(message)
|
|
452
|
-
|
|
453
|
-
return content if content.is_a?(String)
|
|
454
|
-
|
|
455
|
-
if content.is_a?(RubyLLM::Content)
|
|
456
|
-
unless content.attachments.empty?
|
|
457
|
-
raise UnsupportedFeatureError,
|
|
458
|
-
"Attachments are not supported by the Codex comparison provider"
|
|
459
|
-
end
|
|
460
|
-
return content.text.to_s
|
|
461
|
-
end
|
|
462
|
-
|
|
463
|
-
if defined?(RubyLLM::Content::Raw) && content.is_a?(RubyLLM::Content::Raw)
|
|
486
|
+
unless message.attachments.empty?
|
|
464
487
|
raise UnsupportedFeatureError,
|
|
465
|
-
"
|
|
488
|
+
"Attachments are not supported by the Codex comparison provider"
|
|
466
489
|
end
|
|
467
490
|
|
|
468
|
-
|
|
491
|
+
message.content
|
|
469
492
|
end
|
|
470
493
|
|
|
471
494
|
def present?(value)
|
data/lib/ruby_llm-codex.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby_llm-codex
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Paulo Fidalgo
|
|
@@ -14,22 +14,16 @@ dependencies:
|
|
|
14
14
|
name: ruby_llm
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - "
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: '1.16'
|
|
20
|
-
- - "<"
|
|
17
|
+
- - "~>"
|
|
21
18
|
- !ruby/object:Gem::Version
|
|
22
|
-
version:
|
|
19
|
+
version: 2.1.1
|
|
23
20
|
type: :runtime
|
|
24
21
|
prerelease: false
|
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
26
23
|
requirements:
|
|
27
|
-
- - "
|
|
28
|
-
- !ruby/object:Gem::Version
|
|
29
|
-
version: '1.16'
|
|
30
|
-
- - "<"
|
|
24
|
+
- - "~>"
|
|
31
25
|
- !ruby/object:Gem::Version
|
|
32
|
-
version:
|
|
26
|
+
version: 2.1.1
|
|
33
27
|
- !ruby/object:Gem::Dependency
|
|
34
28
|
name: minitest
|
|
35
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -85,6 +79,7 @@ files:
|
|
|
85
79
|
- CHANGELOG.md
|
|
86
80
|
- LICENSE
|
|
87
81
|
- README.md
|
|
82
|
+
- UPGRADE.md
|
|
88
83
|
- lib/ruby_llm-codex.rb
|
|
89
84
|
- lib/ruby_llm/codex/version.rb
|
|
90
85
|
- lib/ruby_llm/providers/codex.rb
|
|
@@ -115,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
115
110
|
- !ruby/object:Gem::Version
|
|
116
111
|
version: '0'
|
|
117
112
|
requirements: []
|
|
118
|
-
rubygems_version: 4.0.
|
|
113
|
+
rubygems_version: 4.0.20
|
|
119
114
|
specification_version: 4
|
|
120
115
|
summary: Use a local, ChatGPT-authenticated Codex CLI as a RubyLLM provider
|
|
121
116
|
test_files: []
|