Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Add a local Codex CLI provider for RubyLLM plain-text and structured-output
experiments.
requests.
- Add isolated execution defaults, timeout handling, and process-group cleanup.
- Add live smoke coverage for plain-text and structured-output requests.
- Add CI, Conventional Commit checks, changelog validation, and tag-driven
Expand All @@ -24,3 +24,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
during completion-only runs.
- Align the documented Ruby requirement, CI matrix, syntax target, and locked
development dependencies on Ruby 3.3 or newer.
- Generalize documentation examples and live smoke configuration for public
reuse.
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ bundle exec lefthook install
bundle exec rake
```

The authenticated smoke test is separate because it invokes the real Codex CLI
and consumes subscription allowance:
The authenticated smoke test is separate because it invokes the real Codex CLI.
Choose a model available to your configured account:

```bash
bundle exec rake codex:smoke
CODEX_MODEL=your-model bundle exec rake codex:smoke
```

Run it inside an appropriate process and memory boundary when changing process
Expand Down
58 changes: 34 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,17 @@
[![Gem Version](https://badge.fury.io/rb/ruby_llm-codex.svg)](https://rubygems.org/gems/ruby_llm-codex)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

This small adapter lets RubyLLM invoke the official local Codex CLI as a
provider. Codex reuses the ChatGPT login stored by `codex login`, so these runs
consume your Codex subscription allowance rather than OpenAI API credits.
This adapter lets RubyLLM invoke the official local Codex CLI as a provider. It
supports text completions and structured output through RubyLLM's familiar chat
interface.

The intended use is controlled prompt comparison, especially structured-output
experiments. It is not intended to turn a personal ChatGPT subscription into a
public API.
Authentication is handled by the Codex CLI using the account configured by
`codex login`; the gem does not accept or store an OpenAI API key.

This measures **Codex harness output**, not a raw model API response. Codex adds
its own base agent instructions and runtime behavior. RubyLLM system messages
are layered in as Codex developer instructions, which is the closest supported
mapping but not an identical wire-level experiment.
Responses come from the **Codex agent harness**, not directly from a raw model
API. Codex applies its own base instructions and runtime behavior. RubyLLM system
messages are passed as Codex developer instructions, which is the closest
supported mapping but not an identical wire-level request.

## Requirements

Expand Down Expand Up @@ -56,16 +55,15 @@ Require the gem and use Codex as an explicit RubyLLM provider:
```ruby
require "ruby_llm-codex"

class ResultSchema < RubyLLM::Schema
class SummarySchema < RubyLLM::Schema
string :summary
integer :score
end

response = RubyLLM.chat(model: "gpt-5.6-luna", provider: :codex)
.with_instructions("Evaluate the supplied text consistently.")
.with_schema(ResultSchema)
response = RubyLLM.chat(model: ENV.fetch("CODEX_MODEL"), provider: :codex)
.with_instructions("Return a concise summary.")
.with_schema(SummarySchema)
.with_thinking(effort: :medium)
.ask("The text to evaluate")
.ask("The text to summarize")

