ruby_llm-codex 0.2.1 → 0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 60eb34388c3f961cfbfa0604dfc757b407b781108bf9b33c1280bbb07c4a76e4
4
- data.tar.gz: 5c4c9e0a38ced5c2bfd8d67cad4d135100bc55f4107479fcea3bb3e0cca37bfc
3
+ metadata.gz: dd8ce8fd8d9ea9084dd8a54f70e7aac4a790a74ea932820d77466f2eee87c660
4
+ data.tar.gz: 46a28a66dd5c527e31be3b86fc007edd4e9de6a5182aab124dc535f4025f8718
5
5
  SHA512:
6
- metadata.gz: b22b44093a68b3c5d9c9c73fe533ef304d1aa7aa2408ede897940b78fc2cf2e23953237de4107b3a95f56a3b25cc72a06b9a2d67d554e7c81a968b5854d3122a
7
- data.tar.gz: f6db93982d737c0769485f00c15c05ce48f182b394b594ea87bf2426add6e0f25eacca7e876ada5abdd8020a809ffe029193ee018bdc360929f7f2d6f310a173
6
+ metadata.gz: bc3d9164ed7f97cd26e22c259342df7bd870252a0c82306f9344dd6d9891f409c2604dee8a2e542c36454fb9fedbec87a88fde0d5d24f1db6b9ba00d2be481b5
7
+ data.tar.gz: bc03eb41c6baffa0c88872dd229d7f6dcb3b99adbfcaa7010fb94d9469bcae28cc76cc8641f7da45995221e9d83d73183ca8e49d5a8b050b950dffe2361f1f0c
data/CHANGELOG.md CHANGED
@@ -5,6 +5,24 @@ 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.4.0] - 2026-11-04
9
+
10
+
11
+ ### Added
12
+
13
+ - require Ruby 4 and adopt Gemkit
14
+
15
+
16
+
17
+ ## [0.3.0] - 2026-10-12
18
+
19
+
20
+ ### feat
21
+
22
+ - migrate Codex provider to RubyLLM 2.1
23
+
24
+
25
+
8
26
  ## [0.2.1] - 2026-07-17
9
27
 
10
28
 
data/README.md CHANGED
@@ -18,9 +18,9 @@ supported mapping but not an identical wire-level request.
18
18
 
19
19
  ## Requirements
20
20
 
21
- - Ruby 3.3 or newer
22
- - RubyLLM 1.16.0 or newer, below 2.0
23
- - Codex CLI 0.144.5 or newer, available as `codex`
21
+ - Ruby 4.0 or newer
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 < RubyLLM::Schema
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.content
70
+ pp response.parsed
69
71
  ```
70
72
 
71
- `response.content` is normalized by RubyLLM into the same Hash shape returned
72
- for structured outputs from its other providers.
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.with_params(
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 `with_params`; unsupported
130
- generic params raise an error instead of being silently ignored. Use
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
@@ -162,95 +164,45 @@ client while keeping the same RubyLLM provider surface.
162
164
  - **Local instructions affect output:** keep `ignore_user_config: true` and do
163
165
  not set a working directory when you need isolated requests.
164
166
 
165
- ## Development
166
-
167
- ```bash
168
- git clone https://github.com/ethos-link/ruby_llm-codex.git
169
- cd ruby_llm-codex
170
-
171
- bundle install
172
- bundle exec rake
173
- ```
174
-
175
- To test an unreleased checkout from another application, use a local path in
176
- that application's `Gemfile`:
177
-
178
- ```ruby
179
- gem "ruby_llm-codex", path: "/path/to/ruby_llm-codex"
180
- ```
181
-
182
- The default Rake task runs the unit suite and Standard Ruby. CI runs the same
183
- gate on Ruby 3.3, 3.4, and 4.0.3, then validates commit messages and
184
- git-cliff changelog generation.
185
-
186
- The authenticated smoke task is intentionally separate because it sends real
187
- requests through the configured Codex account. Choose a model available to that
188
- account:
167
+ ## Contributing
189
168
 
190
- ```bash
191
- CODEX_MODEL=your-model bundle exec rake codex:smoke
192
- ```
169
+ See [CONTRIBUTING.md](CONTRIBUTING.md). By participating, you agree to follow
170
+ the [Code of Conduct](CODE_OF_CONDUCT.md). Security reports should follow
171
+ [SECURITY.md](SECURITY.md), not a public issue.
193
172
 
194
- When changing process lifecycle or shell execution, run that task inside a
195
- bounded process and memory scope.
173
+ ## License
196
174
 
197
- ### Git hooks
175
+ MIT License. See [LICENSE](LICENSE).
198
176
 
199
- This repository uses [lefthook](https://lefthook.dev/) and the Ruby
200
- [commitlint](https://github.com/arandilopez/commitlint) gem. Hooks reject direct
201
- commits to `main` or `master`, validate Conventional Commit messages, and run
202
- Standard Ruby.
177
+ ## Development and releases
203
178
 
204
- Install them once per clone:
179
+ Shared tooling uses published [Ethos Gemkit](https://github.com/ethos-link/ethos-gemkit).
180
+ Keep the exact Gemfile version and workflow commit pins on the same release.
205
181
 
206
- ```bash
182
+ ```sh
183
+ bundle install
207
184
  bundle exec lefthook install
185
+ bundle exec rake verify
208
186
  ```
