Class: AgentHarness::Providers::Codex

Inherits:
Base
  • Object
show all
Defined in:
lib/agent_harness/providers/codex.rb

Overview

OpenAI Codex CLI provider

Provides integration with the OpenAI Codex CLI tool.

Constant Summary collapse

SUPPORTED_CLI_VERSION =
"0.116.0"
SUPPORTED_CLI_REQUIREMENT =
Gem::Requirement.new(">= #{SUPPORTED_CLI_VERSION}", "< 0.117.0").freeze

Constants inherited from Base

Base::COMMON_ERROR_PATTERNS, Base::DEFAULT_SMOKE_TEST_CONTRACT

Instance Attribute Summary

Attributes inherited from Base

#config, #executor, #logger

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Base

#configure, #initialize, #sandboxed_environment?, #send_message

Methods included from Adapter

#auth_type, #build_mcp_flags, #fetch_mcp_servers, included, #parse_rate_limit_reset, #send_message, #smoke_test, #smoke_test_contract, #supported_mcp_transports, #supports_dangerous_mode?, #supports_mcp?, #validate_mcp_servers!

Constructor Details

This class inherits a constructor from AgentHarness::Providers::Base

Class Method Details

.available?Boolean

Returns:

  • (Boolean)


23
24
25
26
# File 'lib/agent_harness/providers/codex.rb', line 23

def available?
  executor = AgentHarness.configuration.command_executor
  !!executor.which(binary_name)
end

.binary_nameObject



19
20
21
# File 'lib/agent_harness/providers/codex.rb', line 19

def binary_name
  "codex"
end

.discover_modelsObject



48
49
50
51
52
53
54
# File 'lib/agent_harness/providers/codex.rb', line 48

def discover_models
  return [] unless available?

  [
    {name: "codex", family: "codex", tier: "standard", provider: "codex"}
  ]
end

.firewall_requirementsObject



28
29
30
31
32
33
34
35
36
# File 'lib/agent_harness/providers/codex.rb', line 28

def firewall_requirements
  {
    domains: [
      "api.openai.com",
      "openai.com"
    ],
    ip_ranges: []
  }
end

.installation_contractObject



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/agent_harness/providers/codex.rb', line 56

def installation_contract
  default_package = "@openai/codex@#{SUPPORTED_CLI_VERSION}".freeze
  install_command_prefix = ["npm", "install", "-g", "--ignore-scripts"].freeze
  install_command = (install_command_prefix + [default_package]).freeze
  supported_versions = [SUPPORTED_CLI_VERSION].freeze
  version_requirement = SUPPORTED_CLI_REQUIREMENT.requirements
    .map { |op, ver| "#{op} #{ver}".freeze }
    .freeze

  contract = {
    source: :npm,
    package: default_package,
    package_name: "@openai/codex",
    version: SUPPORTED_CLI_VERSION,
    version_requirement: version_requirement,
    binary_name: binary_name,
    install_command_prefix: install_command_prefix,
    install_command: install_command,
    supported_versions: supported_versions
  }

  contract.each_value do |value|
    value.freeze if value.is_a?(String)
  end
  contract.freeze
end

.instruction_file_pathsObject



38
39
40
41
42
43
44
45
46
# File 'lib/agent_harness/providers/codex.rb', line 38

def instruction_file_paths
  [
    {
      path: "AGENTS.md",
      description: "OpenAI Codex agent instructions",
      symlink: false
    }
  ]
end

.provider_nameObject



15
16
17
# File 'lib/agent_harness/providers/codex.rb', line 15

def provider_name
  :codex
end

.smoke_test_contractObject



83
84
85
# File 'lib/agent_harness/providers/codex.rb', line 83

def smoke_test_contract
  Base::DEFAULT_SMOKE_TEST_CONTRACT
end

Instance Method Details

#auth_statusObject



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/agent_harness/providers/codex.rb', line 154

