Class: AgentHarness::Providers::Codex
Overview
OpenAI Codex CLI provider
Provides integration with the OpenAI Codex CLI tool.
Defined Under Namespace
Classes: StreamingEvent
Constant Summary
collapse
- SUPPORTED_CLI_VERSION =
"0.122.0"
- SUPPORTED_CLI_REQUIREMENT =
Gem::Requirement.new(">= #{SUPPORTED_CLI_VERSION}", "< 0.123.0").freeze
- DEFAULT_COMPATIBLE_MODEL_ID =
Default model recommended by the Codex runner contract when callers
have no explicit preference. Used as the ModelCompatibility::Result#fallback_model_id
for unsupported/unknown model lookups so downstream orchestrators
(smoke tests, tier fallback) have a stable, contract-backed choice
instead of relying on whichever model the CLI's default points at.
"gpt-5-codex"
- MODEL_COMPATIBILITY_FACTS =
Known CLI-gated model facts. Each entry expresses the minimum Codex
CLI version required to drive that model. Keep entries here only when
the requirement is durable runner contract knowledge — not
provider-side experiments or one-off CLI defaults.
The gpt-5.5 entry tracks the failure class observed in
viamin/agent-harness#245 and viamin/agent-harness#250: older Codex
CLI builds (e.g. 0.115.x) could not drive the gpt-5.5 family.
{
"gpt-5.5" => {minimum_cli_version: "0.116.0"},
"gpt-5.5-codex" => {minimum_cli_version: "0.116.0"}
}.each_value(&:freeze).freeze
- BASELINE_SUPPORTED_MODELS =
Models that the runner contract considers supported on every Codex
CLI release we ship. Used by model_compatibility so callers can
distinguish "unknown to the contract" from "explicitly supported."
%w[
gpt-5
gpt-5-codex
gpt-5-mini
gpt-4o
gpt-4o-mini
o4-mini
].freeze
- SUPPORTED_AUTH_MODES =
Auth modes the Codex runner accepts. Compatibility checks for an
unrecognised auth mode return :auth_mode_not_supported rather than
silently approving the request.
%i[api_key subscription].freeze
- OAUTH_REFRESH_FAILURE_PATTERNS =
[
/refresh_token_reused/i,
/failed to refresh token\b.*\b401\b/im,
/failed to refresh token\b.*unauthorized/im,
/failed to refresh token\b.*\binvalid_client\b/im,
/failed to refresh token\b.*\binvalid_grant\b/im,
/failed to refresh token\b.*invalid.*refresh.*token/im,
/failed to refresh token\b.*refresh.*token.*invalid/im,
/your access token could not be refreshed because\b.*\b401\b/im,
/your access token could not be refreshed because\b.*unauthorized/im,
/your access token could not be refreshed because\b.*\binvalid_client\b/im,
/your access token could not be refreshed because\b.*\binvalid_grant\b/im,
/your access token could not be refreshed because\b.*invalid.*refresh.*token/im,
/your access token could not be refreshed because\b.*refresh.*token.*invalid/im,
/your access token could not be refreshed because\s+your refresh token .*already (?:been )?used/im,
/refresh token .*already (?:been )?used/im
].freeze
- OAUTH_REFRESH_TRANSIENT_PATTERNS =
[
/your access token could not be refreshed because\s+(?:the\s+)?auth(?:entication)? service(?:\s+(?:is|was))?\s+(?:temporarily\s+)?unavailable/im,
/your access token could not be refreshed because .*connection.*error/im,
/failed to refresh token\b.*connection.*error/im,
/failed to refresh token\b.*service(?:\s+(?:is|was))?\s+(?:temporarily\s+)?unavailable/im
].freeze
- SHARED_OUTPUT_ERROR_PATTERNS =
{
quota_exceeded: [
/free tier limit reached/i,
/please upgrade to a paid plan/i,
/quota.*exceeded/i,
/insufficient.*quota/i,
/billing/i
],
rate_limited: [
/rate.?limit/i,
/too.?many.?requests/i,
/\b429\b/
],
auth_expired: [
/authentication_error/i,
/invalid_grant/i,
/Token is expired or invalid/i,
/unauthorized/i
],
sandbox_failure: [
/bwrap.*no permissions/i,
/no permissions to create a new namespace/i,
/unprivileged.*namespace/i
],
transient_error: [
/timeout/i,
/connection.*error/i,
/service.*unavailable/i,
/\b503\b/,
/\b502\b/,
/connection.*reset/i
]
}.tap { |h| h.each_value(&:freeze) }.freeze
- STDOUT_ERROR_PATTERNS =
SHARED_OUTPUT_ERROR_PATTERNS.merge(
auth_expired: [
/authentication_error/i,
/invalid_grant/i,
/Token is expired or invalid/i,
/unauthorized/i
]
).tap { |h| h.each_value(&:freeze) }.freeze
- STDERR_ERROR_PATTERNS =
SHARED_OUTPUT_ERROR_PATTERNS.merge(
auth_expired: OAUTH_REFRESH_FAILURE_PATTERNS + [
/invalid.*api.*key/i,
/unauthorized/i,
/authentication_error/i,
/invalid_grant/i,
/Token is expired or invalid/i,
/\b401\b/,
/incorrect.*api.*key/i
],
transient_error: OAUTH_REFRESH_TRANSIENT_PATTERNS + SHARED_OUTPUT_ERROR_PATTERNS[:transient_error]
).tap { |h| h.each_value(&:freeze) }.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
#cleanup_mcp_tempfiles!, #write_mcp_config_file
#parse_rate_limit_reset
Methods inherited from Base
#configure, #initialize, #parse_container_output, #parse_test_error, #plan_execution, #sandboxed_environment?, #send_chat_message
Methods included from Adapter
#auth_type, #chat_transport, #chat_transport_type, #fetch_mcp_servers, #heartbeat_integration, included, metadata_package_name, #noisy_error_patterns, normalize_metadata_installation, normalize_metadata_source_type, normalize_metadata_version_requirement, #parse_rate_limit_reset, #plan_execution, #smoke_test, #smoke_test_contract, #supports_activity_heartbeat?, #supports_chat?, #supports_dangerous_mode?, #supports_message_tool_injection?, #supports_text_mode?, #supports_token_counting?, #supports_tool_control?, #validate_mcp_servers!
Class Method Details
.binary_name ⇒ Object
144
145
146
|
# File 'lib/agent_harness/providers/codex.rb', line 144
def binary_name
"codex"
end
|
.classify_output_chunk(text, stream:, stdout_buffer: nil) ⇒ nil, Hash
Classify a chunk of output text from the provider CLI in real-time
Can be called during streaming to classify both stdout and stderr
chunks as they arrive. For stdout, attempts to parse JSONL events
and extract error information from structured output.
Because CommandExecutor reads arbitrary 4096-byte chunks, a single
JSONL event may be split across consecutive calls. Pass a String
buffer via stdout_buffer that persists across calls so incomplete
trailing lines are re-assembled before parsing.
165
166
167
168
169
170
171
172
173
174
|
# File 'lib/agent_harness/providers/codex.rb', line 165
def classify_output_chunk(text, stream:, stdout_buffer: nil)
return nil if text.nil? || text.strip.empty?
case normalize_output_stream(stream)
when :stdout
classify_stdout_chunk(text, stdout_buffer)
when :stderr
classify_stderr_chunk(text)
end
end
|
.comparable_cli_version(value) ⇒ Object
Coerce normalized CLI version into a Gem::Version usable for
requirement comparison. Returns nil when the value is missing or
cannot be parsed — callers treat that as "no installed-version
signal," not as a failure.
394
395
396
397
398
399
400
401
402
403
404
405
406
|
# File 'lib/agent_harness/providers/codex.rb', line 394
def comparable_cli_version(value)
return value if value.is_a?(Gem::Version)
return nil if value.nil?
str = value.respond_to?(:strip) ? value.strip : value.to_s
return nil if str.empty?
begin
Gem::Version.new(str)
rescue ArgumentError
nil
end
end
|
.default_compatible_model_id ⇒ Object
.discover_models ⇒ Object
210
211
212
213
214
215
216
|
# File 'lib/agent_harness/providers/codex.rb', line 210
def discover_models
return [] unless available?
[
{name: "codex", family: "codex", tier: "standard", provider: "codex"}
]
end
|
.firewall_requirements ⇒ Object
190
191
192
193
194
195
196
197
198
|
# File 'lib/agent_harness/providers/codex.rb', line 190
def firewall_requirements
{
domains: [
"api.openai.com",
"openai.com"
],
ip_ranges: []
}
end
|
.installation_contract(version: SUPPORTED_CLI_VERSION) ⇒ Object
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
|
# File 'lib/agent_harness/providers/codex.rb', line 218
def installation_contract(version: SUPPORTED_CLI_VERSION)
version = version.strip if version.respond_to?(:strip)
unless version.is_a?(String) && !version.empty?
raise ArgumentError,
"Unsupported Codex CLI version #{version.inspect}; " \
"supported versions must satisfy #{SUPPORTED_CLI_REQUIREMENT}"
end
parsed_version = begin
Gem::Version.new(version)
rescue ArgumentError
raise ArgumentError,
"Unsupported Codex CLI version #{version.inspect}; " \
"supported versions must satisfy #{SUPPORTED_CLI_REQUIREMENT}"
end
unless SUPPORTED_CLI_REQUIREMENT.satisfied_by?(parsed_version)
raise ArgumentError,
"Unsupported Codex CLI version #{version.inspect}; " \
"supported versions must satisfy #{SUPPORTED_CLI_REQUIREMENT}"
end
default_package = "@openai/codex@#{version}".freeze
install_command_prefix = ["npm", "install", "-g", "--ignore-scripts"].freeze
install_command = (install_command_prefix + [default_package]).freeze
supported_versions = [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: 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_paths ⇒ Object
200
201
202
203
204
205
206
207
208
|
# File 'lib/agent_harness/providers/codex.rb', line 200
def instruction_file_paths
[
{
path: "AGENTS.md",
description: "OpenAI Codex agent instructions",
symlink: false
}
]
end
|
Structured Codex compatibility contract.
Returns an ModelCompatibility::Result for the
combination of model_id, auth_mode, and installed
cli_version. The contract surfaces three concrete outcomes:
- Supported — the model is in BASELINE_SUPPORTED_MODELS (or
a known CLI-gated model whose minimum CLI version is met).
- Unsupported due to CLI version — the model is known to need
a newer Codex CLI than was supplied; the result carries
:minimum_cli_version so callers can act on it.
- Unknown / dynamic — the model is not in this static
contract. Callers must treat this as "ask the provider" rather
than as approval.
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
|
# File 'lib/agent_harness/providers/codex.rb', line 294
def model_compatibility(model_id:, auth_mode: nil, cli_version: nil)
normalized_model_id = model_id.to_s
normalized_auth_mode = auth_mode&.to_sym
normalized_cli_version = normalize_cli_version_for_compatibility(cli_version)
if normalized_auth_mode && !SUPPORTED_AUTH_MODES.include?(normalized_auth_mode)
return AgentHarness::ModelCompatibility.build_result(
runner: provider_name,
model_id: normalized_model_id,
auth_mode: normalized_auth_mode,
cli_version: normalized_cli_version,
supported: false,
reason: AgentHarness::ModelCompatibility::UNSUPPORTED_AUTH_MODE_REASON,
fallback_model_id: DEFAULT_COMPATIBLE_MODEL_ID,
source: :static_contract,
details: {supported_auth_modes: SUPPORTED_AUTH_MODES}
)
end
gated_fact = MODEL_COMPATIBILITY_FACTS[normalized_model_id]
if gated_fact
minimum_version = gated_fact[:minimum_cli_version]
requirement = Gem::Requirement.new(">= #{minimum_version}")
comparable_version = comparable_cli_version(normalized_cli_version)
if comparable_version.nil?
return AgentHarness::ModelCompatibility.unknown_result(
runner: provider_name,
model_id: normalized_model_id,
auth_mode: normalized_auth_mode,
cli_version: normalized_cli_version,
reason: AgentHarness::ModelCompatibility::UNKNOWN_CLI_VERSION_REASON,
minimum_cli_version: minimum_version,
cli_version_requirement: requirement.to_s,
fallback_model_id: DEFAULT_COMPATIBLE_MODEL_ID,
source: :static_contract
)
end
unless requirement.satisfied_by?(comparable_version)
return AgentHarness::ModelCompatibility.build_result(
runner: provider_name,
model_id: normalized_model_id,
auth_mode: normalized_auth_mode,
cli_version: normalized_cli_version,
supported: false,
reason: AgentHarness::ModelCompatibility::UNSUPPORTED_CLI_VERSION_REASON,
minimum_cli_version: minimum_version,
cli_version_requirement: requirement.to_s,
fallback_model_id: DEFAULT_COMPATIBLE_MODEL_ID,
source: :static_contract
)
end
return AgentHarness::ModelCompatibility.build_result(
runner: provider_name,
model_id: normalized_model_id,
auth_mode: normalized_auth_mode,
cli_version: normalized_cli_version,
supported: true,
reason: AgentHarness::ModelCompatibility::SUPPORTED_REASON,
minimum_cli_version: minimum_version,
cli_version_requirement: requirement.to_s,
source: :static_contract
)
end
if BASELINE_SUPPORTED_MODELS.include?(normalized_model_id)
return AgentHarness::ModelCompatibility.build_result(
runner: provider_name,
model_id: normalized_model_id,
auth_mode: normalized_auth_mode,
cli_version: normalized_cli_version,
supported: true,
reason: AgentHarness::ModelCompatibility::SUPPORTED_REASON,
source: :static_contract
)
end
AgentHarness::ModelCompatibility.unknown_result(
runner: provider_name,
model_id: normalized_model_id,
auth_mode: normalized_auth_mode,
cli_version: normalized_cli_version,
reason: AgentHarness::ModelCompatibility::UNKNOWN_MODEL_REASON,
fallback_model_id: DEFAULT_COMPATIBLE_MODEL_ID
)
end
|
.parse_cli_jsonl_transcript(raw_output, max_events: nil) ⇒ Object
408
409
410
411
412
413
414
|
# File 'lib/agent_harness/providers/codex.rb', line 408
def parse_cli_jsonl_transcript(raw_output, max_events: nil)
return parser_instance.send(:parse_jsonl_output, "") if max_events && max_events <= 0
output = max_events ? tail_nonempty_lines(raw_output, limit: max_events).join("\n") : raw_output
parser_instance.send(:parse_jsonl_output, output)
end
|
.parse_streaming_event(line) ⇒ Object
Parse a single Codex JSONL event as it arrives on stdout and classify it
for real-time progress tracking. Returns nil for malformed JSON, scalar
JSON values, plain-text output, or unsupported event types.
419
420
421
422
423
424
425
426
|
# File 'lib/agent_harness/providers/codex.rb', line 419
def parse_streaming_event(line)
event = JSON.parse(line.to_s)
return unless event.is_a?(Hash)
parser_instance.send(:build_streaming_event, event)
rescue JSON::ParserError, TypeError
nil
end
|
181
182
183
184
185
186
187
188
|
# File 'lib/agent_harness/providers/codex.rb', line 181
def provider_metadata_overrides
{
auth: {
service: :openai,
api_family: :openai
}
}
end
|
.provider_name ⇒ Object
140
141
142
|
# File 'lib/agent_harness/providers/codex.rb', line 140
def provider_name
:codex
end
|
.smoke_test_contract ⇒ Object
Instance Method Details
#api_key_env_var_names ⇒ Object
594
|
# File 'lib/agent_harness/providers/codex.rb', line 594
def api_key_env_var_names = ["OPENAI_API_KEY"]
|
#api_key_unset_vars ⇒ Object
596
|
# File 'lib/agent_harness/providers/codex.rb', line 596
def api_key_unset_vars = ["OPENAI_BASE_URL", "OPENAI_HEADER_X_AGENT_RUN_ID", "OPENAI_HEADER_X_PROXY_TOKEN"]
|
#auth_lock_config ⇒ Object
791
792
793
|
# File 'lib/agent_harness/providers/codex.rb', line 791
def auth_lock_config
{path: "/tmp/codex-auth.lock", timeout: 30}
end
|
#auth_status ⇒ Object
714
715
716
|
# File 'lib/agent_harness/providers/codex.rb', line 714
def auth_status
auth_status_for_env({})
end
|
#build_mcp_flags(mcp_servers, working_dir: nil) ⇒ Object
616
617
618
619
620
621
|
# File 'lib/agent_harness/providers/codex.rb', line 616
def build_mcp_flags(mcp_servers, working_dir: nil)
return [] if mcp_servers.empty?
config_path = write_mcp_config_file(mcp_servers, working_dir: working_dir)
["--mcp-config", config_path]
end
|
#capabilities ⇒ Object
582
583
584
585
586
587
588
589
590
591
592
|
# File 'lib/agent_harness/providers/codex.rb', line 582
def capabilities
{
streaming: false,
file_upload: false,
vision: false,
tool_use: true,
json_mode: false,
mcp: true,
dangerous_mode: true
}
end
|
#cli_env_overrides ⇒ Object
600
|
# File 'lib/agent_harness/providers/codex.rb', line 600
def cli_env_overrides = {"PAID_CODEX_SUBSCRIPTION_AUTH" => "1"}
|
#config_file_content(options = {}) ⇒ Object
773
774
775
776
777
778
779
780
781
|
# File 'lib/agent_harness/providers/codex.rb', line 773
def config_file_content(options = {})
<<~TOML
[chatgpt]
model_provider = "#{escape_toml_string(options[:model_provider])}"
base_url = "#{escape_toml_string(options[:base_url])}"
env_key = "#{escape_toml_string(options[:env_key])}"
wire_api = "#{escape_toml_string(options[:wire_api])}"
TOML
end
|
#configuration_schema ⇒ Object
574
575
576
577
578
579
580
|
# File 'lib/agent_harness/providers/codex.rb', line 574
def configuration_schema
{
fields: [],
auth_modes: [:api_key],
openai_compatible: true
}
end
|
#dangerous_mode_flags ⇒ Object
627
628
629
|
# File 'lib/agent_harness/providers/codex.rb', line 627
def dangerous_mode_flags
["--full-auto"]
end
|
#display_name ⇒ Object
570
571
572
|
# File 'lib/agent_harness/providers/codex.rb', line 570
def display_name
"OpenAI Codex CLI"
end
|
#error_classification_patterns ⇒ Object
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
|
# File 'lib/agent_harness/providers/codex.rb', line 686
def error_classification_patterns
super.merge(
auth_expired: [
/refresh_token_reused/i,
/refresh token has already been used/i,
/Please log out and sign in again/i,
/authentication_error/i,
/invalid_grant/i,
/Token is expired or invalid/i
],
abort: [
/free tier limit reached/i,
/please upgrade to a paid plan/i,
/bwrap.*no permissions/i,
/no permissions to create a new namespace/i,
/unprivileged.*namespace/i
]
)
end
|
#error_patterns ⇒ Object
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
|
# File 'lib/agent_harness/providers/codex.rb', line 663
def error_patterns
{
rate_limited: COMMON_ERROR_PATTERNS[:rate_limited],
timeout: [
/your access token could not be refreshed.*(?:timeout|timed.?out)/im,
/failed to refresh token\b.*(?:timeout|timed.?out)/im
],
transient: COMMON_ERROR_PATTERNS[:transient] + [
/connection.*reset/i
] + OAUTH_REFRESH_TRANSIENT_PATTERNS,
auth_expired: COMMON_ERROR_PATTERNS[:auth_expired] + [
/\b401\b/,
/incorrect.*api.*key/i
] + OAUTH_REFRESH_FAILURE_PATTERNS,
quota_exceeded: COMMON_ERROR_PATTERNS[:quota_exceeded],
sandbox_failure: [
/bwrap.*no permissions/i,
/no permissions to create a new namespace/i,
/unprivileged.*namespace/i
]
}
end
|
#execution_semantics ⇒ Object
641
642
643
644
645
646
647
648
649
650
651
652
|
# File 'lib/agent_harness/providers/codex.rb', line 641
def execution_semantics
{
prompt_delivery: :arg,
output_format: :json,
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_status ⇒ Object
718
719
720
721
722
723
724
725
726
727
728
729
|
# File 'lib/agent_harness/providers/codex.rb', line 718
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
|
#name ⇒ Object
566
567
568
|
# File 'lib/agent_harness/providers/codex.rb', line 566
def name
"codex"
end
|
#notify_hook_content ⇒ Object
783
784
785
786
787
788
789
|
# File 'lib/agent_harness/providers/codex.rb', line 783
def notify_hook_content
<<~TOML
[notify]
# Paid notification hook
TOML
end
|
#preflight_check(env:, timeout: 10) ⇒ Object
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
|
# File 'lib/agent_harness/providers/codex.rb', line 731
def preflight_check(env:, timeout: 10)
auth = auth_status_for_env(env)
return {healthy: false, reason: auth[:error], error_category: :authentication} unless auth[:valid]
version = codex_cli_version(env: env, timeout: timeout)
unless version
return {
healthy: false,
reason: "Codex CLI version check failed. Ensure 'codex' is installed and available in PATH.",
error_category: :installation
}
end
unless SUPPORTED_CLI_REQUIREMENT.satisfied_by?(version)
return {
healthy: false,
reason: "Unsupported Codex CLI version #{version}. Expected #{SUPPORTED_CLI_REQUIREMENT}.",
error_category: :installation
}
end
check_base_url_reachability(env: env, timeout: timeout)
rescue => e
{healthy: false, reason: "Codex preflight failed: #{e.message}"}
end
|
#send_message(prompt:, **options) ⇒ Object
602
603
604
605
606
|
# File 'lib/agent_harness/providers/codex.rb', line 602
def send_message(prompt:, **options)
super
ensure
cleanup_mcp_tempfiles!
end
|
#session_flags(session_id) ⇒ Object
658
659
660
661
|
# File 'lib/agent_harness/providers/codex.rb', line 658
def session_flags(session_id)
return [] unless session_id && !session_id.empty?
["--session", session_id]
end
|
#subscription_unset_vars ⇒ Object
598
|
# File 'lib/agent_harness/providers/codex.rb', line 598
def subscription_unset_vars = ["OPENAI_API_KEY", "OPENAI_BASE_URL"] + api_key_unset_vars
|
#supported_mcp_transports ⇒ Object
612
613
614
|
# File 'lib/agent_harness/providers/codex.rb', line 612
def supported_mcp_transports
%w[stdio http sse]
end
|
#supports_mcp? ⇒ Boolean
608
609
610
|
# File 'lib/agent_harness/providers/codex.rb', line 608
def supports_mcp?
true
end
|
#supports_sessions? ⇒ Boolean
654
655
656
|
# File 'lib/agent_harness/providers/codex.rb', line 654
def supports_sessions?
true
end
|
#test_command_overrides ⇒ Object
623
624
625
|
# File 'lib/agent_harness/providers/codex.rb', line 623
def test_command_overrides
["--skip-git-repo-check", "--output-last-message", "/tmp/codex-smoke-output.txt"]
end
|
#token_usage_from_api_response(body) ⇒ Object
631
632
633
634
635
636
637
638
639
|
# File 'lib/agent_harness/providers/codex.rb', line 631
def token_usage_from_api_response(body)
usage = body&.dig("usage")
return {} unless usage
{
input_tokens: usage["prompt_tokens"].to_i,
output_tokens: usage["completion_tokens"].to_i
}
end
|
#translate_error(message) ⇒ Object
706
707
708
709
710
711
712
|
# File 'lib/agent_harness/providers/codex.rb', line 706
def translate_error(message)
case message
when /refresh_token_reused/i then "Codex authentication expired. Please re-authenticate."
when /free tier limit/i then "Codex free tier limit reached."
else message
end
end
|
#validate_config ⇒ Object
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
|
# File 'lib/agent_harness/providers/codex.rb', line 757
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
|