codex-ruby 0.1.5 → 0.2.3

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: e93f52960b3bb6893715a92cc506bb5f20cc3af6a7861580a730d52fae665e3e
4
- data.tar.gz: 9a4e6d6eeeef6cfffd5dbef64913df0572eddd9e5e843cea8846f8a832a1c630
3
+ metadata.gz: 8b977d75f6a42ed832175f5f2fe94a51edc5cee832e23de58ef7a154062310e9
4
+ data.tar.gz: 9742a0cd52e621689755eee26f7f41cf199b2f964dced853e41765ae44595a82
5
5
  SHA512:
6
- metadata.gz: b2401fd6294a9500a42aefeaaf09702ef39fa36fb214abc1b3508a94ba4cb1a7556d7afdf8c6881f6a9bcf34307dae786a6167418ed0e4af292c0939b4f51ef5
7
- data.tar.gz: 103c8026510f1e5e0b02afd5ba284a28093dcf83c19bf380f4fa29871eca156af67279c43d456bd22ceb75fae3efef64bc6cac59ff83c4d933091e2fe2241d6e
6
+ metadata.gz: f7f9f2b48f75d8f07bf09befcd9f5f5d85c420f4757ea11f4d9292f6421e4c7d2d345ac29b414280df2fb05815ab382f996fe2b6efa99568cae19b52c53fa5fb
7
+ data.tar.gz: 6cad8b57516073b897fb6465f3a8409995559cf616928cd3ae9663ffe0afa6afe8315c9ca9e904a9ea9e389727cc2909aeb3264398c0879441d53c5c729103ae
data/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.3 (2026-09-23)
4
+
5
+ - Preserve provider failures when macOS reports a transient permission error for an exiting process group; verify that the group is gone before ignoring cleanup errors.
6
+
7
+ ## 0.2.2 (2026-09-23)
8
+
9
+ - Clear the exposed PID after an already-exited CLI has been reaped, preventing stale process diagnostics.
10
+
11
+ ## 0.2.1 (2026-09-23)
12
+
13
+ - Expose the active CLI PID through `AgentThread#pid` for worker recovery diagnostics.
14
+
15
+ ## 0.2.0 (2026-09-23)
16
+
17
+ - Forward ordered `local_image` inputs through the public thread API; reject unsupported input types and invalid local paths instead of silently discarding them.
18
+ - Add `ignore_user_config`, `ignore_rules`, and `ephemeral` thread options; use the documented `--json` CLI flag.
19
+ - Honor explicit environment replacement without inheriting parent secrets.
20
+ - Terminate and reap CLI process groups on cancellation or consumer errors, with concurrent interruption serialized.
21
+ - Raise on streams that end without a completed turn instead of returning partial answers as success.
22
+
3
23
  ## 0.1.5 (2026-06-17)
4
24
 
5
25
  - Include structured Codex CLI error events in non-zero exit errors, so
@@ -38,7 +38,8 @@ module CodexSDK
38
38
 
39
39
  # Streaming run: yields each event to the block as it arrives.
40
40
  def run_streamed(input, turn_options: TurnOptions.new, &block)
41
- prompt = normalize_input(input)
41
+ prompt, images = normalize_input(input)
42
+ completed = false
42
43
 
43
44
  output_schema_path = nil
44
45
  output_schema_path = write_output_schema(turn_options.output_schema) if turn_options.output_schema
@@ -50,19 +51,27 @@ module CodexSDK
50
51
 
51
52
  @exec.run(
52
53
  prompt,
54
+ images: images,
53
55
  resume_thread_id: @id,
54
56
  output_schema_path: output_schema_path
55
57
  ) do |event|
56
58
  # Capture thread ID from first event
57
59
  @id = event.thread_id if event.is_a?(Events::ThreadStarted)
60
+ completed = true if event.is_a?(Events::TurnCompleted)
58
61
 
59
62
  block.call(event)
60
63
  end
64
+ raise Error, "Codex stream ended without turn completion" unless completed
61
65
  ensure
62
66
  @context_snapshot = @exec&.context_snapshot
63
67
  cleanup_output_schema(output_schema_path)
64
68
  end
65
69
 
70
+ # PID of the running CLI, for external lifecycle diagnostics.
71
+ def pid
72
+ @exec&.pid
73
+ end
74
+
66
75
  # Interrupt the running subprocess.
67
76
  def interrupt
68
77
  @exec&.interrupt
@@ -73,13 +82,32 @@ module CodexSDK
73
82
  def normalize_input(input)
74
83
  case input
75
84
  when String
76
- input
85
+ [input, []]
77
86
  when Array
78
- input.filter_map do |entry|
79
- entry[:text] if entry[:type] == "text"
80
- end.join("\n\n")
87
+ texts = []
88
+ images = []
89
+ input.each do |entry|
90
+ raise ArgumentError, "Unsupported input entry" unless entry.is_a?(Hash)
91
+
92
+ case entry[:type]
93
+ when "text"
94
+ raise ArgumentError, "Text input must be a string" unless entry[:text].is_a?(String)
95
+
96
+ texts << entry[:text]
97
+ when "local_image"
98
+ path = entry[:path]
99
+ unless path.is_a?(String) && path.start_with?(File::SEPARATOR) && File.file?(path) && File.readable?(path)
100
+ raise ArgumentError, "Local image must be an absolute readable file path"
101
+ end
102
+
103
+ images << path
104
+ else
105
+ raise ArgumentError, "Unsupported input type: #{entry[:type].inspect}"
106
+ end
107
+ end
108
+ [texts.join("\n\n"), images]
81
109
  else
82
- input.to_s
110
+ raise ArgumentError, "Unsupported input: expected string or array"
83
111
  end