def auth_status
  api_key = ENV["OPENAI_API_KEY"]
  if api_key && !api_key.strip.empty?
    if api_key.strip.start_with?("sk-")
      return {valid: true, expires_at: nil, error: nil, auth_method: :api_key}
    else
      return {valid: false, expires_at: nil, error: "OPENAI_API_KEY is set but does not appear to be a valid OpenAI API key"}
    end
  end

  credentials = read_codex_credentials
  if credentials
    key = credentials["api_key"] || credentials["apiKey"] || credentials["OPENAI_API_KEY"]
    if key.is_a?(String) && !key.strip.empty?
      if key.strip.start_with?("sk-")
        return {valid: true, expires_at: nil, error: nil, auth_method: :config_file}
      else
        return {valid: false, expires_at: nil, error: "Config file API key is set but does not appear to be a valid OpenAI API key"}
      end
    end
  end

  {valid: false, expires_at: nil, error: "No OpenAI API key found. Set OPENAI_API_KEY or configure in #{codex_config_path}"}
rescue IOError, JSON::ParserError => e
  {valid: false, expires_at: nil, error: e.message}
end

#capabilitiesObject



104
105
106
107
108
109
110
111
112
113
114
# File 'lib/agent_harness/providers/codex.rb', line 104

def capabilities
  {
    streaming: false,
    file_upload: false,
    vision: false,
    tool_use: true,
    json_mode: false,
    mcp: false,
    dangerous_mode: true
  }
end

#configuration_schemaObject



96
97
98
99
100
101
102
# File 'lib/agent_harness/providers/codex.rb', line 96

def configuration_schema
  {
    fields: [],
    auth_modes: [:api_key],
    openai_compatible: true
  }
end

#dangerous_mode_flagsObject



116
117
118
# File 'lib/agent_harness/providers/codex.rb', line 116

def dangerous_mode_flags
  ["--full-auto"]
end

#display_nameObject



92
93
94
# File 'lib/agent_harness/providers/codex.rb', line 92

def display_name
  "OpenAI Codex CLI"
end

#error_patternsObject



142
143
144
145
146
147
148
149
150
151
152
# File 'lib/agent_harness/providers/codex.rb', line 142

def error_patterns
  COMMON_ERROR_PATTERNS.merge(
    auth_expired: COMMON_ERROR_PATTERNS[:auth_expired] + [/401/, /incorrect.*api.*key/i],
    transient: COMMON_ERROR_PATTERNS[:transient] + [/connection.*reset/i],
    sandbox_failure: [
      /bwrap.*no permissions/i,
      /no permissions to create a new namespace/i,
      /unprivileged.*namespace/i
    ]
  )
end

#execution_semanticsObject



120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/agent_harness/providers/codex.rb', line 120

def execution_semantics
  {
    prompt_delivery: :arg,
    output_format: :text,
    sandbox_aware: true,
    uses_subcommand: true,
    non_interactive_flag: nil,
    legitimate_exit_codes: [0],
    stderr_is_diagnostic: true,
    parses_rate_limit_reset: false
  }
end

#health_statusObject



181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/agent_harness/providers/codex.rb', line 181

def health_status
  unless self.class.available?
    return {healthy: false, message: "Codex CLI not found in PATH. Install from https://github.com/openai/codex"}
  end

  auth = auth_status
  unless auth[:valid]
    return {healthy: false, message: auth[:error]}
  end

  {healthy: true, message: "Codex CLI available and authenticated"}
end

#nameObject



88
89
90
# File 'lib/agent_harness/providers/codex.rb', line 88

def name
  "codex"
end

#session_flags(session_id) ⇒ Object



137
138
139
140
# File 'lib/agent_harness/providers/codex.rb', line 137

def session_flags(session_id)
  return [] unless session_id && !session_id.empty?
  ["--session", session_id]
end

#supports_sessions?Boolean

Returns:

  • (Boolean)


133
134
135
# File 'lib/agent_harness/providers/codex.rb', line 133

def supports_sessions?
  true
end

#validate_configObject



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/agent_harness/providers/codex.rb', line 194

def validate_config
  errors = []

  flags = @config.default_flags
  unless flags.nil?
    if flags.is_a?(Array)
      invalid = flags.reject { |f| f.is_a?(String) }
      errors << "default_flags contains non-string values" if invalid.any?
    else
      errors << "default_flags must be an array of strings"
    end
  end

  {valid: errors.empty?, errors: errors}
end