209
187
 
210
- ## Release
211
-
212
- Releases are tag-driven. GitHub Actions runs the full test matrix, publishes
213
- through RubyGems trusted publishing, and creates the GitHub release from
214
- `CHANGELOG.md`. See [docs/releasing.md](docs/releasing.md) for repository setup,
215
- the first-release procedure, verification, and recovery guidance.
216
-
217
- Install [git-cliff](https://git-cliff.org/) locally, check out a clean `main`,
218
- and run one of:
219
-
220
- ```bash
221
- bundle exec rake 'release:prepare[patch]'
222
- bundle exec rake 'release:prepare[minor]'
223
- bundle exec rake 'release:prepare[major]'
224
- bundle exec rake 'release:prepare[0.2.0]'
225
- ```
226
-
227
- For releases after `0.1.0`, the task:
228
-
229
- 1. Prepends the next changelog section from Conventional Commits.
230
- 2. Updates `lib/ruby_llm/codex/version.rb`.
231
- 3. Commits the release files and creates `vX.Y.Z`.
232
- 4. Pushes `main` and the tag to `origin`.
233
-
234
- The initial `0.1.0` release only needs its existing version commit tagged after
235
- the GitHub environment and pending RubyGems trusted publisher are configured.
188
+ Verification uses a fake Codex executable; it makes no live model requests.
189
+ The optional `codex:smoke` task requires your Codex account, an available
190
+ `CODEX_MODEL` and a bounded process/memory scope.
236
191
 
237
- ## Contributing
238
-
239
- See [CONTRIBUTING.md](CONTRIBUTING.md). By participating, you agree to follow
240
- the [Code of Conduct](CODE_OF_CONDUCT.md). Security reports should follow
241
- [SECURITY.md](SECURITY.md), not a public issue.
242
-
243
- ## License
244
-
245
- MIT License. See [LICENSE](LICENSE).
192
+ From clean, current `main`, run `bundle exec rake 'release:prepare[patch]'`
193
+ (or `minor`, `major`, or an explicit version). Review and merge the generated
194
+ release PR to publish through Actions. Shared prerequisites, registry credentials
195
+ and failed-job recovery are in the [Gemkit release guide](https://github.com/ethos-link/ethos-gemkit#releasing-a-gem).
246
196
 
247
- ## About
197
+ ## Built by Ethos Link
248
198
 
249
- Made by the team at [Ethos Link](https://www.ethos-link.com) — practical
250
- software for growing businesses. We build tools for hospitality operators who
251
- need clear workflows, fast onboarding, and real human support.
199
+ [Ethos Link](https://www.ethos-link.com) builds business software and integrations
200
+ that replace manual handoffs with connected workflows. We help teams automate
201
+ routine work, use their data and improve the processes their business runs on.
252
202
 
253
- We also build [Reviato](https://www.reviato.com), “Capture. Interpret. Act.”.
254
- Turn guest feedback into clear next steps for your team. Collect private
255
- appraisals, spot patterns across reviews, and act before small issues turn into
256
- public ones.
203
+ - [Reviato](https://www.reviato.com): Read public review trends and collect
204
+ private customer feedback, so your team can decide what to improve.
205
+ - [EthosPlaces](https://www.ethos.place): Give customers the details they need to
206
+ choose and visit your business, from opening hours to directions.
207
+ - [Nuntio](https://www.nuntiohq.com): Draft, review and schedule social posts in
208
+ one workspace, then track each publishing result.
data/UPGRADE.md ADDED
@@ -0,0 +1,39 @@
1
+ # Upgrade Guide
2
+
3
+ ## 0.4.0
4
+
5
+ ### Shared development and release tooling
6
+
7
+ Use Ruby 4 and published Gemkit 0.2.0. Install the bundle and hooks, then run the
8
+ project's verification command from the README. Prepare releases from clean,
9
+ current main; merging the generated release PR publishes through Actions.
10
+ See the [Gemkit upgrade guide](https://github.com/ethos-link/ethos-gemkit/blob/main/UPGRADE.md)
11
+ for workflow permissions and registry setup.
12
+
13
+
14
+ ## RubyLLM 2.1
15
+
16
+ This provider now requires RubyLLM `~> 2.1.1` and Codex CLI 0.162.1 or newer.
17
+ RubyLLM 1.x and 2.0 are no longer supported. Upgrade the CLI with
18
+ `npm install -g @openai/codex` and check `codex --version`.
19
+
20
+ Host applications must:
21
+
22
+ - Replace `with_params(codex: ...)` with `with_provider_options(codex: ...)`.
23
+ - Replace `RubyLLM::Schema` with `Schematist::Schema`.
24
+ - Read structured results through `response.parsed`; `response.content` is
25
+ now a JSON string.
26
+ - Read the model ID from `response.model` and usage from `response.tokens`:
27
+ `input`, `output`, `cache_read`, and `thinking`. Input excludes cached tokens;
28
+ output includes thinking tokens. Unreported counts remain `nil`.
29
+
30
+ Plain Ruby consumers can update directly after adapting these calls. Rails
31
+ hosts using RubyLLM persistence must complete the
32
+ [2.0 upgrade](https://github.com/crmne/ruby_llm/blob/v2.0.0/docs/_reference/upgrading.md)
33
+ on 2.0, including cleanup, before following the
34
+ [2.1 upgrade](https://rubyllm.com/upgrading/). On Rails below 8.1.4, pin `json`
35
+ to `< 3` before updating.
36
+
37
+ Each Codex invocation records successful or failed usage attempts in RubyLLM.
38
+ Timeouts cover process exit and pipe draining, including descendants that keep
39
+ output pipes open after the CLI exits. Process-group cleanup remains bounded.
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RubyLLM
4
4
  module Codex
5
- VERSION = "0.2.1"
5
+ VERSION = "0.4.0"
6
6
  end
7
7
  end
@@ -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.144.5"
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:, params: {}, headers: {},
41
- schema: nil, thinking: nil, tool_prefs: nil, &stream)
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
- options = codex_options(params)
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
- run_codex(request, working_directory:, options:)
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(params)
113
- validate_params!(params)
114
- raw = params.is_a?(Hash) ? (params[:codex] || params["codex"] || {}) : {}
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 params must be a Hash"
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 validate_params!(params)
148
- unless params.is_a?(Hash)
149
- raise ArgumentError, "RubyLLM params must be a Hash"
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 = params.keys.reject { |key| key.to_s == "codex" }
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 params for the local Codex provider: #{unsupported.map(&:inspect).join(", ")}"
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 << writer = Thread.new do
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
- unless wait_thread.join(options[:timeout])
310
- terminate_process_group(wait_thread)
311
- raise TimeoutError, "Codex CLI timed out after #{options[:timeout]} seconds"
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
- return if wait_thread.join(2)
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.respond_to?(:effort) ? thinking.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.reverse.find { |item| %w[turn.failed error].include?(item["type"]) }
421
+ event = events.rfind { |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.reverse.find do |item|
429
+ event = events.rfind 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.reverse.find { |event| event["type"] == "turn.completed" }&.fetch("usage", {}) || {}
436
+ usage = events.rfind { |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
- model_id: model,
414
- input_tokens: uncached_input,
415
- cached_tokens: cached,
416
- output_tokens: integer_or_nil(usage["output_tokens"]),
417
- thinking_tokens: integer_or_nil(usage["reasoning_output_tokens"]),
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
- content = message.content
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
- "Raw provider content is not supported by the Codex comparison provider"
488
+ "Attachments are not supported by the Codex comparison provider"
466
489
  end
467
490
 
468
- content.respond_to?(:to_json) ? content.to_json : content.to_s
491
+ message.content
469
492
  end
470
493
 
471
494
  def present?(value)
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "ruby_llm"
4
- require "ruby_llm/schema"
5
4
  require_relative "ruby_llm/codex/version"
6
5
  require_relative "ruby_llm/providers/codex"
7
6
 
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.2.1
4
+ version: 0.4.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: '2.0'
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: '2.0'
26
+ version: 2.1.1
33
27
  - !ruby/object:Gem::Dependency
34
28
  name: minitest
35
29
  requirement: !ruby/object:Gem::Requirement
@@ -44,34 +38,6 @@ dependencies:
44
38
  - - "~>"
45
39
  - !ruby/object:Gem::Version
46
40
  version: '5'
47
- - !ruby/object:Gem::Dependency
48
- name: rake
49
- requirement: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - "~>"
52
- - !ruby/object:Gem::Version
53
- version: '13'
54
- type: :development
55
- prerelease: false
56
- version_requirements: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - "~>"
59
- - !ruby/object:Gem::Version
60
- version: '13'
61
- - !ruby/object:Gem::Dependency
62
- name: standard
63
- requirement: !ruby/object:Gem::Requirement
64
- requirements:
65
- - - "~>"
66
- - !ruby/object:Gem::Version
67
- version: '1.0'
68
- type: :development
69
- prerelease: false
70
- version_requirements: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - "~>"
73
- - !ruby/object:Gem::Version
74
- version: '1.0'
75
41
  description: |-
76
42
  A RubyLLM provider that invokes the official Codex CLI, reuses local ChatGPT
77
43
  authentication, and maps RubyLLM structured-output schemas to Codex output
@@ -85,6 +51,7 @@ files:
85
51
  - CHANGELOG.md
86
52
  - LICENSE
87
53
  - README.md
54
+ - UPGRADE.md
88
55
  - lib/ruby_llm-codex.rb
89
56
  - lib/ruby_llm/codex/version.rb
90
57
  - lib/ruby_llm/providers/codex.rb
@@ -108,14 +75,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
108
75
  requirements:
109
76
  - - ">="
110
77
  - !ruby/object:Gem::Version
111
- version: 3.3.0
78
+ version: '4.0'
112
79
  required_rubygems_version: !ruby/object:Gem::Requirement
113
80
  requirements:
114
81
  - - ">="
115
82
  - !ruby/object:Gem::Version
116
83
  version: '0'
117
84
  requirements: []
118
- rubygems_version: 4.0.16
85
+ rubygems_version: 4.0.20
119
86
  specification_version: 4
120
87
  summary: Use a local, ChatGPT-authenticated Codex CLI as a RubyLLM provider
121
88
  test_files: []