agent-cli-runtime 0.1.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 +7 -0
- data/CHANGELOG.md +14 -0
- data/LICENSE.txt +21 -0
- data/README.md +156 -0
- data/agent-cli-runtime.gemspec +48 -0
- data/exe/agent-runtime +5 -0
- data/lib/agent_cli_runtime/cli.rb +98 -0
- data/lib/agent_cli_runtime/errors.rb +17 -0
- data/lib/agent_cli_runtime/probe.rb +91 -0
- data/lib/agent_cli_runtime/profile.rb +390 -0
- data/lib/agent_cli_runtime/profiles.rb +190 -0
- data/lib/agent_cli_runtime/redactor.rb +42 -0
- data/lib/agent_cli_runtime/runtime.rb +289 -0
- data/lib/agent_cli_runtime/usage_extractors.rb +115 -0
- data/lib/agent_cli_runtime/values.rb +174 -0
- data/lib/agent_cli_runtime/version.rb +3 -0
- data/lib/agent_cli_runtime.rb +47 -0
- metadata +121 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
module AgentCliRuntime
|
|
2
|
+
module Runtime
|
|
3
|
+
module_function
|
|
4
|
+
|
|
5
|
+
def compile(request)
|
|
6
|
+
unless request.is_a?(Request)
|
|
7
|
+
raise ArgumentError,
|
|
8
|
+
"request must be an AgentCliRuntime::Request"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
profile = Profiles.resolve(request.profile)
|
|
12
|
+
evidence = [ supported_evidence(profile, :headless) ]
|
|
13
|
+
argv = [ request.executable || profile.bin ]
|
|
14
|
+
prompt_style = profile.prompt_style
|
|
15
|
+
argv << profile.headless_flag
|
|
16
|
+
argv << request.prompt if prompt_style == :headless_flag_value
|
|
17
|
+
|
|
18
|
+
argv.concat(
|
|
19
|
+
permission_arguments(
|
|
20
|
+
profile, request.permission_mode, evidence,
|
|
21
|
+
trusted_arguments: request.permission_arguments
|
|
22
|
+
)
|
|
23
|
+
)
|
|
24
|
+
argv.concat(directory_arguments(profile, request, evidence))
|
|
25
|
+
argv.concat(tool_scope_arguments(profile, request, evidence))
|
|
26
|
+
argv.concat(budget_arguments(profile, request.max_budget_usd, evidence))
|
|
27
|
+
argv.concat(identity_arguments(profile, request, evidence))
|
|
28
|
+
|
|
29
|
+
request.capabilities.each do |capability|
|
|
30
|
+
capability_evidence = require_capability!(profile, capability)
|
|
31
|
+
evidence << capability_evidence
|
|
32
|
+
argv.concat(capability_evidence.arguments)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
unless request.raw_cli_arguments.empty?
|
|
36
|
+
unless profile.raw_cli_arguments_supported?
|
|
37
|
+
unsupported!(
|
|
38
|
+
profile, :raw_cli_arguments,
|
|
39
|
+
"agent profile #{profile.name.inspect} does not accept raw CLI arguments"
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
evidence << supported_evidence(
|
|
43
|
+
profile, :raw_cli_arguments, request.raw_cli_arguments
|
|
44
|
+
)
|
|
45
|
+
argv.concat(request.raw_cli_arguments)
|
|
46
|
+
end
|
|
47
|
+
argv.concat(request.trusted_cli_arguments)
|
|
48
|
+
argv.concat(profile.output_format_flags) if request.include_output_format
|
|
49
|
+
argv << (prompt_style == :stdin ? "-" : request.prompt) unless
|
|
50
|
+
prompt_style == :headless_flag_value
|
|
51
|
+
|
|
52
|
+
CompiledInvocation.new(
|
|
53
|
+
argv: request.command_prefix + argv,
|
|
54
|
+
stdin_data: prompt_style == :stdin ? request.prompt : nil,
|
|
55
|
+
provider: profile.name,
|
|
56
|
+
launcher_identity: profile.launcher_identity,
|
|
57
|
+
capability_evidence: evidence
|
|
58
|
+
)
|
|
59
|
+
rescue Error
|
|
60
|
+
raise
|
|
61
|
+
rescue StandardError => e
|
|
62
|
+
profile = Profiles.resolve(request.profile) if request.is_a?(Request)
|
|
63
|
+
compilation_error!(profile, e)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def prepare!(profile)
|
|
67
|
+
resolved = Profiles.resolve(profile)
|
|
68
|
+
result = Probe.call(resolved)
|
|
69
|
+
return result if result.ready
|
|
70
|
+
|
|
71
|
+
evidence = unsupported_evidence(
|
|
72
|
+
resolved, :probe, result.diagnostic || "local prerequisites unavailable"
|
|
73
|
+
)
|
|
74
|
+
raise ProbeError.new(
|
|
75
|
+
"agent profile #{resolved.name.inspect} probe failed: #{evidence.diagnostic}",
|
|
76
|
+
evidence: evidence
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def require_capability!(profile, capability)
|
|
81
|
+
resolved = Profiles.resolve(profile)
|
|
82
|
+
arguments = resolved.require_cli_capability!(capability)
|
|
83
|
+
supported_evidence(resolved, capability, arguments)
|
|
84
|
+
rescue UnknownProvider
|
|
85
|
+
raise
|
|
86
|
+
rescue Error => e
|
|
87
|
+
evidence = unsupported_evidence(
|
|
88
|
+
resolved, capability, Redactor.diagnostic(e)
|
|
89
|
+
)
|
|
90
|
+
raise UnsupportedCapability.new(
|
|
91
|
+
"agent profile #{resolved.name.inspect} cannot provide " \
|
|
92
|
+
"#{capability.to_sym.inspect}: #{evidence.diagnostic}",
|
|
93
|
+
evidence: evidence
|
|
94
|
+
), cause: e
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def extract_usage(profile, event)
|
|
98
|
+
resolved = Profiles.resolve(profile)
|
|
99
|
+
begin
|
|
100
|
+
normalize_usage(resolved.extract_usage_event(event))
|
|
101
|
+
rescue StandardError
|
|
102
|
+
nil
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def observe(profile, result)
|
|
107
|
+
resolved = Profiles.resolve(profile)
|
|
108
|
+
raw = result.is_a?(Hash) ? result : {}
|
|
109
|
+
ObservableResult.new(
|
|
110
|
+
provider: resolved.name,
|
|
111
|
+
launcher_identity: resolved.launcher_identity,
|
|
112
|
+
exit_code: raw[:exit_code],
|
|
113
|
+
timed_out: raw[:timed_out],
|
|
114
|
+
status: raw[:status],
|
|
115
|
+
usage: normalize_usage(raw[:usage]),
|
|
116
|
+
final_message: raw[:final_message],
|
|
117
|
+
diagnostic: Redactor.diagnostic(
|
|
118
|
+
raw[:error_message] || raw[:limit_text]
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def supported_evidence(profile, capability, arguments = [])
|
|
124
|
+
CapabilityEvidence.new(
|
|
125
|
+
capability: capability,
|
|
126
|
+
supported: true,
|
|
127
|
+
provider: profile.name,
|
|
128
|
+
launcher_identity: profile.launcher_identity,
|
|
129
|
+
arguments: arguments
|
|
130
|
+
)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def unsupported_evidence(profile, capability, diagnostic)
|
|
134
|
+
CapabilityEvidence.new(
|
|
135
|
+
capability: capability,
|
|
136
|
+
supported: false,
|
|
137
|
+
provider: profile&.name || :unknown,
|
|
138
|
+
launcher_identity: profile&.launcher_identity || "unknown",
|
|
139
|
+
diagnostic: Redactor.diagnostic(diagnostic)
|
|
140
|
+
)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def permission_arguments(profile, permission_mode, evidence,
|
|
144
|
+
trusted_arguments: nil)
|
|
145
|
+
arguments =
|
|
146
|
+
if trusted_arguments
|
|
147
|
+
trusted_arguments
|
|
148
|
+
else
|
|
149
|
+
profile.permission_flags(permission_mode)
|
|
150
|
+
end
|
|
151
|
+
capability =
|
|
152
|
+
permission_mode&.tr("-", "_")&.to_sym || :permission_bypass
|
|
153
|
+
evidence << supported_evidence(profile, capability, arguments)
|
|
154
|
+
arguments
|
|
155
|
+
rescue ArgumentError => e
|
|
156
|
+
unsupported!(
|
|
157
|
+
profile, permission_mode&.tr("-", "_")&.to_sym || :permission, e
|
|
158
|
+
)
|
|
159
|
+
end
|
|
160
|
+
private_class_method :permission_arguments
|
|
161
|
+
|
|
162
|
+
def directory_arguments(profile, request, evidence)
|
|
163
|
+
return [] if request.add_dirs.empty?
|
|
164
|
+
|
|
165
|
+
unless profile.add_dir_flag
|
|
166
|
+
if request.require_add_dirs
|
|
167
|
+
unsupported!(
|
|
168
|
+
profile, :add_directory,
|
|
169
|
+
"agent profile #{profile.name.inspect} cannot constrain additional directories"
|
|
170
|
+
)
|
|
171
|
+
end
|
|
172
|
+
evidence << unsupported_evidence(
|
|
173
|
+
profile, :add_directory,
|
|
174
|
+
"unsupported; directories intentionally omitted"
|
|
175
|
+
)
|
|
176
|
+
return []
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
arguments =
|
|
180
|
+
request.add_dirs.flat_map { |directory| [ profile.add_dir_flag, directory ] }
|
|
181
|
+
evidence << supported_evidence(profile, :add_directory, arguments)
|
|
182
|
+
arguments
|
|
183
|
+
end
|
|
184
|
+
private_class_method :directory_arguments
|
|
185
|
+
|
|
186
|
+
def tool_scope_arguments(profile, request, evidence)
|
|
187
|
+
arguments = []
|
|
188
|
+
{
|
|
189
|
+
allowed_tools: [ request.allowed_tools, :allowed ],
|
|
190
|
+
disallowed_tools: [ request.disallowed_tools, :disallowed ]
|
|
191
|
+
}.each do |capability, (tools, scope)|
|
|
192
|
+
value = tool_csv(tools)
|
|
193
|
+
next unless value
|
|
194
|
+
|
|
195
|
+
flag = profile.tool_scope_flags[scope]
|
|
196
|
+
unless flag
|
|
197
|
+
unsupported!(
|
|
198
|
+
profile, capability,
|
|
199
|
+
"agent profile #{profile.name.inspect} cannot enforce #{scope} tool scope"
|
|
200
|
+
)
|
|
201
|
+
end
|
|
202
|
+
scoped_arguments = [ flag, value ]
|
|
203
|
+
evidence << supported_evidence(profile, capability, scoped_arguments)
|
|
204
|
+
arguments.concat(scoped_arguments)
|
|
205
|
+
end
|
|
206
|
+
arguments
|
|
207
|
+
end
|
|
208
|
+
private_class_method :tool_scope_arguments
|
|
209
|
+
|
|
210
|
+
def budget_arguments(profile, max_budget_usd, evidence)
|
|
211
|
+
return [] if max_budget_usd.nil?
|
|
212
|
+
unless profile.budget_flag
|
|
213
|
+
unsupported!(
|
|
214
|
+
profile, :budget,
|
|
215
|
+
"agent profile #{profile.name.inspect} cannot enforce a native budget"
|
|
216
|
+
)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
arguments = [ profile.budget_flag, max_budget_usd.to_s ]
|
|
220
|
+
evidence << supported_evidence(profile, :budget, arguments)
|
|
221
|
+
arguments
|
|
222
|
+
end
|
|
223
|
+
private_class_method :budget_arguments
|
|
224
|
+
|
|
225
|
+
def identity_arguments(profile, request, evidence)
|
|
226
|
+
arguments = request.identity_arguments.dup
|
|
227
|
+
return arguments unless request.model || request.effort
|
|
228
|
+
unless request.model
|
|
229
|
+
unsupported!(
|
|
230
|
+
profile, :effort,
|
|
231
|
+
"effort requires an explicit model in an invocation request"
|
|
232
|
+
)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
identity = profile.identity_arguments(
|
|
236
|
+
model: request.model,
|
|
237
|
+
effort: request.effort,
|
|
238
|
+
pin_model: request.pin_model
|
|
239
|
+
)
|
|
240
|
+
evidence << supported_evidence(profile, :model, identity.native_arguments)
|
|
241
|
+
evidence << supported_evidence(profile, :effort) if request.effort
|
|
242
|
+
arguments.concat(identity.native_arguments)
|
|
243
|
+
rescue UnsupportedCapability => e
|
|
244
|
+
capability = request.effort && !profile.effort_argument_builder ? :effort : :model
|
|
245
|
+
unsupported!(profile, capability, e)
|
|
246
|
+
end
|
|
247
|
+
private_class_method :identity_arguments
|
|
248
|
+
|
|
249
|
+
def tool_csv(tools)
|
|
250
|
+
values = Array(tools).compact.map(&:to_s).reject(&:empty?).uniq
|
|
251
|
+
values.empty? ? nil : values.join(",")
|
|
252
|
+
end
|
|
253
|
+
private_class_method :tool_csv
|
|
254
|
+
|
|
255
|
+
def normalize_usage(usage)
|
|
256
|
+
return nil unless usage.is_a?(Hash)
|
|
257
|
+
|
|
258
|
+
input = usage.key?(:input) ? usage[:input] : usage["input"]
|
|
259
|
+
output = usage.key?(:output) ? usage[:output] : usage["output"]
|
|
260
|
+
cached = usage.key?(:cached) ? usage[:cached] : usage["cached"]
|
|
261
|
+
model = usage.key?(:model) ? usage[:model] : usage["model"]
|
|
262
|
+
{
|
|
263
|
+
input: [ input.to_i, 0 ].max,
|
|
264
|
+
output: [ output.to_i, 0 ].max,
|
|
265
|
+
cached: [ cached.to_i, 0 ].max,
|
|
266
|
+
model: model&.to_s&.dup&.freeze
|
|
267
|
+
}.freeze
|
|
268
|
+
end
|
|
269
|
+
private_class_method :normalize_usage
|
|
270
|
+
|
|
271
|
+
def unsupported!(profile, capability, diagnostic)
|
|
272
|
+
evidence = unsupported_evidence(profile, capability, diagnostic)
|
|
273
|
+
raise UnsupportedCapability.new(
|
|
274
|
+
evidence.diagnostic,
|
|
275
|
+
evidence: evidence
|
|
276
|
+
)
|
|
277
|
+
end
|
|
278
|
+
private_class_method :unsupported!
|
|
279
|
+
|
|
280
|
+
def compilation_error!(profile, error)
|
|
281
|
+
evidence = unsupported_evidence(profile, :compilation, error)
|
|
282
|
+
raise CompilationError.new(
|
|
283
|
+
"agent invocation compilation failed: #{evidence.diagnostic}",
|
|
284
|
+
evidence: evidence
|
|
285
|
+
), cause: error
|
|
286
|
+
end
|
|
287
|
+
private_class_method :compilation_error!
|
|
288
|
+
end
|
|
289
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
module AgentCliRuntime
|
|
2
|
+
module UsageExtractors
|
|
3
|
+
module_function
|
|
4
|
+
|
|
5
|
+
CLAUDE = lambda do |event|
|
|
6
|
+
next nil unless event.is_a?(Hash)
|
|
7
|
+
|
|
8
|
+
usage = usage_hash(event)
|
|
9
|
+
if event["type"] == "result" && usage
|
|
10
|
+
usage_result(event, usage, model_from(event))
|
|
11
|
+
elsif event["type"] == "stream_event" && usage
|
|
12
|
+
usage_result(event, usage, model_from(event))
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
CODEX = lambda do |event|
|
|
17
|
+
next nil unless event.is_a?(Hash)
|
|
18
|
+
|
|
19
|
+
usage = usage_hash(event)
|
|
20
|
+
usage_result(event, usage, model_from(event)) if usage
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
PI = lambda do |event|
|
|
24
|
+
next nil unless event.is_a?(Hash)
|
|
25
|
+
|
|
26
|
+
usage = usage_hash(event)
|
|
27
|
+
usage_result(event, usage, model_from(event)) if usage
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
GROK = lambda do |event|
|
|
31
|
+
next nil unless event.is_a?(Hash)
|
|
32
|
+
|
|
33
|
+
usage = usage_hash(event)
|
|
34
|
+
usage_result(event, usage, model_from(event)) if usage
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def usage_hash(event)
|
|
38
|
+
candidates = [
|
|
39
|
+
event["usage"],
|
|
40
|
+
event["token_usage"],
|
|
41
|
+
event.dig("event", "usage"),
|
|
42
|
+
event.dig("event", "message", "usage"),
|
|
43
|
+
event.dig("info", "usage"),
|
|
44
|
+
event.dig("info", "total_token_usage"),
|
|
45
|
+
event.dig("response", "usage"),
|
|
46
|
+
event.dig("item", "usage")
|
|
47
|
+
]
|
|
48
|
+
candidates.find { |value| value.is_a?(Hash) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def usage_result(event, usage, model)
|
|
52
|
+
{
|
|
53
|
+
input: token_count(
|
|
54
|
+
usage, "input_tokens", "inputTokens", "prompt_tokens", "promptTokens"
|
|
55
|
+
),
|
|
56
|
+
output: token_count(
|
|
57
|
+
usage, "output_tokens", "outputTokens", "completion_tokens",
|
|
58
|
+
"completionTokens"
|
|
59
|
+
),
|
|
60
|
+
cached: cached_tokens(usage),
|
|
61
|
+
model: model || model_from_usage(usage) || model_from(event)
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def cached_tokens(usage)
|
|
66
|
+
direct = token_count(
|
|
67
|
+
usage, "cached", "cached_tokens", "cachedTokens",
|
|
68
|
+
"cached_input_tokens", "cachedInputTokens"
|
|
69
|
+
)
|
|
70
|
+
return direct if direct.positive?
|
|
71
|
+
|
|
72
|
+
token_count(usage, "cache_read_input_tokens", "cacheReadInputTokens") +
|
|
73
|
+
token_count(usage, "cache_creation_input_tokens", "cacheCreationInputTokens") +
|
|
74
|
+
token_count(
|
|
75
|
+
nested_hash(usage, "prompt_tokens_details"), "cached_tokens", "cachedTokens"
|
|
76
|
+
) +
|
|
77
|
+
token_count(
|
|
78
|
+
nested_hash(usage, "input_tokens_details"), "cached_tokens", "cachedTokens"
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def token_count(hash, *keys)
|
|
83
|
+
return 0 unless hash.is_a?(Hash)
|
|
84
|
+
|
|
85
|
+
key = keys.find { |candidate| hash.key?(candidate) }
|
|
86
|
+
key ? hash[key].to_i : 0
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def nested_hash(hash, key)
|
|
90
|
+
value = hash[key]
|
|
91
|
+
value.is_a?(Hash) ? value : {}
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def model_from(event)
|
|
95
|
+
model_usage = event["modelUsage"]
|
|
96
|
+
if model_usage.is_a?(Hash) && !model_usage.empty?
|
|
97
|
+
return model_usage.keys.first.to_s
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
candidates = [
|
|
101
|
+
event["model"],
|
|
102
|
+
event.dig("message", "model"),
|
|
103
|
+
event.dig("event", "message", "model"),
|
|
104
|
+
event.dig("response", "model"),
|
|
105
|
+
event.dig("item", "model")
|
|
106
|
+
]
|
|
107
|
+
candidates.find { |value| !value.to_s.empty? }&.to_s
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def model_from_usage(usage)
|
|
111
|
+
value = usage["model"] || usage["model_name"] || usage["modelName"]
|
|
112
|
+
value.to_s.empty? ? nil : value.to_s
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
module AgentCliRuntime
|
|
2
|
+
module Immutable
|
|
3
|
+
module_function
|
|
4
|
+
|
|
5
|
+
def string(value)
|
|
6
|
+
string = value.to_s
|
|
7
|
+
string.frozen? ? string : string.dup.freeze
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def strings(values)
|
|
11
|
+
Array(values).map { |value| string(value) }.freeze
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def symbols(values)
|
|
15
|
+
Array(values).map(&:to_sym).uniq.freeze
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
private_constant :Immutable
|
|
19
|
+
|
|
20
|
+
CapabilityEvidence = Data.define(
|
|
21
|
+
:capability, :supported, :provider, :launcher_identity, :arguments, :diagnostic
|
|
22
|
+
) do
|
|
23
|
+
def initialize(capability:, supported:, provider:, launcher_identity:,
|
|
24
|
+
arguments: [], diagnostic: nil)
|
|
25
|
+
super(
|
|
26
|
+
capability: capability.to_sym,
|
|
27
|
+
supported: supported == true,
|
|
28
|
+
provider: provider.to_sym,
|
|
29
|
+
launcher_identity: Immutable.string(launcher_identity),
|
|
30
|
+
arguments: Immutable.strings(arguments),
|
|
31
|
+
diagnostic: diagnostic.nil? ? nil : Immutable.string(diagnostic)
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
IdentityArguments = Data.define(
|
|
37
|
+
:model, :requested_effort, :effective_effort, :effort_supported,
|
|
38
|
+
:model_pinned, :native_arguments
|
|
39
|
+
) do
|
|
40
|
+
def initialize(model:, requested_effort:, effective_effort:, effort_supported:,
|
|
41
|
+
model_pinned:, native_arguments:)
|
|
42
|
+
super(
|
|
43
|
+
model: Immutable.string(model),
|
|
44
|
+
requested_effort:
|
|
45
|
+
requested_effort.nil? ? nil : Immutable.string(requested_effort),
|
|
46
|
+
effective_effort:
|
|
47
|
+
effective_effort.nil? ? nil : Immutable.string(effective_effort),
|
|
48
|
+
effort_supported: effort_supported == true,
|
|
49
|
+
model_pinned: model_pinned == true,
|
|
50
|
+
native_arguments: Immutable.strings(native_arguments)
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
Request = Data.define(
|
|
56
|
+
:profile, :prompt, :permission_mode, :permission_arguments,
|
|
57
|
+
:add_dirs, :require_add_dirs,
|
|
58
|
+
:allowed_tools, :disallowed_tools, :max_budget_usd,
|
|
59
|
+
:model, :effort, :pin_model, :identity_arguments,
|
|
60
|
+
:capabilities, :raw_cli_arguments, :trusted_cli_arguments,
|
|
61
|
+
:executable, :command_prefix, :include_output_format
|
|
62
|
+
) do
|
|
63
|
+
def initialize(profile:, prompt:, permission_mode: nil, permission_arguments: nil,
|
|
64
|
+
add_dirs: [], require_add_dirs: false, allowed_tools: nil,
|
|
65
|
+
disallowed_tools: nil, max_budget_usd: nil, model: nil,
|
|
66
|
+
effort: nil, pin_model: true, identity_arguments: [],
|
|
67
|
+
capabilities: [], raw_cli_arguments: [],
|
|
68
|
+
trusted_cli_arguments: [], executable: nil,
|
|
69
|
+
command_prefix: [], include_output_format: true)
|
|
70
|
+
super(
|
|
71
|
+
profile: profile,
|
|
72
|
+
prompt: Immutable.string(prompt),
|
|
73
|
+
permission_mode:
|
|
74
|
+
permission_mode.nil? ? nil : Immutable.string(permission_mode),
|
|
75
|
+
permission_arguments:
|
|
76
|
+
permission_arguments.nil? ? nil : Immutable.strings(permission_arguments),
|
|
77
|
+
add_dirs: Immutable.strings(add_dirs),
|
|
78
|
+
require_add_dirs: require_add_dirs == true,
|
|
79
|
+
allowed_tools: Immutable.strings(allowed_tools),
|
|
80
|
+
disallowed_tools: Immutable.strings(disallowed_tools),
|
|
81
|
+
max_budget_usd: max_budget_usd,
|
|
82
|
+
model: model.nil? ? nil : Immutable.string(model),
|
|
83
|
+
effort: effort.nil? ? nil : Immutable.string(effort),
|
|
84
|
+
pin_model: pin_model != false,
|
|
85
|
+
identity_arguments: Immutable.strings(identity_arguments),
|
|
86
|
+
capabilities: Immutable.symbols(capabilities),
|
|
87
|
+
raw_cli_arguments: Immutable.strings(raw_cli_arguments),
|
|
88
|
+
trusted_cli_arguments: Immutable.strings(trusted_cli_arguments),
|
|
89
|
+
executable: executable.nil? ? nil : Immutable.string(executable),
|
|
90
|
+
command_prefix: Immutable.strings(command_prefix),
|
|
91
|
+
include_output_format: include_output_format != false
|
|
92
|
+
)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
CompiledInvocation = Data.define(
|
|
97
|
+
:argv, :stdin_data, :provider, :launcher_identity, :capability_evidence
|
|
98
|
+
) do
|
|
99
|
+
def initialize(argv:, stdin_data:, provider:, launcher_identity:,
|
|
100
|
+
capability_evidence:)
|
|
101
|
+
super(
|
|
102
|
+
argv: Immutable.strings(argv),
|
|
103
|
+
stdin_data: stdin_data.nil? ? nil : Immutable.string(stdin_data),
|
|
104
|
+
provider: provider.to_sym,
|
|
105
|
+
launcher_identity: Immutable.string(launcher_identity),
|
|
106
|
+
capability_evidence: Array(capability_evidence).freeze
|
|
107
|
+
)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
AuthConfiguration = Data.define(:status, :source, :diagnostic) do
|
|
112
|
+
STATUSES = %i[configured missing not_checked].freeze
|
|
113
|
+
|
|
114
|
+
def initialize(status:, source: nil, diagnostic: nil)
|
|
115
|
+
normalized_status = status.to_sym
|
|
116
|
+
unless STATUSES.include?(normalized_status)
|
|
117
|
+
raise ArgumentError,
|
|
118
|
+
"unknown auth configuration status #{status.inspect}; valid: #{STATUSES.inspect}"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
super(
|
|
122
|
+
status: normalized_status,
|
|
123
|
+
source: source.nil? ? nil : Immutable.string(source),
|
|
124
|
+
diagnostic: diagnostic.nil? ? nil : Immutable.string(diagnostic)
|
|
125
|
+
)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def configured?
|
|
129
|
+
status == :configured
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
ProbeResult = Data.define(
|
|
134
|
+
:provider, :ready, :installed, :executable, :version, :minimum_version,
|
|
135
|
+
:auth_configuration, :capability_evidence, :diagnostic
|
|
136
|
+
) do
|
|
137
|
+
def initialize(provider:, ready:, installed:, executable:, version:,
|
|
138
|
+
minimum_version:, auth_configuration:, capability_evidence:,
|
|
139
|
+
diagnostic:)
|
|
140
|
+
super(
|
|
141
|
+
provider: provider.to_sym,
|
|
142
|
+
ready: ready == true,
|
|
143
|
+
installed: installed == true,
|
|
144
|
+
executable: Immutable.string(executable),
|
|
145
|
+
version: version.nil? ? nil : Immutable.string(version),
|
|
146
|
+
minimum_version:
|
|
147
|
+
minimum_version.nil? ? nil : Immutable.string(minimum_version),
|
|
148
|
+
auth_configuration: auth_configuration,
|
|
149
|
+
capability_evidence: Array(capability_evidence).freeze,
|
|
150
|
+
diagnostic: diagnostic.nil? ? nil : Immutable.string(diagnostic)
|
|
151
|
+
)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
ObservableResult = Data.define(
|
|
156
|
+
:provider, :launcher_identity, :exit_code, :timed_out, :status,
|
|
157
|
+
:usage, :final_message, :diagnostic
|
|
158
|
+
) do
|
|
159
|
+
def initialize(provider:, launcher_identity:, exit_code:, timed_out:,
|
|
160
|
+
status:, usage:, final_message:, diagnostic:)
|
|
161
|
+
super(
|
|
162
|
+
provider: provider.to_sym,
|
|
163
|
+
launcher_identity: Immutable.string(launcher_identity),
|
|
164
|
+
exit_code: exit_code,
|
|
165
|
+
timed_out: timed_out == true,
|
|
166
|
+
status: status&.to_sym,
|
|
167
|
+
usage: usage&.dup&.freeze,
|
|
168
|
+
final_message:
|
|
169
|
+
final_message.nil? ? nil : Immutable.string(final_message),
|
|
170
|
+
diagnostic: diagnostic.nil? ? nil : Immutable.string(diagnostic)
|
|
171
|
+
)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
require "agent_cli_runtime/version"
|
|
2
|
+
require "agent_cli_runtime/errors"
|
|
3
|
+
require "agent_cli_runtime/values"
|
|
4
|
+
|
|
5
|
+
module AgentCliRuntime
|
|
6
|
+
DIAGNOSTIC_BYTES = 512
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
require "agent_cli_runtime/redactor"
|
|
10
|
+
require "agent_cli_runtime/usage_extractors"
|
|
11
|
+
require "agent_cli_runtime/profile"
|
|
12
|
+
require "agent_cli_runtime/profiles"
|
|
13
|
+
require "agent_cli_runtime/probe"
|
|
14
|
+
require "agent_cli_runtime/runtime"
|
|
15
|
+
require "agent_cli_runtime/cli"
|
|
16
|
+
|
|
17
|
+
module AgentCliRuntime
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
def compile(request)
|
|
21
|
+
Runtime.compile(request)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def prepare!(profile)
|
|
25
|
+
Runtime.prepare!(profile)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def require_capability!(profile, capability)
|
|
29
|
+
Runtime.require_capability!(profile, capability)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def extract_usage(profile, event)
|
|
33
|
+
Runtime.extract_usage(profile, event)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def observe(profile, result)
|
|
37
|
+
Runtime.observe(profile, result)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def probe(profile, home: nil, env: ENV)
|
|
41
|
+
Probe.call(profile, home: home, env: env)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def probe_all(home: nil, env: ENV)
|
|
45
|
+
Probe.all(home: home, env: env)
|
|
46
|
+
end
|
|
47
|
+
end
|