codex-ruby 0.1.4 → 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 +4 -4
- data/CHANGELOG.md +25 -0
- data/lib/codex_sdk/agent_thread.rb +34 -6
- data/lib/codex_sdk/exec.rb +61 -13
- data/lib/codex_sdk/options.rb +8 -2
- data/lib/codex_sdk/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8b977d75f6a42ed832175f5f2fe94a51edc5cee832e23de58ef7a154062310e9
|
|
4
|
+
data.tar.gz: 9742a0cd52e621689755eee26f7f41cf199b2f964dced853e41765ae44595a82
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f7f9f2b48f75d8f07bf09befcd9f5f5d85c420f4757ea11f4d9292f6421e4c7d2d345ac29b414280df2fb05815ab382f996fe2b6efa99568cae19b52c53fa5fb
|
|
7
|
+
data.tar.gz: 6cad8b57516073b897fb6465f3a8409995559cf616928cd3ae9663ffe0afa6afe8315c9ca9e904a9ea9e389727cc2909aeb3264398c0879441d53c5c729103ae
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
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
|
+
|
|
23
|
+
## 0.1.5 (2026-06-17)
|
|
24
|
+
|
|
25
|
+
- Include structured Codex CLI error events in non-zero exit errors, so
|
|
26
|
+
API failures such as unsupported model names are visible to callers
|
|
27
|
+
|
|
3
28
|
## 0.1.4 (2026-06-16)
|
|
4
29
|
|
|
5
30
|
- Pass `web_search: true` to current Codex CLI versions as the global
|
|
@@ -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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
|
110
|
+
raise ArgumentError, "Unsupported input: expected string or array"
|
|
83
111
|
end
|
|
84
112
|
end
|
|
85
113
|
|
data/lib/codex_sdk/exec.rb
CHANGED
|
@@ -5,7 +5,7 @@ require "json"
|
|
|
5
5
|
|
|
6
6
|
module CodexSDK
|
|
7
7
|
# Internal: manages the codex CLI subprocess.
|
|
8
|
-
# Spawns `codex exec --
|
|
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
|
|
@@ -30,8 +30,11 @@ module CodexSDK
|
|
|
30
30
|
sessions_root = codex_sessions_root(env)
|
|
31
31
|
started_at = Time.now
|
|
32
32
|
@context_snapshot = nil
|
|
33
|
+
diagnostics = []
|
|
33
34
|
|
|
34
|
-
@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
|
|
35
38
|
|
|
36
39
|
# Write prompt and close stdin (one-shot, matching TypeScript SDK)
|
|
37
40
|
@stdin.write(prompt.to_s)
|
|
@@ -56,6 +59,7 @@ module CodexSDK
|
|
|
56
59
|
end
|
|
57
60
|
|
|
58
61
|
event = Events.parse(data)
|
|
62
|
+
capture_diagnostic(event, diagnostics)
|
|
59
63
|
block.call(event)
|
|
60
64
|
end
|
|
61
65
|
|
|
@@ -64,9 +68,9 @@ module CodexSDK
|
|
|
64
68
|
|
|
65
69
|
unless status.success?
|
|
66
70
|
code = status.exitstatus || status.termsig
|
|
67
|
-
|
|
71
|
+
diagnostic = exit_diagnostic(diagnostics, stderr_buf)
|
|
68
72
|
raise ExecError.new(
|
|
69
|
-
"Codex exited with code #{code}: #{
|
|
73
|
+
"Codex exited with code #{code}: #{diagnostic}",
|
|
70
74
|
exit_code: code,
|
|
71
75
|
stderr: stderr_buf
|
|
72
76
|
)
|
|
@@ -83,32 +87,72 @@ module CodexSDK
|
|
|
83
87
|
# Sends SIGTERM to the subprocess, waits, then SIGKILL if needed.
|
|
84
88
|
def interrupt
|
|
85
89
|
@mutex.synchronize do
|
|
86
|
-
return unless @
|
|
90
|
+
return unless @pid
|
|
87
91
|
|
|
88
92
|
begin
|
|
89
|
-
|
|
93
|
+
signal_group("TERM")
|
|
90
94
|
rescue Errno::ESRCH
|
|
95
|
+
@wait_thread.value
|
|
96
|
+
@pid = nil
|
|
91
97
|
return
|
|
92
98
|
end
|
|
93
99
|
|
|
94
100
|
# Wait for graceful shutdown
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
|
101
107
|
end
|
|
108
|
+
@wait_thread.value
|
|
109
|
+
@pid = nil
|
|
102
110
|
end
|
|
103
111
|
end
|
|
104
112
|
|
|
105
113
|
private
|
|
106
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
|
+
|
|
125
|
+
def capture_diagnostic(event, diagnostics)
|
|
126
|
+
message =
|
|
127
|
+
case event
|
|
128
|
+
when Events::TurnFailed
|
|
129
|
+
event.error_message
|
|
130
|
+
when Events::Error
|
|
131
|
+
event.message
|
|
132
|
+
when Events::ItemStarted, Events::ItemUpdated, Events::ItemCompleted
|
|
133
|
+
event.item.message if event.item.is_a?(Items::Error)
|
|
134
|
+
end
|
|
135
|
+
diagnostics << readable_error_message(message) if message.to_s.strip != ""
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def exit_diagnostic(diagnostics, stderr)
|
|
139
|
+
diagnostic = diagnostics.compact.uniq.join("\n")
|
|
140
|
+
diagnostic = stderr.to_s if diagnostic == ""
|
|
141
|
+
diagnostic.length > 500 ? "#{diagnostic[0, 497]}..." : diagnostic
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def readable_error_message(message)
|
|
145
|
+
parsed = JSON.parse(message.to_s)
|
|
146
|
+
parsed.dig("error", "message") || parsed["message"] || message.to_s
|
|
147
|
+
rescue JSON::ParserError
|
|
148
|
+
message.to_s
|
|
149
|
+
end
|
|
150
|
+
|
|
107
151
|
def build_args(resume_thread_id: nil, images: [], output_schema_path: nil)
|
|
108
152
|
codex_path = @options.codex_path || find_codex_path
|
|
109
153
|
args = [codex_path]
|
|
110
154
|
args << "--search" if @thread_options.web_search
|
|
111
|
-
args.concat(["exec", "--
|
|
155
|
+
args.concat(["exec", "--json"])
|
|
112
156
|
|
|
113
157
|
# Global config overrides
|
|
114
158
|
args.concat(ConfigSerializer.to_flags(@options.config)) if @options.config.any?
|
|
@@ -125,6 +169,9 @@ module CodexSDK
|
|
|
125
169
|
args.concat(["--cd", to.working_directory]) if to.working_directory
|
|
126
170
|
args << "--dangerously-bypass-approvals-and-sandbox" if to.dangerously_bypass_approvals_and_sandbox
|
|
127
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
|
|
128
175
|
|
|
129
176
|
to.additional_directories.each { |dir| args.concat(["--add-dir", dir]) }
|
|
130
177
|
|
|
@@ -199,6 +246,7 @@ module CodexSDK
|
|
|
199
246
|
end
|
|
200
247
|
|
|
201
248
|
def cleanup
|
|
249
|
+
interrupt
|
|
202
250
|
@stdin&.close unless @stdin&.closed?
|
|
203
251
|
@stdout&.close unless @stdout&.closed?
|
|
204
252
|
@stderr&.close unless @stderr&.closed?
|
data/lib/codex_sdk/options.rb
CHANGED
|
@@ -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
|
data/lib/codex_sdk/version.rb
CHANGED
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.
|
|
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:
|
|
54
|
+
rubygems_version: 4.0.20
|
|
55
55
|
specification_version: 4
|
|
56
56
|
summary: Ruby SDK for the Codex CLI
|
|
57
57
|
test_files: []
|