84
112
  end
85
113
 
@@ -5,7 +5,7 @@ require "json"
5
5
 
6
6
  module CodexSDK
7
7
  # Internal: manages the codex CLI subprocess.
8
- # Spawns `codex exec --experimental-json`, writes prompt to stdin,
8
+ # Spawns `codex exec --json`, writes prompt to stdin,
9
9
  # reads JSONL events from stdout.
10
10
  class Exec
11
11
  SHUTDOWN_TIMEOUT = 10 # seconds to wait after SIGTERM before SIGKILL
@@ -32,7 +32,9 @@ module CodexSDK
32
32
  @context_snapshot = nil
33
33
  diagnostics = []
34
34
 
35
- @stdin, @stdout, @stderr, @wait_thread = Open3.popen3(env, *args)
35
+ @stdin, @stdout, @stderr, @wait_thread = Open3.popen3(env, *args, unsetenv_others: !@options.env.nil?,
36
+ pgroup: true)
37
+ @pid = @wait_thread.pid
36
38
 
37
39
  # Write prompt and close stdin (one-shot, matching TypeScript SDK)
38
40
  @stdin.write(prompt.to_s)
@@ -85,27 +87,41 @@ module CodexSDK
85
87
  # Sends SIGTERM to the subprocess, waits, then SIGKILL if needed.
86
88
  def interrupt
87
89
  @mutex.synchronize do
88
- return unless @wait_thread&.alive?
90
+ return unless @pid
89
91
 
90
92
  begin
91
- Process.kill("TERM", @wait_thread.pid)
93
+ signal_group("TERM")
92
94
  rescue Errno::ESRCH
95
+ @wait_thread.value
96
+ @pid = nil
93
97
  return
94
98
  end
95
99
 
96
100
  # Wait for graceful shutdown
97
- unless wait_for_exit(SHUTDOWN_TIMEOUT)
98
- begin
99
- Process.kill("KILL", @wait_thread.pid)
100
- rescue Errno::ESRCH
101
- # already gone
102
- end
101
+ wait_for_exit(SHUTDOWN_TIMEOUT)
102
+ begin
103
+ # Also stop descendants that outlive the immediate CLI child.
104
+ signal_group("KILL")
105
+ rescue Errno::ESRCH
106
+ # already gone
103
107
  end
108
+ @wait_thread.value
109
+ @pid = nil
104
110
  end
105
111
  end
106
112
 
107
113
  private
108
114
 
115
+ def signal_group(signal)
116
+ Process.kill(signal, -@pid)
117
+ rescue Errno::EPERM
118
+ # macOS can report EPERM while the last group member is exiting.
119
+ # Only treat it as gone when a subsequent group probe confirms ESRCH.
120
+ @wait_thread.join(0.1)
121
+ Process.kill(0, -@pid)
122
+ raise
123
+ end
124
+
109
125
  def capture_diagnostic(event, diagnostics)
110
126
  message =
111
127
  case event
@@ -136,7 +152,7 @@ module CodexSDK
136
152
  codex_path = @options.codex_path || find_codex_path
137
153
  args = [codex_path]
138
154
  args << "--search" if @thread_options.web_search
139
- args.concat(["exec", "--experimental-json"])
155
+ args.concat(["exec", "--json"])
140
156
 
141
157
  # Global config overrides
142
158
  args.concat(ConfigSerializer.to_flags(@options.config)) if @options.config.any?
@@ -153,6 +169,9 @@ module CodexSDK
153
169
  args.concat(["--cd", to.working_directory]) if to.working_directory
154
170
  args << "--dangerously-bypass-approvals-and-sandbox" if to.dangerously_bypass_approvals_and_sandbox
155
171
  args << "--skip-git-repo-check" if to.skip_git_repo_check
172
+ args << "--ignore-user-config" if to.ignore_user_config
173
+ args << "--ignore-rules" if to.ignore_rules
174
+ args << "--ephemeral" if to.ephemeral
156
175
 
157
176
  to.additional_directories.each { |dir| args.concat(["--add-dir", dir]) }
158
177
 
@@ -227,6 +246,7 @@ module CodexSDK
227
246
  end
228
247
 
229
248
  def cleanup
249
+ interrupt
230
250
  @stdin&.close unless @stdin&.closed?
231
251
  @stdout&.close unless @stdout&.closed?
232
252
  @stderr&.close unless @stderr&.closed?
@@ -31,7 +31,10 @@ module CodexSDK
31
31
  :network_access,
32
32
  :web_search,
33
33
  :additional_directories,
34
- :skip_git_repo_check
34
+ :skip_git_repo_check,
35
+ :ignore_user_config,
36
+ :ignore_rules,
37
+ :ephemeral
35
38
  ) do
36
39
  def initialize(
37
40
  model: nil,
@@ -43,7 +46,10 @@ module CodexSDK
43
46
  network_access: nil,
44
47
  web_search: nil,
45
48
  additional_directories: [],
46
- skip_git_repo_check: false
49
+ skip_git_repo_check: false,
50
+ ignore_user_config: false,
51
+ ignore_rules: false,
52
+ ephemeral: false
47
53
  )
48
54
  super
49
55
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CodexSDK
4
- VERSION = "0.1.5"
4
+ VERSION = "0.2.3"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: codex-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anton Kopylov
@@ -51,7 +51,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
51
51
  - !ruby/object:Gem::Version
52
52
  version: '0'
53
53
  requirements: []
54
- rubygems_version: 3.7.2
54
+ rubygems_version: 4.0.20
55
55
  specification_version: 4
56
56
  summary: Ruby SDK for the Codex CLI
57
57
  test_files: []