ace-llm-providers-cli 0.32.3 → 0.33.0
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/.ace-defaults/llm/providers/pi.yml +7 -8
- data/CHANGELOG.md +16 -0
- data/lib/ace/llm/providers/cli/molecules/process_supervisor.rb +68 -0
- data/lib/ace/llm/providers/cli/molecules/safe_capture.rb +48 -21
- data/lib/ace/llm/providers/cli/pi_client.rb +4 -2
- data/lib/ace/llm/providers/cli/version.rb +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7410921d087bbcfa634cec60984b2d578de92d92d84fc0730aa4cae054848b4b
|
|
4
|
+
data.tar.gz: '08b71084f7f13ac67941da17c20e84b34c1dd52673f037a929ac7e896e0c2310'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 92613d00db61f1ad8e8a1e99285c478526b38dd998bd26355a543939ba2453da13f23df97479ae9a4186653fce4b31bd4383dc8fa23ff69c9a887ede90d8c339
|
|
7
|
+
data.tar.gz: 50d6485baf304a089b0311a3f0fe096fc90bb697a1b72b4c43f27d295a21631753e1e1d8be718c89722a71e51d3cbfa296926d49fe46875844d4dd53b4969e0a
|
|
@@ -3,11 +3,9 @@ class: Ace::LLM::Providers::CLI::PiClient
|
|
|
3
3
|
gem: ace-llm-providers-cli
|
|
4
4
|
models:
|
|
5
5
|
# ZAI (default provider)
|
|
6
|
-
- zai/glm-
|
|
7
|
-
- zai/glm-
|
|
8
|
-
- zai/glm-
|
|
9
|
-
- zai/glm-5.1
|
|
10
|
-
- zai/glm-5-turbo
|
|
6
|
+
- zai/glm-5.3
|
|
7
|
+
- zai/glm-5.3-flash
|
|
8
|
+
- zai/glm-5.3-highspeed
|
|
11
9
|
# Anthropic
|
|
12
10
|
- anthropic/claude-opus-4-6
|
|
13
11
|
- anthropic/claude-opus-4-5
|
|
@@ -26,10 +24,11 @@ models:
|
|
|
26
24
|
- openai-codex/gpt-5.1
|
|
27
25
|
aliases:
|
|
28
26
|
global:
|
|
29
|
-
pi: pi:zai/glm-5.
|
|
27
|
+
pi: pi:zai/glm-5.3-flash
|
|
30
28
|
model:
|
|
31
|
-
glm: zai/glm-5.
|
|
32
|
-
|
|
29
|
+
glm: zai/glm-5.3
|
|
30
|
+
glmflash: zai/glm-5.3-flash
|
|
31
|
+
glmhighspeed: zai/glm-5.3-highspeed
|
|
33
32
|
gpt: openai-codex/gpt-5.4
|
|
34
33
|
capabilities:
|
|
35
34
|
- text_generation
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Fixed
|
|
11
|
+
- Hardened `SafeCapture` process group termination for container PID namespaces where background processes reparent to PID 1, ensuring reliable cleanup of descendant processes without ESRCH failures.
|
|
12
|
+
|
|
13
|
+
## [0.33.0] - 2026-09-04
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- Registered the live Pi/Z.AI GLM 5.3 family (`zai/glm-5.3`, `zai/glm-5.3-flash`, `zai/glm-5.3-highspeed`) in the shipped provider registry and removed the obsolete GLM 4.x/5.1/5-turbo entries and `glmturbo` alias.
|
|
17
|
+
- Made `zai/glm-5.3-flash` the routine Pi default: the global `pi` alias and `PiClient` default model now target the flash model, with `glm`, `glmflash`, and `glmhighspeed` model aliases for the three live targets.
|
|
18
|
+
|
|
19
|
+
### Technical
|
|
20
|
+
- Added focused regression coverage for registry parity between project and shipped defaults, alias and full-slash-selector resolution without truncation, unknown-model fail-closed behavior, and `--no-fallback` preserving exactly one provider/model target.
|
|
21
|
+
|
|
22
|
+
## [0.32.4] - 2026-09-02
|
|
23
|
+
|
|
24
|
+
### Technical
|
|
25
|
+
- Included in the coordinated all-package patch release preparation for ACE monorepo review.
|
|
10
26
|
## [0.32.3] - 2026-08-29
|
|
11
27
|
|
|
12
28
|
### Fixed
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Linux normally reparents orphaned grandchildren to PID 1. Container
|
|
4
|
+
# entrypoints do not always reap them, leaving killed descendants as observable
|
|
5
|
+
# zombies. This supervisor becomes a child subreaper before spawning the provider
|
|
6
|
+
# command, so it owns and reaps the complete isolated command group before exit.
|
|
7
|
+
module Ace
|
|
8
|
+
module LLM
|
|
9
|
+
module Providers
|
|
10
|
+
module CLI
|
|
11
|
+
module Molecules
|
|
12
|
+
module ProcessSupervisor
|
|
13
|
+
PR_SET_CHILD_SUBREAPER = 36
|
|
14
|
+
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
def run(command)
|
|
18
|
+
enable_child_subreaper
|
|
19
|
+
ready_fd = Integer(ENV.delete("ACE_SAFE_CAPTURE_READY_FD"))
|
|
20
|
+
command_pid = Process.spawn(*command, pgroup: true, ready_fd => :close)
|
|
21
|
+
command_pgid = command_pid
|
|
22
|
+
IO.for_fd(ready_fd).tap { |io| io.write("1"); io.close }
|
|
23
|
+
|
|
24
|
+
Signal.trap("TERM") { terminate_group(command_pgid) }
|
|
25
|
+
|
|
26
|
+
_pid, status = Process.wait2(command_pid)
|
|
27
|
+
terminate_group(command_pgid)
|
|
28
|
+
reap_group(command_pgid)
|
|
29
|
+
|
|
30
|
+
exit(status.exitstatus || 128 + status.termsig)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def enable_child_subreaper
|
|
34
|
+
previous_deprecated = Warning[:deprecated]
|
|
35
|
+
Warning[:deprecated] = false
|
|
36
|
+
require "fiddle"
|
|
37
|
+
Warning[:deprecated] = previous_deprecated
|
|
38
|
+
|
|
39
|
+
prctl = Fiddle::Function.new(
|
|
40
|
+
Fiddle::Handle::DEFAULT["prctl"],
|
|
41
|
+
[Fiddle::TYPE_INT, Fiddle::TYPE_LONG, Fiddle::TYPE_LONG, Fiddle::TYPE_LONG, Fiddle::TYPE_LONG],
|
|
42
|
+
Fiddle::TYPE_INT
|
|
43
|
+
)
|
|
44
|
+
raise "unable to become a child subreaper" unless prctl.call(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0).zero?
|
|
45
|
+
ensure
|
|
46
|
+
Warning[:deprecated] = previous_deprecated unless previous_deprecated.nil?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def terminate_group(pgid)
|
|
50
|
+
Process.kill("TERM", -pgid)
|
|
51
|
+
Process.kill("KILL", -pgid)
|
|
52
|
+
rescue Errno::ESRCH, Errno::EPERM
|
|
53
|
+
nil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def reap_group(pgid)
|
|
57
|
+
loop { Process.waitpid(-pgid) }
|
|
58
|
+
rescue Errno::ECHILD
|
|
59
|
+
nil
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
Ace::LLM::Providers::CLI::Molecules::ProcessSupervisor.run(ARGV) if $PROGRAM_NAME == __FILE__
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "open3"
|
|
4
|
+
require "rbconfig"
|
|
4
5
|
|
|
5
6
|
module Ace
|
|
6
7
|
module LLM
|
|
@@ -13,8 +14,9 @@ module Ace
|
|
|
13
14
|
# which causes "stream closed in another thread (IOError)" when the
|
|
14
15
|
# timeout fires while Open3's internal reader threads hold pipe handles.
|
|
15
16
|
#
|
|
16
|
-
# Uses Open3.popen3
|
|
17
|
-
#
|
|
17
|
+
# Uses Open3.popen3 and, on Linux, a dedicated child-subreaper supervisor
|
|
18
|
+
# so the complete command tree is terminated and reaped without thread
|
|
19
|
+
# interruption or closed-stream races.
|
|
18
20
|
class SafeCapture
|
|
19
21
|
# @param cmd [Array<String>] Command arguments
|
|
20
22
|
# @param timeout [Integer] Timeout in seconds
|
|
@@ -23,7 +25,7 @@ module Ace
|
|
|
23
25
|
# @param env [Hash, nil] Environment variables (merged with current env)
|
|
24
26
|
# @param provider_name [String] Provider name for error messages
|
|
25
27
|
# @param isolate_process_group [Boolean] Spawn subprocess in isolated process group
|
|
26
|
-
# @param cleanup_group_on_exit [Boolean]
|
|
28
|
+
# @param cleanup_group_on_exit [Boolean] Clean up descendants on success
|
|
27
29
|
# @return [Array(String, String, Process::Status)] [stdout, stderr, status]
|
|
28
30
|
# @raise [Ace::LLM::ProviderError] on timeout
|
|
29
31
|
def self.call(cmd, timeout:, stdin_data: nil, chdir: nil, env: nil, provider_name: "CLI",
|
|
@@ -35,9 +37,19 @@ module Ace
|
|
|
35
37
|
opts[:pgroup] = true if isolate_process_group
|
|
36
38
|
|
|
37
39
|
full_cmd = Array(command_prefix) + cmd
|
|
38
|
-
|
|
40
|
+
supervised = isolate_process_group && cleanup_group_on_exit && RUBY_PLATFORM.include?("linux")
|
|
41
|
+
full_cmd = supervisor_command(full_cmd) if supervised
|
|
42
|
+
ready_reader, ready_writer = IO.pipe if supervised
|
|
43
|
+
opts[ready_writer.fileno] = ready_writer if supervised
|
|
44
|
+
spawn_env = env&.dup || {}
|
|
45
|
+
spawn_env["ACE_SAFE_CAPTURE_READY_FD"] = ready_writer.fileno.to_s if supervised
|
|
46
|
+
args = spawn_env.empty? ? full_cmd : [spawn_env, *full_cmd]
|
|
39
47
|
|
|
40
48
|
Open3.popen3(*args, **opts) do |stdin, stdout, stderr, wait_thr|
|
|
49
|
+
ready_writer&.close
|
|
50
|
+
ready_reader&.read(1)
|
|
51
|
+
ready_reader&.close
|
|
52
|
+
|
|
41
53
|
pid = wait_thr.pid
|
|
42
54
|
pgid = safe_getpgid(pid)
|
|
43
55
|
debug_log(provider_name, "spawn pid=#{pid} pgid=#{pgid || "n/a"}")
|
|
@@ -56,8 +68,13 @@ module Ace
|
|
|
56
68
|
|
|
57
69
|
unless wait_thr.join(normalized_timeout)
|
|
58
70
|
# Timeout: kill subprocess group (and descendants), then clean up
|
|
59
|
-
terminate_subprocess_tree(
|
|
60
|
-
|
|
71
|
+
terminate_subprocess_tree(
|
|
72
|
+
pid: pid, pgid: pgid, provider_name: provider_name, supervised: supervised
|
|
73
|
+
)
|
|
74
|
+
unless wait_thr.join(5)
|
|
75
|
+
terminate_group_or_pid("KILL", pid, pgid)
|
|
76
|
+
wait_thr.join(5)
|
|
77
|
+
end
|
|
61
78
|
|
|
62
79
|
stdout.close unless stdout.closed?
|
|
63
80
|
stderr.close unless stderr.closed?
|
|
@@ -70,12 +87,15 @@ module Ace
|
|
|
70
87
|
end
|
|
71
88
|
|
|
72
89
|
status = wait_thr.value
|
|
73
|
-
if isolate_process_group && cleanup_group_on_exit
|
|
90
|
+
if isolate_process_group && cleanup_group_on_exit && !supervised
|
|
74
91
|
terminate_descendants_after_success(pid: pid, pgid: pgid, provider_name: provider_name)
|
|
75
92
|
end
|
|
76
93
|
|
|
77
94
|
[out_reader.value, err_reader.value, status]
|
|
78
95
|
end
|
|
96
|
+
ensure
|
|
97
|
+
ready_reader&.close unless ready_reader&.closed?
|
|
98
|
+
ready_writer&.close unless ready_writer&.closed?
|
|
79
99
|
end
|
|
80
100
|
|
|
81
101
|
class << self
|
|
@@ -99,21 +119,24 @@ module Ace
|
|
|
99
119
|
raise ArgumentError, "timeout must be a positive numeric value, got #{value.inspect}"
|
|
100
120
|
end
|
|
101
121
|
|
|
102
|
-
def
|
|
122
|
+
def supervisor_command(command)
|
|
123
|
+
supervisor = File.expand_path("process_supervisor.rb", __dir__)
|
|
124
|
+
[RbConfig.ruby, "-W0", supervisor, *command]
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def terminate_subprocess_tree(pid:, pgid:, provider_name:, supervised: false)
|
|
103
128
|
debug_log(provider_name, "timeout cleanup pid=#{pid} pgid=#{pgid || "n/a"}")
|
|
104
129
|
terminate_group_or_pid("TERM", pid, pgid)
|
|
105
|
-
|
|
106
|
-
terminate_group_or_pid("KILL", pid, pgid)
|
|
130
|
+
terminate_group_or_pid("KILL", pid, pgid) unless supervised
|
|
107
131
|
end
|
|
108
132
|
|
|
109
133
|
def terminate_descendants_after_success(pid:, pgid:, provider_name:)
|
|
110
134
|
return unless pgid
|
|
111
|
-
return unless group_alive?(pgid)
|
|
112
135
|
|
|
113
136
|
debug_log(provider_name, "post-exit cleanup pgid=#{pgid}")
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
137
|
+
safe_kill_group(pgid, "TERM")
|
|
138
|
+
safe_kill_group(pgid, "KILL")
|
|
139
|
+
reap_terminated_group(pgid)
|
|
117
140
|
end
|
|
118
141
|
|
|
119
142
|
def terminate_group_or_pid(signal, pid, pgid)
|
|
@@ -122,6 +145,13 @@ module Ace
|
|
|
122
145
|
else
|
|
123
146
|
Process.kill(signal, pid)
|
|
124
147
|
end
|
|
148
|
+
true
|
|
149
|
+
rescue Errno::ESRCH, Errno::EPERM
|
|
150
|
+
nil
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def safe_kill_group(pgid, signal)
|
|
154
|
+
Process.kill(signal, -pgid)
|
|
125
155
|
rescue Errno::ESRCH, Errno::EPERM
|
|
126
156
|
nil
|
|
127
157
|
end
|
|
@@ -132,13 +162,10 @@ module Ace
|
|
|
132
162
|
nil
|
|
133
163
|
end
|
|
134
164
|
|
|
135
|
-
def
|
|
136
|
-
Process.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
false
|
|
140
|
-
rescue Errno::EPERM
|
|
141
|
-
true
|
|
165
|
+
def reap_terminated_group(pgid)
|
|
166
|
+
loop { Process.waitpid(-pgid) }
|
|
167
|
+
rescue Errno::ECHILD
|
|
168
|
+
nil
|
|
142
169
|
end
|
|
143
170
|
|
|
144
171
|
def debug_log(provider_name, message)
|
|
@@ -27,7 +27,7 @@ module Ace
|
|
|
27
27
|
"pi"
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
DEFAULT_MODEL = "zai/glm-
|
|
30
|
+
DEFAULT_MODEL = "zai/glm-5.3-flash"
|
|
31
31
|
|
|
32
32
|
def initialize(model: nil, **options)
|
|
33
33
|
@model = model || DEFAULT_MODEL
|
|
@@ -67,7 +67,9 @@ module Ace
|
|
|
67
67
|
# List available Pi models
|
|
68
68
|
def list_models
|
|
69
69
|
[
|
|
70
|
-
{id: "zai/glm-
|
|
70
|
+
{id: "zai/glm-5.3", name: "GLM 5.3", description: "ZAI flagship model", context_size: 1_000_000},
|
|
71
|
+
{id: "zai/glm-5.3-flash", name: "GLM 5.3 Flash", description: "ZAI routine default", context_size: 1_000_000},
|
|
72
|
+
{id: "zai/glm-5.3-highspeed", name: "GLM 5.3 Highspeed", description: "ZAI low-latency model", context_size: 1_000_000},
|
|
71
73
|
{id: "anthropic/claude-opus-4-6", name: "Claude Opus 4.6", description: "Anthropic flagship", context_size: 200_000},
|
|
72
74
|
{id: "anthropic/claude-sonnet-4-5", name: "Claude Sonnet 4.5", description: "Anthropic balanced", context_size: 200_000},
|
|
73
75
|
{id: "anthropic/claude-haiku-4-5", name: "Claude Haiku 4.5", description: "Anthropic fast", context_size: 200_000},
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ace-llm-providers-cli
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.33.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michal Czyz
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-09-05 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: ace-llm
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '0.
|
|
18
|
+
version: '0.39'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '0.
|
|
25
|
+
version: '0.39'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: rake
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -111,6 +111,7 @@ files:
|
|
|
111
111
|
- lib/ace/llm/providers/cli/codex_oai_client.rb
|
|
112
112
|
- lib/ace/llm/providers/cli/gemini_client.rb
|
|
113
113
|
- lib/ace/llm/providers/cli/molecules/health_checker.rb
|
|
114
|
+
- lib/ace/llm/providers/cli/molecules/process_supervisor.rb
|
|
114
115
|
- lib/ace/llm/providers/cli/molecules/safe_capture.rb
|
|
115
116
|
- lib/ace/llm/providers/cli/molecules/session_finder.rb
|
|
116
117
|
- lib/ace/llm/providers/cli/molecules/skill_name_reader.rb
|