pp response.content
```
Expand All @@ -90,7 +88,7 @@ By default every request:
- passes RubyLLM's JSON schema through `--output-schema`.

This prevents local `AGENTS.md`, plugins, MCP servers, repository contents, and
saved session history from quietly affecting a comparison.
saved session history from quietly affecting a request.

## Configuration

Expand All @@ -101,7 +99,7 @@ To deliberately let Codex inspect a repository or use selected configuration:
```ruby
chat.with_params(
codex: {
working_directory: Rails.root.to_s,
working_directory: "/path/to/project",
sandbox: "read-only",
ignore_user_config: false,
ephemeral: true,
Expand Down Expand Up @@ -143,9 +141,9 @@ instructions. Those settings, plus `features.shell_snapshot` and
execution backend for your application.
- Temperature is rejected because this Codex path does not expose it.
- Fresh `codex exec` process per RubyLLM completion. This prioritizes isolation
and simple comparisons over throughput.
and predictable behavior over throughput.
- Multi-turn RubyLLM histories are serialized into a role-labelled transcript.
One-shot `with_instructions(...).ask(...)` experiments have the cleanest
One-shot `with_instructions(...).ask(...)` requests have the most direct
semantic mapping.

If you later need streaming, persistent threads, or high throughput, replace
Expand All @@ -162,7 +160,7 @@ client while keeping the same RubyLLM provider surface.
explicitly disables Codex shell snapshots, which are unnecessary for its
completion-only execution and previously caused runaway Bash processes.
- **Local instructions affect output:** keep `ignore_user_config: true` and do
not set a working directory when you need isolated comparisons.
not set a working directory when you need isolated requests.

## Development

Expand All @@ -185,11 +183,12 @@ The default Rake task runs the unit suite and Standard Ruby. CI runs the same
gate on Ruby 3.3, 3.4, and 4.0.3, then validates commit messages and
git-cliff changelog generation.

The authenticated smoke task is intentionally separate because it consumes
Codex subscription allowance:
The authenticated smoke task is intentionally separate because it sends real
requests through the configured Codex account. Choose a model available to that
account:

```bash
bundle exec rake codex:smoke
CODEX_MODEL=your-model bundle exec rake codex:smoke
```

When changing process lifecycle or shell execution, run that task inside a
Expand Down Expand Up @@ -244,3 +243,14 @@ the [Code of Conduct](CODE_OF_CONDUCT.md). Security reports should follow
## License

MIT License. See [LICENSE](LICENSE).

## About

Made by the team at [Ethos Link](https://www.ethos-link.com) — practical
software for growing businesses. We build tools for hospitality operators who
need clear workflows, fast onboarding, and real human support.

We also build [Reviato](https://www.reviato.com), “Capture. Interpret. Act.”.
Turn guest feedback into clear next steps for your team. Collect private
appraisals, spot patterns across reviews, and act before small issues turn into
public ones.
3 changes: 2 additions & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,8 @@ namespace :codex do
desc "Run live plain-text and structured-output smoke tests through Codex CLI"
task :smoke do
cli_path = ENV.fetch("CODEX_BIN", "codex")
model = ENV.fetch("CODEX_MODEL", "gpt-5.4")
model = ENV["CODEX_MODEL"]
raise "Set CODEX_MODEL to a model available in your Codex account" if model.nil? || model.empty?
timeout = Float(ENV.fetch("CODEX_TIMEOUT", RubyLLM::Providers::Codex::DEFAULT_TIMEOUT))
version_output, version_status = Open3.capture2e(cli_path, "--version")
raise "Could not run #{cli_path.inspect}: #{version_output.strip}" unless version_status.success?
Expand Down
6 changes: 3 additions & 3 deletions ruby_llm-codex.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ Gem::Specification.new do |spec|
spec.email = ["devel@ethos-link.com"]
spec.summary = "Use a local, ChatGPT-authenticated Codex CLI as a RubyLLM provider"
spec.description = <<~TEXT.strip
A small RubyLLM provider for controlled model comparisons. It invokes the
official Codex CLI, reuses local ChatGPT authentication, and maps RubyLLM
structured-output schemas to Codex output schemas.
A RubyLLM provider that invokes the official Codex CLI, reuses local ChatGPT
authentication, and maps RubyLLM structured-output schemas to Codex output
schemas.
TEXT
spec.homepage = "https://github.com/ethos-link/ruby_llm-codex"
spec.license = "MIT"
Expand Down
4 changes: 2 additions & 2 deletions test/codex_provider_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def test_provider_options_are_forwarded_with_explicit_precedence
.with_params(
codex: {
working_directory: @directory,
profile: "experiment",
profile: "custom-profile",
codex_home:,
sandbox: "read-only",
ignore_user_config: false,
Expand All @@ -122,7 +122,7 @@ def test_provider_options_are_forwarded_with_explicit_precedence

assert_equal @directory, invocation.fetch("working_directory")
assert_equal File.expand_path(codex_home), invocation.fetch("codex_home")
assert_equal "experiment", invocation.fetch("profile")
assert_equal "custom-profile", invocation.fetch("profile")
assert_equal "read-only", invocation.fetch("sandbox")
refute invocation.fetch("ignore_user_config")
refute invocation.fetch("ephemeral")
Expand Down