claude_agent 0.7.11 → 0.7.13
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/.claude/rules/testing.md +51 -10
- data/.claude/settings.json +1 -0
- data/ARCHITECTURE.md +237 -0
- data/CHANGELOG.md +52 -0
- data/CLAUDE.md +2 -0
- data/README.md +47 -1
- data/Rakefile +17 -0
- data/SPEC.md +314 -133
- data/lib/claude_agent/client/commands.rb +225 -0
- data/lib/claude_agent/client.rb +4 -204
- data/lib/claude_agent/content_blocks/generic_block.rb +39 -0
- data/lib/claude_agent/content_blocks/image_content_block.rb +54 -0
- data/lib/claude_agent/content_blocks/server_tool_result_block.rb +22 -0
- data/lib/claude_agent/content_blocks/server_tool_use_block.rb +48 -0
- data/lib/claude_agent/content_blocks/text_block.rb +19 -0
- data/lib/claude_agent/content_blocks/thinking_block.rb +19 -0
- data/lib/claude_agent/content_blocks/tool_result_block.rb +25 -0
- data/lib/claude_agent/content_blocks/tool_use_block.rb +134 -0
- data/lib/claude_agent/content_blocks.rb +8 -335
- data/lib/claude_agent/control_protocol/commands.rb +304 -0
- data/lib/claude_agent/control_protocol/lifecycle.rb +113 -0
- data/lib/claude_agent/control_protocol/messaging.rb +166 -0
- data/lib/claude_agent/control_protocol/primitives.rb +168 -0
- data/lib/claude_agent/control_protocol/request_handling.rb +231 -0
- data/lib/claude_agent/control_protocol.rb +27 -861
- data/lib/claude_agent/event_handler.rb +1 -0
- data/lib/claude_agent/get_session_info.rb +86 -0
- data/lib/claude_agent/hooks.rb +23 -2
- data/lib/claude_agent/list_sessions.rb +22 -13
- data/lib/claude_agent/message_parser.rb +27 -4
- data/lib/claude_agent/messages/conversation.rb +138 -0
- data/lib/claude_agent/messages/generic.rb +39 -0
- data/lib/claude_agent/messages/hook_lifecycle.rb +158 -0
- data/lib/claude_agent/messages/result.rb +80 -0
- data/lib/claude_agent/messages/streaming.rb +84 -0
- data/lib/claude_agent/messages/system.rb +67 -0
- data/lib/claude_agent/messages/task_lifecycle.rb +240 -0
- data/lib/claude_agent/messages/tool_lifecycle.rb +95 -0
- data/lib/claude_agent/messages.rb +11 -827
- data/lib/claude_agent/options/serializer.rb +194 -0
- data/lib/claude_agent/options.rb +11 -176
- data/lib/claude_agent/sandbox_settings.rb +3 -0
- data/lib/claude_agent/session.rb +0 -204
- data/lib/claude_agent/session_mutations.rb +148 -0
- data/lib/claude_agent/types/mcp.rb +30 -0
- data/lib/claude_agent/types/models.rb +146 -0
- data/lib/claude_agent/types/operations.rb +38 -0
- data/lib/claude_agent/types/sessions.rb +50 -0
- data/lib/claude_agent/types/tools.rb +32 -0
- data/lib/claude_agent/types.rb +6 -264
- data/lib/claude_agent/v2_session.rb +207 -0
- data/lib/claude_agent/version.rb +1 -1
- data/lib/claude_agent.rb +37 -3
- data/sig/claude_agent.rbs +146 -13
- metadata +33 -1
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module ClaudeAgent
|
|
6
|
+
class Options
|
|
7
|
+
# Converts Options into CLI arguments and environment variables.
|
|
8
|
+
#
|
|
9
|
+
# Separated from Options to keep configuration concerns (what to store)
|
|
10
|
+
# distinct from serialization concerns (how to render for the CLI).
|
|
11
|
+
module Serializer
|
|
12
|
+
# Build CLI arguments from options
|
|
13
|
+
# @return [Array<String>] CLI arguments
|
|
14
|
+
def to_cli_args
|
|
15
|
+
[].tap do |args|
|
|
16
|
+
args.concat(system_prompt_args)
|
|
17
|
+
args.concat(model_args)
|
|
18
|
+
args.concat(tools_args)
|
|
19
|
+
args.concat(permission_args)
|
|
20
|
+
args.concat(conversation_args)
|
|
21
|
+
args.concat(limits_args)
|
|
22
|
+
args.concat(mcp_args)
|
|
23
|
+
args.concat(sandbox_args)
|
|
24
|
+
args.concat(environment_args)
|
|
25
|
+
args.concat(output_args)
|
|
26
|
+
args.concat(debug_args)
|
|
27
|
+
args.concat(extra_cli_args)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Build environment variables for CLI process
|
|
32
|
+
# @return [Hash] Environment variables
|
|
33
|
+
def to_env
|
|
34
|
+
env.dup.tap do |process_env|
|
|
35
|
+
process_env["CLAUDE_CODE_ENTRYPOINT"] = "sdk-rb"
|
|
36
|
+
process_env["CLAUDE_AGENT_SDK_VERSION"] = ClaudeAgent::VERSION
|
|
37
|
+
process_env["CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING"] = "true" if enable_file_checkpointing
|
|
38
|
+
process_env["PWD"] = cwd.to_s if cwd
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
# --- CLI Argument Builders ---
|
|
45
|
+
|
|
46
|
+
def system_prompt_args
|
|
47
|
+
[].tap do |args|
|
|
48
|
+
if system_prompt
|
|
49
|
+
case system_prompt
|
|
50
|
+
when String then args.push("--system-prompt", system_prompt)
|
|
51
|
+
when Hash then args.push("--system-prompt", JSON.generate(system_prompt))
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
args.push("--append-system-prompt", append_system_prompt) if append_system_prompt
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def model_args
|
|
59
|
+
[].tap do |args|
|
|
60
|
+
args.push("--model", model) if model
|
|
61
|
+
args.push("--fallback-model", fallback_model) if fallback_model
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def tools_args
|
|
66
|
+
[].tap do |args|
|
|
67
|
+
if tools
|
|
68
|
+
case tools
|
|
69
|
+
when Array then args.push("--tools", tools.join(","))
|
|
70
|
+
when ToolsPreset then args.push("--tools", JSON.generate(tools.to_h))
|
|
71
|
+
when Hash then args.push("--tools", JSON.generate(tools))
|
|
72
|
+
else args.push("--tools", tools.to_s)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
args.push("--allowedTools", allowed_tools.join(",")) if allowed_tools.any?
|
|
76
|
+
args.push("--disallowedTools", disallowed_tools.join(",")) if disallowed_tools.any?
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def permission_args
|
|
81
|
+
[].tap do |args|
|
|
82
|
+
args.push("--permission-mode", permission_mode) if permission_mode
|
|
83
|
+
args.push("--permission-prompt-tool", permission_prompt_tool_name) if permission_prompt_tool_name
|
|
84
|
+
args.push("--dangerously-skip-permissions") if allow_dangerously_skip_permissions
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def conversation_args
|
|
89
|
+
[].tap do |args|
|
|
90
|
+
args.push("--continue") if continue_conversation
|
|
91
|
+
args.push("--resume", resume) if resume
|
|
92
|
+
args.push("--fork-session") if fork_session
|
|
93
|
+
args.push("--resume-session-at", resume_session_at) if resume_session_at
|
|
94
|
+
args.push("--session-id", session_id) if session_id
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def limits_args
|
|
99
|
+
[].tap do |args|
|
|
100
|
+
args.push("--max-turns", max_turns.to_s) if max_turns
|
|
101
|
+
args.push("--max-budget-usd", max_budget_usd.to_s) if max_budget_usd
|
|
102
|
+
args.concat(thinking_args)
|
|
103
|
+
args.push("--max-thinking-tokens", max_thinking_tokens.to_s) if !thinking && max_thinking_tokens
|
|
104
|
+
args.push("--effort", effort) if effort
|
|
105
|
+
args.push("--strict-mcp-config") if strict_mcp_config
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def thinking_args
|
|
110
|
+
return [] unless thinking.is_a?(Hash)
|
|
111
|
+
|
|
112
|
+
type = thinking[:type] || thinking["type"]
|
|
113
|
+
case type
|
|
114
|
+
when "disabled"
|
|
115
|
+
[ "--max-thinking-tokens", "0" ]
|
|
116
|
+
when "enabled"
|
|
117
|
+
budget = thinking[:budgetTokens] || thinking[:budget_tokens] ||
|
|
118
|
+
thinking["budgetTokens"] || thinking["budget_tokens"]
|
|
119
|
+
budget ? [ "--max-thinking-tokens", budget.to_s ] : []
|
|
120
|
+
else # "adaptive" or unrecognized — omit flag, let CLI use default
|
|
121
|
+
[]
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def mcp_args
|
|
126
|
+
[].tap do |args|
|
|
127
|
+
if mcp_servers.is_a?(Hash) && mcp_servers.any?
|
|
128
|
+
external_servers = mcp_servers.reject { |_, v| v.is_a?(Hash) && v[:type] == "sdk" }
|
|
129
|
+
args.push("--mcp-config", JSON.generate(external_servers)) if external_servers.any?
|
|
130
|
+
elsif mcp_servers.is_a?(String)
|
|
131
|
+
args.push("--mcp-config", mcp_servers)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def sandbox_args
|
|
137
|
+
[].tap do |args|
|
|
138
|
+
if sandbox
|
|
139
|
+
args.push("--sandbox", JSON.generate(sandbox.to_h))
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def environment_args
|
|
145
|
+
[].tap do |args|
|
|
146
|
+
args.push("--agent", agent) if agent
|
|
147
|
+
add_dirs.each { |dir| args.push("--add-dir", dir.to_s) }
|
|
148
|
+
args.push("--setting-sources", setting_sources.join(",")) if setting_sources&.any?
|
|
149
|
+
if settings
|
|
150
|
+
case settings
|
|
151
|
+
when String then args.push("--settings", settings)
|
|
152
|
+
when Hash then args.push("--settings", JSON.generate(settings))
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
plugins.each do |plugin|
|
|
156
|
+
dir = plugin.is_a?(Hash) ? plugin[:dir] : plugin
|
|
157
|
+
args.push("--plugin-dir", dir.to_s)
|
|
158
|
+
end
|
|
159
|
+
args.push("--betas", betas.join(",")) if betas.any?
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def output_args
|
|
164
|
+
[].tap do |args|
|
|
165
|
+
args.push("--enable-file-checkpointing") if enable_file_checkpointing
|
|
166
|
+
args.push("--no-persist-session") if persist_session == false
|
|
167
|
+
args.push("--json-schema", JSON.generate(output_format)) if output_format
|
|
168
|
+
args.push("--include-partial-messages") if include_partial_messages
|
|
169
|
+
args.push("--prompt-suggestions") if prompt_suggestions
|
|
170
|
+
if agents
|
|
171
|
+
agents_hash = agents.transform_values(&:to_h)
|
|
172
|
+
args.push("--agents", JSON.generate(agents_hash))
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def debug_args
|
|
178
|
+
[].tap do |args|
|
|
179
|
+
args.push("--debug") if debug
|
|
180
|
+
args.push("--debug-file", debug_file) if debug_file
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def extra_cli_args
|
|
185
|
+
[].tap do |args|
|
|
186
|
+
extra_args.each do |key, value|
|
|
187
|
+
flag = key.to_s.start_with?("--") ? key.to_s : "--#{key}"
|
|
188
|
+
value.nil? ? args.push(flag) : args.push(flag, value.to_s)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
data/lib/claude_agent/options.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require_relative "options/serializer"
|
|
4
4
|
|
|
5
5
|
module ClaudeAgent
|
|
6
6
|
# Permission modes for tool execution (TypeScript SDK parity)
|
|
@@ -22,6 +22,8 @@ module ClaudeAgent
|
|
|
22
22
|
# )
|
|
23
23
|
#
|
|
24
24
|
class Options
|
|
25
|
+
include Serializer
|
|
26
|
+
|
|
25
27
|
# Default values for options that have non-nil defaults
|
|
26
28
|
DEFAULTS = {
|
|
27
29
|
allowed_tools: [],
|
|
@@ -52,17 +54,19 @@ module ClaudeAgent
|
|
|
52
54
|
tools allowed_tools disallowed_tools
|
|
53
55
|
system_prompt append_system_prompt
|
|
54
56
|
model fallback_model
|
|
55
|
-
permission_mode permission_prompt_tool_name can_use_tool allow_dangerously_skip_permissions
|
|
57
|
+
permission_mode permission_prompt_tool_name can_use_tool on_elicitation allow_dangerously_skip_permissions
|
|
56
58
|
permission_queue
|
|
57
59
|
continue_conversation resume fork_session resume_session_at session_id
|
|
58
60
|
max_turns max_budget_usd thinking effort max_thinking_tokens
|
|
59
61
|
strict_mcp_config mcp_servers hooks
|
|
60
62
|
sandbox cwd add_dirs env agent
|
|
61
|
-
cli_path extra_args agents setting_sources plugins
|
|
63
|
+
cli_path extra_args agents setting_sources settings plugins
|
|
62
64
|
include_partial_messages output_format enable_file_checkpointing
|
|
63
65
|
persist_session prompt_suggestions betas max_buffer_size stderr_callback
|
|
64
66
|
abort_controller spawn_claude_code_process
|
|
65
67
|
debug debug_file
|
|
68
|
+
tool_config
|
|
69
|
+
agent_progress_summaries
|
|
66
70
|
logger
|
|
67
71
|
].freeze
|
|
68
72
|
|
|
@@ -78,36 +82,6 @@ module ClaudeAgent
|
|
|
78
82
|
validate!
|
|
79
83
|
end
|
|
80
84
|
|
|
81
|
-
# Build CLI arguments from options
|
|
82
|
-
# @return [Array<String>] CLI arguments
|
|
83
|
-
def to_cli_args
|
|
84
|
-
[].tap do |args|
|
|
85
|
-
args.concat(system_prompt_args)
|
|
86
|
-
args.concat(model_args)
|
|
87
|
-
args.concat(tools_args)
|
|
88
|
-
args.concat(permission_args)
|
|
89
|
-
args.concat(conversation_args)
|
|
90
|
-
args.concat(limits_args)
|
|
91
|
-
args.concat(mcp_args)
|
|
92
|
-
args.concat(sandbox_args)
|
|
93
|
-
args.concat(environment_args)
|
|
94
|
-
args.concat(output_args)
|
|
95
|
-
args.concat(debug_args)
|
|
96
|
-
args.concat(extra_cli_args)
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
# Build environment variables for CLI process
|
|
101
|
-
# @return [Hash] Environment variables
|
|
102
|
-
def to_env
|
|
103
|
-
env.dup.tap do |process_env|
|
|
104
|
-
process_env["CLAUDE_CODE_ENTRYPOINT"] = "sdk-rb"
|
|
105
|
-
process_env["CLAUDE_AGENT_SDK_VERSION"] = ClaudeAgent::VERSION
|
|
106
|
-
process_env["CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING"] = "true" if enable_file_checkpointing
|
|
107
|
-
process_env["PWD"] = cwd.to_s if cwd
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
|
|
111
85
|
# Check if SDK MCP servers are configured
|
|
112
86
|
# @return [Boolean]
|
|
113
87
|
def has_sdk_mcp_servers?
|
|
@@ -136,149 +110,6 @@ module ClaudeAgent
|
|
|
136
110
|
|
|
137
111
|
private
|
|
138
112
|
|
|
139
|
-
# --- CLI Argument Builders ---
|
|
140
|
-
|
|
141
|
-
def system_prompt_args
|
|
142
|
-
[].tap do |args|
|
|
143
|
-
if system_prompt
|
|
144
|
-
case system_prompt
|
|
145
|
-
when String then args.push("--system-prompt", system_prompt)
|
|
146
|
-
when Hash then args.push("--system-prompt", JSON.generate(system_prompt))
|
|
147
|
-
end
|
|
148
|
-
end
|
|
149
|
-
args.push("--append-system-prompt", append_system_prompt) if append_system_prompt
|
|
150
|
-
end
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
def model_args
|
|
154
|
-
[].tap do |args|
|
|
155
|
-
args.push("--model", model) if model
|
|
156
|
-
args.push("--fallback-model", fallback_model) if fallback_model
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
def tools_args
|
|
161
|
-
[].tap do |args|
|
|
162
|
-
if tools
|
|
163
|
-
case tools
|
|
164
|
-
when Array then args.push("--tools", tools.join(","))
|
|
165
|
-
when ToolsPreset then args.push("--tools", JSON.generate(tools.to_h))
|
|
166
|
-
when Hash then args.push("--tools", JSON.generate(tools))
|
|
167
|
-
else args.push("--tools", tools.to_s)
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
args.push("--allowedTools", allowed_tools.join(",")) if allowed_tools.any?
|
|
171
|
-
args.push("--disallowedTools", disallowed_tools.join(",")) if disallowed_tools.any?
|
|
172
|
-
end
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
def permission_args
|
|
176
|
-
[].tap do |args|
|
|
177
|
-
args.push("--permission-mode", permission_mode) if permission_mode
|
|
178
|
-
args.push("--permission-prompt-tool", permission_prompt_tool_name) if permission_prompt_tool_name
|
|
179
|
-
args.push("--dangerously-skip-permissions") if allow_dangerously_skip_permissions
|
|
180
|
-
end
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
def conversation_args
|
|
184
|
-
[].tap do |args|
|
|
185
|
-
args.push("--continue") if continue_conversation
|
|
186
|
-
args.push("--resume", resume) if resume
|
|
187
|
-
args.push("--fork-session") if fork_session
|
|
188
|
-
args.push("--resume-session-at", resume_session_at) if resume_session_at
|
|
189
|
-
args.push("--session-id", session_id) if session_id
|
|
190
|
-
end
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
def limits_args
|
|
194
|
-
[].tap do |args|
|
|
195
|
-
args.push("--max-turns", max_turns.to_s) if max_turns
|
|
196
|
-
args.push("--max-budget-usd", max_budget_usd.to_s) if max_budget_usd
|
|
197
|
-
args.concat(thinking_args)
|
|
198
|
-
args.push("--max-thinking-tokens", max_thinking_tokens.to_s) if !thinking && max_thinking_tokens
|
|
199
|
-
args.push("--effort", effort) if effort
|
|
200
|
-
args.push("--strict-mcp-config") if strict_mcp_config
|
|
201
|
-
end
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
def thinking_args
|
|
205
|
-
return [] unless thinking.is_a?(Hash)
|
|
206
|
-
|
|
207
|
-
type = thinking[:type] || thinking["type"]
|
|
208
|
-
case type
|
|
209
|
-
when "disabled"
|
|
210
|
-
[ "--max-thinking-tokens", "0" ]
|
|
211
|
-
when "enabled"
|
|
212
|
-
budget = thinking[:budgetTokens] || thinking[:budget_tokens] ||
|
|
213
|
-
thinking["budgetTokens"] || thinking["budget_tokens"]
|
|
214
|
-
budget ? [ "--max-thinking-tokens", budget.to_s ] : []
|
|
215
|
-
else # "adaptive" or unrecognized — omit flag, let CLI use default
|
|
216
|
-
[]
|
|
217
|
-
end
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
def mcp_args
|
|
221
|
-
[].tap do |args|
|
|
222
|
-
if mcp_servers.is_a?(Hash) && mcp_servers.any?
|
|
223
|
-
external_servers = mcp_servers.reject { |_, v| v.is_a?(Hash) && v[:type] == "sdk" }
|
|
224
|
-
args.push("--mcp-config", JSON.generate(external_servers)) if external_servers.any?
|
|
225
|
-
elsif mcp_servers.is_a?(String)
|
|
226
|
-
args.push("--mcp-config", mcp_servers)
|
|
227
|
-
end
|
|
228
|
-
end
|
|
229
|
-
end
|
|
230
|
-
|
|
231
|
-
def sandbox_args
|
|
232
|
-
[].tap do |args|
|
|
233
|
-
if sandbox
|
|
234
|
-
args.push("--sandbox", JSON.generate(sandbox.to_h))
|
|
235
|
-
end
|
|
236
|
-
end
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
def environment_args
|
|
240
|
-
[].tap do |args|
|
|
241
|
-
args.push("--agent", agent) if agent
|
|
242
|
-
add_dirs.each { |dir| args.push("--add-dir", dir.to_s) }
|
|
243
|
-
args.push("--setting-sources", setting_sources.join(",")) if setting_sources&.any?
|
|
244
|
-
plugins.each do |plugin|
|
|
245
|
-
dir = plugin.is_a?(Hash) ? plugin[:dir] : plugin
|
|
246
|
-
args.push("--plugin-dir", dir.to_s)
|
|
247
|
-
end
|
|
248
|
-
args.push("--betas", betas.join(",")) if betas.any?
|
|
249
|
-
end
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
def output_args
|
|
253
|
-
[].tap do |args|
|
|
254
|
-
args.push("--enable-file-checkpointing") if enable_file_checkpointing
|
|
255
|
-
args.push("--no-persist-session") if persist_session == false
|
|
256
|
-
args.push("--json-schema", JSON.generate(output_format)) if output_format
|
|
257
|
-
args.push("--include-partial-messages") if include_partial_messages
|
|
258
|
-
args.push("--prompt-suggestions") if prompt_suggestions
|
|
259
|
-
if agents
|
|
260
|
-
agents_hash = agents.transform_values(&:to_h)
|
|
261
|
-
args.push("--agents", JSON.generate(agents_hash))
|
|
262
|
-
end
|
|
263
|
-
end
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
def debug_args
|
|
267
|
-
[].tap do |args|
|
|
268
|
-
args.push("--debug") if debug
|
|
269
|
-
args.push("--debug-file", debug_file) if debug_file
|
|
270
|
-
end
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
def extra_cli_args
|
|
274
|
-
[].tap do |args|
|
|
275
|
-
extra_args.each do |key, value|
|
|
276
|
-
flag = key.to_s.start_with?("--") ? key.to_s : "--#{key}"
|
|
277
|
-
value.nil? ? args.push(flag) : args.push(flag, value.to_s)
|
|
278
|
-
end
|
|
279
|
-
end
|
|
280
|
-
end
|
|
281
|
-
|
|
282
113
|
# --- Validation ---
|
|
283
114
|
|
|
284
115
|
def validate!
|
|
@@ -295,6 +126,10 @@ module ClaudeAgent
|
|
|
295
126
|
raise ConfigurationError, "can_use_tool must be callable (Proc, Lambda, or object responding to #call)"
|
|
296
127
|
end
|
|
297
128
|
|
|
129
|
+
if on_elicitation && !on_elicitation.respond_to?(:call)
|
|
130
|
+
raise ConfigurationError, "on_elicitation must be callable (Proc, Lambda, or object responding to #call)"
|
|
131
|
+
end
|
|
132
|
+
|
|
298
133
|
# Auto-set permission_prompt_tool_name to "stdio" when can_use_tool or
|
|
299
134
|
# permission_queue is configured, so the CLI routes permission prompts
|
|
300
135
|
# through the control protocol instead of interactive terminal prompts
|
|
@@ -135,6 +135,7 @@ module ClaudeAgent
|
|
|
135
135
|
:network,
|
|
136
136
|
:ignore_violations,
|
|
137
137
|
:enable_weaker_nested_sandbox,
|
|
138
|
+
:enable_weaker_network_isolation,
|
|
138
139
|
:ripgrep,
|
|
139
140
|
:filesystem
|
|
140
141
|
) do
|
|
@@ -146,6 +147,7 @@ module ClaudeAgent
|
|
|
146
147
|
network: nil,
|
|
147
148
|
ignore_violations: nil,
|
|
148
149
|
enable_weaker_nested_sandbox: false,
|
|
150
|
+
enable_weaker_network_isolation: false,
|
|
149
151
|
ripgrep: nil,
|
|
150
152
|
filesystem: nil
|
|
151
153
|
)
|
|
@@ -160,6 +162,7 @@ module ClaudeAgent
|
|
|
160
162
|
result[:network] = network.to_h if network && !network.to_h.empty?
|
|
161
163
|
result[:ignoreViolations] = ignore_violations.to_h if ignore_violations && !ignore_violations.to_h.empty?
|
|
162
164
|
result[:enableWeakerNestedSandbox] = enable_weaker_nested_sandbox if enable_weaker_nested_sandbox
|
|
165
|
+
result[:enableWeakerNetworkIsolation] = enable_weaker_network_isolation if enable_weaker_network_isolation
|
|
163
166
|
result[:ripgrep] = ripgrep.to_h if ripgrep
|
|
164
167
|
result[:filesystem] = filesystem.to_h if filesystem && !filesystem.to_h.empty?
|
|
165
168
|
result
|
data/lib/claude_agent/session.rb
CHANGED
|
@@ -1,210 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module ClaudeAgent
|
|
4
|
-
# V2 Session options (subset of full Options)
|
|
5
|
-
# V2 API - UNSTABLE
|
|
6
|
-
# @alpha
|
|
7
|
-
#
|
|
8
|
-
# @example
|
|
9
|
-
# options = SessionOptions.new(
|
|
10
|
-
# model: "claude-sonnet-4-5-20250929",
|
|
11
|
-
# permission_mode: "acceptEdits"
|
|
12
|
-
# )
|
|
13
|
-
#
|
|
14
|
-
SessionOptions = Data.define(
|
|
15
|
-
:model,
|
|
16
|
-
:path_to_claude_code_executable,
|
|
17
|
-
:env,
|
|
18
|
-
:allowed_tools,
|
|
19
|
-
:disallowed_tools,
|
|
20
|
-
:can_use_tool,
|
|
21
|
-
:hooks,
|
|
22
|
-
:permission_mode
|
|
23
|
-
) do
|
|
24
|
-
def initialize(
|
|
25
|
-
model:,
|
|
26
|
-
path_to_claude_code_executable: nil,
|
|
27
|
-
env: nil,
|
|
28
|
-
allowed_tools: nil,
|
|
29
|
-
disallowed_tools: nil,
|
|
30
|
-
can_use_tool: nil,
|
|
31
|
-
hooks: nil,
|
|
32
|
-
permission_mode: nil
|
|
33
|
-
)
|
|
34
|
-
super
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# V2 API - UNSTABLE
|
|
39
|
-
# Multi-turn session interface for persistent conversations.
|
|
40
|
-
#
|
|
41
|
-
# This provides a simpler interface than the full Client class,
|
|
42
|
-
# matching the TypeScript SDK's SDKSession interface.
|
|
43
|
-
#
|
|
44
|
-
# @alpha
|
|
45
|
-
#
|
|
46
|
-
# @example Create a session and send messages
|
|
47
|
-
# session = ClaudeAgent.unstable_v2_create_session(model: "claude-sonnet-4-5-20250929")
|
|
48
|
-
# session.send("Hello!")
|
|
49
|
-
# session.stream.each { |msg| puts msg.inspect }
|
|
50
|
-
# session.close
|
|
51
|
-
#
|
|
52
|
-
class V2Session
|
|
53
|
-
attr_reader :session_id, :options
|
|
54
|
-
|
|
55
|
-
def initialize(options)
|
|
56
|
-
@options = options.is_a?(SessionOptions) ? options : SessionOptions.new(**options)
|
|
57
|
-
@client = nil
|
|
58
|
-
@session_id = nil
|
|
59
|
-
@closed = false
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# Send a message to the agent
|
|
63
|
-
#
|
|
64
|
-
# @param message [String, UserMessage] The message to send
|
|
65
|
-
# @return [void]
|
|
66
|
-
def send(message)
|
|
67
|
-
ensure_connected!
|
|
68
|
-
content = message.is_a?(String) ? message : message
|
|
69
|
-
@client.send_message(content)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# Stream messages from the agent
|
|
73
|
-
#
|
|
74
|
-
# @return [Enumerator<message>] An enumerator of messages
|
|
75
|
-
# @yield [message] Each message received from the agent
|
|
76
|
-
def stream(&block)
|
|
77
|
-
ensure_connected!
|
|
78
|
-
if block_given?
|
|
79
|
-
@client.receive_response(&block)
|
|
80
|
-
else
|
|
81
|
-
@client.receive_response
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
# Close the session
|
|
86
|
-
#
|
|
87
|
-
# @return [void]
|
|
88
|
-
def close
|
|
89
|
-
return if @closed
|
|
90
|
-
@client&.disconnect
|
|
91
|
-
@closed = true
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
# Check if session is closed
|
|
95
|
-
#
|
|
96
|
-
# @return [Boolean]
|
|
97
|
-
def closed?
|
|
98
|
-
@closed
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
private
|
|
102
|
-
|
|
103
|
-
def ensure_connected!
|
|
104
|
-
raise AbortError, "Session is closed" if @closed
|
|
105
|
-
return if @client&.connected?
|
|
106
|
-
|
|
107
|
-
@client = Client.new(options: build_client_options)
|
|
108
|
-
@client.connect
|
|
109
|
-
update_session_id
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def build_client_options
|
|
113
|
-
Options.new(
|
|
114
|
-
model: @options.model,
|
|
115
|
-
cli_path: @options.path_to_claude_code_executable,
|
|
116
|
-
env: @options.env,
|
|
117
|
-
allowed_tools: @options.allowed_tools,
|
|
118
|
-
disallowed_tools: @options.disallowed_tools,
|
|
119
|
-
can_use_tool: @options.can_use_tool,
|
|
120
|
-
hooks: @options.hooks,
|
|
121
|
-
permission_mode: @options.permission_mode
|
|
122
|
-
)
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def update_session_id
|
|
126
|
-
# Session ID is typically extracted from the first system message
|
|
127
|
-
# but since we don't have it immediately, we leave it nil until available
|
|
128
|
-
@session_id = @client.server_info&.dig("session_id")
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
class << self
|
|
133
|
-
# V2 API - UNSTABLE
|
|
134
|
-
# Create a persistent session for multi-turn conversations.
|
|
135
|
-
#
|
|
136
|
-
# @param options [Hash, SessionOptions] Session configuration
|
|
137
|
-
# @return [Session] A new session instance
|
|
138
|
-
# @alpha
|
|
139
|
-
#
|
|
140
|
-
# @example
|
|
141
|
-
# session = ClaudeAgent.unstable_v2_create_session(model: "claude-sonnet-4-5-20250929")
|
|
142
|
-
#
|
|
143
|
-
def unstable_v2_create_session(options)
|
|
144
|
-
V2Session.new(options)
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
# V2 API - UNSTABLE
|
|
148
|
-
# Resume an existing session by ID.
|
|
149
|
-
#
|
|
150
|
-
# @param session_id [String] The session ID to resume
|
|
151
|
-
# @param options [Hash, SessionOptions] Session configuration
|
|
152
|
-
# @return [Session] A session configured to resume the specified session
|
|
153
|
-
# @alpha
|
|
154
|
-
#
|
|
155
|
-
# @example
|
|
156
|
-
# session = ClaudeAgent.unstable_v2_resume_session("session-abc123", model: "claude-sonnet-4-5-20250929")
|
|
157
|
-
#
|
|
158
|
-
def unstable_v2_resume_session(session_id, options)
|
|
159
|
-
# For resumption, we need to pass the resume option through
|
|
160
|
-
# Since SessionOptions doesn't have resume, we handle it in the Client options
|
|
161
|
-
session = V2Session.new(options)
|
|
162
|
-
session.instance_variable_set(:@resume_session_id, session_id)
|
|
163
|
-
|
|
164
|
-
# Override build_client_options to include resume
|
|
165
|
-
session.define_singleton_method(:build_client_options) do
|
|
166
|
-
Options.new(
|
|
167
|
-
model: @options.model,
|
|
168
|
-
cli_path: @options.path_to_claude_code_executable,
|
|
169
|
-
env: @options.env,
|
|
170
|
-
allowed_tools: @options.allowed_tools,
|
|
171
|
-
disallowed_tools: @options.disallowed_tools,
|
|
172
|
-
can_use_tool: @options.can_use_tool,
|
|
173
|
-
hooks: @options.hooks,
|
|
174
|
-
permission_mode: @options.permission_mode,
|
|
175
|
-
resume: @resume_session_id
|
|
176
|
-
)
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
session
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
# V2 API - UNSTABLE
|
|
183
|
-
# One-shot convenience function for single prompts.
|
|
184
|
-
#
|
|
185
|
-
# @param message [String] The prompt message
|
|
186
|
-
# @param options [Hash, SessionOptions] Session configuration
|
|
187
|
-
# @return [ResultMessage] The result of the query
|
|
188
|
-
# @alpha
|
|
189
|
-
#
|
|
190
|
-
# @example
|
|
191
|
-
# result = ClaudeAgent.unstable_v2_prompt("What files are here?", model: "claude-sonnet-4-5-20250929")
|
|
192
|
-
#
|
|
193
|
-
def unstable_v2_prompt(message, options)
|
|
194
|
-
session = unstable_v2_create_session(options)
|
|
195
|
-
begin
|
|
196
|
-
session.send(message)
|
|
197
|
-
result = nil
|
|
198
|
-
session.stream.each do |msg|
|
|
199
|
-
result = msg if msg.is_a?(ResultMessage)
|
|
200
|
-
end
|
|
201
|
-
result
|
|
202
|
-
ensure
|
|
203
|
-
session.close
|
|
204
|
-
end
|
|
205
|
-
end
|
|
206
|
-
end
|
|
207
|
-
|
|
208
4
|
# Historical session finder with Rails-like API.
|
|
209
5
|
#
|
|
210
6
|
# Wraps SessionInfo with a rich interface for finding sessions
|