Class: CodexSDK::Exec
- Inherits:
-
Object
- Object
- CodexSDK::Exec
- Defined in:
- lib/codex_sdk/exec.rb
Overview
Internal: manages the codex CLI subprocess. Spawns ‘codex exec –experimental-json`, writes prompt to stdin, reads JSONL events from stdout.
Constant Summary collapse
- SHUTDOWN_TIMEOUT =
seconds to wait after SIGTERM before SIGKILL
10
Instance Attribute Summary collapse
-
#context_snapshot ⇒ Object
readonly
Returns the value of attribute context_snapshot.
-
#pid ⇒ Object
readonly
Returns the value of attribute pid.
Instance Method Summary collapse
-
#initialize(options, thread_options: ThreadOptions.new) ⇒ Exec
constructor
A new instance of Exec.
-
#interrupt ⇒ Object
Sends SIGTERM to the subprocess, waits, then SIGKILL if needed.
-
#run(prompt, resume_thread_id: nil, images: [], output_schema_path: nil, &block) ⇒ Object
Spawns the subprocess, writes the prompt, reads JSONL events.
Constructor Details
#initialize(options, thread_options: ThreadOptions.new) ⇒ Exec
Returns a new instance of Exec.
15 16 17 18 19 20 21 22 23 |
# File 'lib/codex_sdk/exec.rb', line 15 def initialize(, thread_options: ThreadOptions.new) @options = @thread_options = @stdin = nil @stdout = nil @stderr = nil @wait_thread = nil @mutex = Mutex.new end |
Instance Attribute Details
#context_snapshot ⇒ Object (readonly)
Returns the value of attribute context_snapshot.
13 14 15 |
# File 'lib/codex_sdk/exec.rb', line 13 def context_snapshot @context_snapshot end |
#pid ⇒ Object (readonly)
Returns the value of attribute pid.
13 14 15 |
# File 'lib/codex_sdk/exec.rb', line 13 def pid @pid end |
Instance Method Details
#interrupt ⇒ Object
Sends SIGTERM to the subprocess, waits, then SIGKILL if needed.
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/codex_sdk/exec.rb', line 84 def interrupt @mutex.synchronize do return unless @wait_thread&.alive? begin Process.kill("TERM", @wait_thread.pid) rescue Errno::ESRCH return end # Wait for graceful shutdown unless wait_for_exit(SHUTDOWN_TIMEOUT) begin Process.kill("KILL", @wait_thread.pid) rescue Errno::ESRCH # already gone end end end end |
#run(prompt, resume_thread_id: nil, images: [], output_schema_path: nil, &block) ⇒ Object
Spawns the subprocess, writes the prompt, reads JSONL events. Yields each parsed event hash to the block.
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 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/codex_sdk/exec.rb', line 27 def run(prompt, resume_thread_id: nil, images: [], output_schema_path: nil, &block) args = build_args(resume_thread_id: resume_thread_id, images: images, output_schema_path: output_schema_path) env = build_env sessions_root = codex_sessions_root(env) started_at = Time.now @context_snapshot = nil @stdin, @stdout, @stderr, @wait_thread = Open3.popen3(env, *args) # Write prompt and close stdin (one-shot, matching TypeScript SDK) @stdin.write(prompt.to_s) @stdin.close # Read stderr in background thread stderr_reader = ::Thread.new do @stderr.read rescue StandardError "" end # Read JSONL from stdout line by line @stdout.each_line do |line| line = line.strip next if line.empty? begin data = JSON.parse(line) rescue JSON::ParserError => e raise ParseError.new("Failed to parse event: #{e.}", line: line) end event = Events.parse(data) block.call(event) end stderr_buf = stderr_reader.value.to_s status = @wait_thread.value unless status.success? code = status.exitstatus || status.termsig truncated = stderr_buf.length > 500 ? "#{stderr_buf[0, 497]}..." : stderr_buf raise ExecError.new( "Codex exited with code #{code}: #{truncated}", exit_code: code, stderr: stderr_buf ) end @context_snapshot = read_context_snapshot( sessions_root: sessions_root, started_at: started_at ) ensure cleanup end |