claude_agent 0.7.12 → 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 +45 -0
- data/CLAUDE.md +2 -0
- data/README.md +46 -1
- data/Rakefile +17 -0
- data/SPEC.md +214 -125
- 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 -882
- 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 +26 -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 -829
- 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 +144 -13
- metadata +33 -1
|
@@ -42,6 +42,7 @@ module ClaudeAgent
|
|
|
42
42
|
status tool_progress hook_response auth_status task_notification
|
|
43
43
|
hook_started hook_progress tool_use_summary task_started
|
|
44
44
|
task_progress rate_limit_event prompt_suggestion files_persisted
|
|
45
|
+
elicitation_complete local_command_output
|
|
45
46
|
].freeze
|
|
46
47
|
|
|
47
48
|
# Decomposed events — extracted content from rich message types
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ClaudeAgent
|
|
4
|
+
# Looks up a single session by ID and returns its metadata.
|
|
5
|
+
#
|
|
6
|
+
# Reuses ListSessions parsing logic to read session files from disk.
|
|
7
|
+
# Searches project directories for the session file by UUID.
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
# info = ClaudeAgent.get_session_info("abc-123-...")
|
|
11
|
+
# puts info.summary if info
|
|
12
|
+
#
|
|
13
|
+
module GetSessionInfo
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
# Look up a single session by ID.
|
|
17
|
+
#
|
|
18
|
+
# @param session_id [String] UUID of the session to look up
|
|
19
|
+
# @param dir [String, nil] Project directory to scope the search.
|
|
20
|
+
# When nil, searches all projects.
|
|
21
|
+
# @return [SessionInfo, nil] Session metadata or nil if not found
|
|
22
|
+
def call(session_id, dir: nil)
|
|
23
|
+
unless SessionPaths::UUID_PATTERN.match?(session_id.to_s)
|
|
24
|
+
raise ArgumentError, "Invalid session_id: #{session_id}. Must be a valid UUID."
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
filename = "#{session_id}.jsonl"
|
|
28
|
+
|
|
29
|
+
if dir
|
|
30
|
+
find_in_directory(session_id, filename, dir)
|
|
31
|
+
else
|
|
32
|
+
find_in_all_projects(session_id, filename)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# --- Private Helpers ---
|
|
37
|
+
|
|
38
|
+
def find_in_directory(session_id, filename, dir)
|
|
39
|
+
resolved = SessionPaths.realpath(dir)
|
|
40
|
+
project_dir = SessionPaths.find_project_dir(resolved)
|
|
41
|
+
|
|
42
|
+
if project_dir
|
|
43
|
+
result = try_parse(project_dir, filename, session_id)
|
|
44
|
+
return result if result
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Check worktrees
|
|
48
|
+
worktrees = SessionPaths.git_worktrees(resolved)
|
|
49
|
+
worktrees.each do |wt|
|
|
50
|
+
wt_project_dir = SessionPaths.find_project_dir(wt)
|
|
51
|
+
next unless wt_project_dir
|
|
52
|
+
|
|
53
|
+
result = try_parse(wt_project_dir, filename, session_id)
|
|
54
|
+
return result if result
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
nil
|
|
58
|
+
end
|
|
59
|
+
private_class_method :find_in_directory
|
|
60
|
+
|
|
61
|
+
def find_in_all_projects(session_id, filename)
|
|
62
|
+
base = SessionPaths.projects_dir
|
|
63
|
+
return nil unless File.directory?(base)
|
|
64
|
+
|
|
65
|
+
Dir.entries(base).each do |entry|
|
|
66
|
+
next if entry.start_with?(".")
|
|
67
|
+
dir_path = File.join(base, entry)
|
|
68
|
+
next unless File.directory?(dir_path)
|
|
69
|
+
|
|
70
|
+
result = try_parse(dir_path, filename, session_id)
|
|
71
|
+
return result if result
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
nil
|
|
75
|
+
end
|
|
76
|
+
private_class_method :find_in_all_projects
|
|
77
|
+
|
|
78
|
+
def try_parse(project_dir, filename, session_id)
|
|
79
|
+
path = File.join(project_dir, filename)
|
|
80
|
+
return nil unless File.exist?(path)
|
|
81
|
+
|
|
82
|
+
ListSessions.send(:parse_session_file, path, session_id)
|
|
83
|
+
end
|
|
84
|
+
private_class_method :try_parse
|
|
85
|
+
end
|
|
86
|
+
end
|
data/lib/claude_agent/hooks.rb
CHANGED
|
@@ -18,9 +18,12 @@ module ClaudeAgent
|
|
|
18
18
|
Setup
|
|
19
19
|
TeammateIdle
|
|
20
20
|
TaskCompleted
|
|
21
|
+
Elicitation
|
|
22
|
+
ElicitationResult
|
|
21
23
|
ConfigChange
|
|
22
24
|
WorktreeCreate
|
|
23
25
|
WorktreeRemove
|
|
26
|
+
InstructionsLoaded
|
|
24
27
|
].freeze
|
|
25
28
|
|
|
26
29
|
# Matcher configuration for hooks
|
|
@@ -71,14 +74,16 @@ module ClaudeAgent
|
|
|
71
74
|
# hook_event_name/base field inheritance.
|
|
72
75
|
#
|
|
73
76
|
class BaseHookInput
|
|
74
|
-
attr_reader :hook_event_name, :session_id, :transcript_path, :cwd, :permission_mode
|
|
77
|
+
attr_reader :hook_event_name, :session_id, :transcript_path, :cwd, :permission_mode, :agent_id, :agent_type
|
|
75
78
|
|
|
76
|
-
def initialize(hook_event_name:, session_id: nil, transcript_path: nil, cwd: nil, permission_mode: nil, **kwargs)
|
|
79
|
+
def initialize(hook_event_name:, session_id: nil, transcript_path: nil, cwd: nil, permission_mode: nil, agent_id: nil, agent_type: nil, **kwargs)
|
|
77
80
|
@hook_event_name = hook_event_name
|
|
78
81
|
@session_id = session_id
|
|
79
82
|
@transcript_path = transcript_path
|
|
80
83
|
@cwd = cwd
|
|
81
84
|
@permission_mode = permission_mode
|
|
85
|
+
@agent_id = agent_id
|
|
86
|
+
@agent_type = agent_type
|
|
82
87
|
end
|
|
83
88
|
|
|
84
89
|
# Define a hook input subclass declaratively
|
|
@@ -196,6 +201,14 @@ module ClaudeAgent
|
|
|
196
201
|
required: [ :task_id, :task_subject ],
|
|
197
202
|
optional: { task_description: nil, teammate_name: nil, team_name: nil }
|
|
198
203
|
|
|
204
|
+
BaseHookInput.define_input "Elicitation",
|
|
205
|
+
required: [ :mcp_server_name, :message ],
|
|
206
|
+
optional: { mode: nil, url: nil, elicitation_id: nil, requested_schema: nil }
|
|
207
|
+
|
|
208
|
+
BaseHookInput.define_input "ElicitationResult",
|
|
209
|
+
required: [ :mcp_server_name, :action ],
|
|
210
|
+
optional: { elicitation_id: nil, mode: nil, content: nil }
|
|
211
|
+
|
|
199
212
|
BaseHookInput.define_input "ConfigChange",
|
|
200
213
|
required: [ :source ],
|
|
201
214
|
optional: { file_path: nil },
|
|
@@ -206,4 +219,12 @@ module ClaudeAgent
|
|
|
206
219
|
|
|
207
220
|
BaseHookInput.define_input "WorktreeRemove",
|
|
208
221
|
required: [ :worktree_path ]
|
|
222
|
+
|
|
223
|
+
BaseHookInput.define_input "InstructionsLoaded",
|
|
224
|
+
required: [ :file_path, :memory_type, :load_reason ],
|
|
225
|
+
optional: { globs: nil, trigger_file_path: nil, parent_file_path: nil },
|
|
226
|
+
constants: {
|
|
227
|
+
MEMORY_TYPES: %w[User Project Local Managed].freeze,
|
|
228
|
+
LOAD_REASONS: %w[session_start nested_traversal path_glob_match include].freeze
|
|
229
|
+
}
|
|
209
230
|
end
|
|
@@ -31,12 +31,14 @@ module ClaudeAgent
|
|
|
31
31
|
# @param dir [String, nil] Directory to scope sessions to (with worktree support).
|
|
32
32
|
# When nil, returns sessions from all projects.
|
|
33
33
|
# @param limit [Integer, nil] Maximum number of sessions to return.
|
|
34
|
+
# @param include_worktrees [Boolean] When dir is in a git repo, include sessions
|
|
35
|
+
# from all git worktree paths. Defaults to true.
|
|
34
36
|
# @return [Array<SessionInfo>] Sessions sorted by last_modified descending.
|
|
35
|
-
def call(dir: nil, limit: nil)
|
|
37
|
+
def call(dir: nil, limit: nil, offset: nil, include_worktrees: true)
|
|
36
38
|
if dir
|
|
37
|
-
list_for_directory(dir, limit)
|
|
39
|
+
list_for_directory(dir, limit, offset: offset, include_worktrees: include_worktrees)
|
|
38
40
|
else
|
|
39
|
-
list_all(limit)
|
|
41
|
+
list_all(limit, offset: offset)
|
|
40
42
|
end
|
|
41
43
|
end
|
|
42
44
|
|
|
@@ -243,6 +245,9 @@ module ClaudeAgent
|
|
|
243
245
|
first_prompt = extract_first_prompt(head)
|
|
244
246
|
git_branch = extract_last(tail, "gitBranch") || extract_first(head, "gitBranch")
|
|
245
247
|
cwd = extract_first(head, "cwd")
|
|
248
|
+
tag = extract_last(tail, "tag")
|
|
249
|
+
timestamp_str = extract_first(head, "timestamp")
|
|
250
|
+
created_at = timestamp_str&.to_i
|
|
246
251
|
|
|
247
252
|
# Build summary: customTitle > last summary > firstPrompt > "(session)"
|
|
248
253
|
summary_from_file = extract_last(tail, "summary")
|
|
@@ -256,7 +261,9 @@ module ClaudeAgent
|
|
|
256
261
|
custom_title: custom_title,
|
|
257
262
|
first_prompt: first_prompt,
|
|
258
263
|
git_branch: git_branch,
|
|
259
|
-
cwd: cwd
|
|
264
|
+
cwd: cwd,
|
|
265
|
+
tag: tag,
|
|
266
|
+
created_at: created_at
|
|
260
267
|
)
|
|
261
268
|
end
|
|
262
269
|
|
|
@@ -288,21 +295,22 @@ module ClaudeAgent
|
|
|
288
295
|
|
|
289
296
|
# --- Listing Modes ---
|
|
290
297
|
|
|
291
|
-
# List sessions for a specific directory, including worktree siblings.
|
|
298
|
+
# List sessions for a specific directory, optionally including worktree siblings.
|
|
292
299
|
# Matches TypeScript's bM function.
|
|
293
300
|
#
|
|
294
301
|
# @param dir [String]
|
|
295
302
|
# @param limit [Integer, nil]
|
|
303
|
+
# @param include_worktrees [Boolean] Whether to include worktree sessions
|
|
296
304
|
# @return [Array<SessionInfo>]
|
|
297
|
-
def list_for_directory(dir, limit)
|
|
305
|
+
def list_for_directory(dir, limit, offset: nil, include_worktrees: true)
|
|
298
306
|
resolved = SessionPaths.realpath(dir)
|
|
299
|
-
worktrees = SessionPaths.git_worktrees(resolved)
|
|
307
|
+
worktrees = include_worktrees ? SessionPaths.git_worktrees(resolved) : []
|
|
300
308
|
|
|
301
|
-
# Simple case: not in a worktree (or single worktree)
|
|
309
|
+
# Simple case: not in a worktree (or single worktree) or worktrees disabled
|
|
302
310
|
if worktrees.length <= 1
|
|
303
311
|
project_dir = SessionPaths.find_project_dir(resolved)
|
|
304
312
|
return [] unless project_dir
|
|
305
|
-
return sort_and_limit(scan_project_dir(project_dir), limit)
|
|
313
|
+
return sort_and_limit(scan_project_dir(project_dir), limit, offset: offset)
|
|
306
314
|
end
|
|
307
315
|
|
|
308
316
|
# Complex case: multiple worktrees - scan all related project directories
|
|
@@ -341,7 +349,7 @@ module ClaudeAgent
|
|
|
341
349
|
end
|
|
342
350
|
end
|
|
343
351
|
|
|
344
|
-
sort_and_limit(deduplicate(all_sessions), limit)
|
|
352
|
+
sort_and_limit(deduplicate(all_sessions), limit, offset: offset)
|
|
345
353
|
end
|
|
346
354
|
|
|
347
355
|
# List sessions from all project directories.
|
|
@@ -349,7 +357,7 @@ module ClaudeAgent
|
|
|
349
357
|
#
|
|
350
358
|
# @param limit [Integer, nil]
|
|
351
359
|
# @return [Array<SessionInfo>]
|
|
352
|
-
def list_all(limit)
|
|
360
|
+
def list_all(limit, offset: nil)
|
|
353
361
|
base = SessionPaths.projects_dir
|
|
354
362
|
return [] unless File.directory?(base)
|
|
355
363
|
|
|
@@ -362,7 +370,7 @@ module ClaudeAgent
|
|
|
362
370
|
all_sessions.concat(scan_project_dir(dir_path))
|
|
363
371
|
end
|
|
364
372
|
|
|
365
|
-
sort_and_limit(deduplicate(all_sessions), limit)
|
|
373
|
+
sort_and_limit(deduplicate(all_sessions), limit, offset: offset)
|
|
366
374
|
end
|
|
367
375
|
|
|
368
376
|
# --- Helpers ---
|
|
@@ -389,8 +397,9 @@ module ClaudeAgent
|
|
|
389
397
|
# @param sessions [Array<SessionInfo>]
|
|
390
398
|
# @param limit [Integer, nil]
|
|
391
399
|
# @return [Array<SessionInfo>]
|
|
392
|
-
def sort_and_limit(sessions, limit)
|
|
400
|
+
def sort_and_limit(sessions, limit, offset: nil)
|
|
393
401
|
sorted = sessions.sort_by { |s| -s.last_modified }
|
|
402
|
+
sorted = sorted.drop(offset) if offset
|
|
394
403
|
limit ? sorted.first(limit) : sorted
|
|
395
404
|
end
|
|
396
405
|
end
|
|
@@ -83,7 +83,9 @@ module ClaudeAgent
|
|
|
83
83
|
register "system:hook_progress", :parse_hook_progress_message
|
|
84
84
|
register "system:files_persisted", :parse_files_persisted_event
|
|
85
85
|
register "system:task_started", :parse_task_started_message
|
|
86
|
-
register "system:task_progress",
|
|
86
|
+
register "system:task_progress", :parse_task_progress_message
|
|
87
|
+
register "system:elicitation_complete", :parse_elicitation_complete_message
|
|
88
|
+
register "system:local_command_output", :parse_local_command_output_message
|
|
87
89
|
|
|
88
90
|
private
|
|
89
91
|
|
|
@@ -167,7 +169,8 @@ module ClaudeAgent
|
|
|
167
169
|
errors: raw[:errors],
|
|
168
170
|
permission_denials: permission_denials,
|
|
169
171
|
model_usage: raw[:model_usage],
|
|
170
|
-
stop_reason: raw[:stop_reason]
|
|
172
|
+
stop_reason: raw[:stop_reason],
|
|
173
|
+
fast_mode_state: raw[:fast_mode_state]
|
|
171
174
|
)
|
|
172
175
|
end
|
|
173
176
|
|
|
@@ -368,7 +371,8 @@ module ClaudeAgent
|
|
|
368
371
|
task_id: raw[:task_id] || "",
|
|
369
372
|
tool_use_id: raw[:tool_use_id],
|
|
370
373
|
description: raw[:description],
|
|
371
|
-
task_type: raw[:task_type]
|
|
374
|
+
task_type: raw[:task_type],
|
|
375
|
+
prompt: raw[:prompt]
|
|
372
376
|
)
|
|
373
377
|
end
|
|
374
378
|
|
|
@@ -380,7 +384,25 @@ module ClaudeAgent
|
|
|
380
384
|
tool_use_id: raw[:tool_use_id],
|
|
381
385
|
description: raw[:description] || "",
|
|
382
386
|
usage: raw[:usage],
|
|
383
|
-
last_tool_name: raw[:last_tool_name]
|
|
387
|
+
last_tool_name: raw[:last_tool_name],
|
|
388
|
+
summary: raw[:summary]
|
|
389
|
+
)
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
def parse_elicitation_complete_message(raw)
|
|
393
|
+
ElicitationCompleteMessage.new(
|
|
394
|
+
uuid: raw[:uuid] || "",
|
|
395
|
+
session_id: raw[:session_id] || "",
|
|
396
|
+
mcp_server_name: raw[:mcp_server_name] || "",
|
|
397
|
+
elicitation_id: raw[:elicitation_id] || ""
|
|
398
|
+
)
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
def parse_local_command_output_message(raw)
|
|
402
|
+
LocalCommandOutputMessage.new(
|
|
403
|
+
uuid: raw[:uuid] || "",
|
|
404
|
+
session_id: raw[:session_id] || "",
|
|
405
|
+
content: raw[:content] || ""
|
|
384
406
|
)
|
|
385
407
|
end
|
|
386
408
|
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ClaudeAgent
|
|
4
|
+
# User message sent to Claude
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# msg = UserMessage.new(content: "Hello!", uuid: "abc-123", session_id: "session-abc")
|
|
8
|
+
#
|
|
9
|
+
UserMessage = Data.define(:content, :uuid, :session_id, :parent_tool_use_id) do
|
|
10
|
+
def initialize(content:, uuid: nil, session_id: nil, parent_tool_use_id: nil)
|
|
11
|
+
super
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def type
|
|
15
|
+
:user
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Get text content if content is a string
|
|
19
|
+
# @return [String, nil]
|
|
20
|
+
def text
|
|
21
|
+
content.is_a?(String) ? content : nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Check if this is a replayed message
|
|
25
|
+
# @return [Boolean]
|
|
26
|
+
def replay?
|
|
27
|
+
false
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# User message replay (TypeScript SDK parity)
|
|
32
|
+
#
|
|
33
|
+
# Sent when resuming a session with existing conversation history.
|
|
34
|
+
# These messages represent replayed user messages from a previous session.
|
|
35
|
+
#
|
|
36
|
+
# @example
|
|
37
|
+
# msg = UserMessageReplay.new(
|
|
38
|
+
# content: "Hello!",
|
|
39
|
+
# uuid: "abc-123",
|
|
40
|
+
# session_id: "session-abc",
|
|
41
|
+
# is_replay: true
|
|
42
|
+
# )
|
|
43
|
+
# msg.replay? # => true
|
|
44
|
+
#
|
|
45
|
+
UserMessageReplay = Data.define(
|
|
46
|
+
:content,
|
|
47
|
+
:uuid,
|
|
48
|
+
:session_id,
|
|
49
|
+
:parent_tool_use_id,
|
|
50
|
+
:is_replay,
|
|
51
|
+
:is_synthetic,
|
|
52
|
+
:tool_use_result
|
|
53
|
+
) do
|
|
54
|
+
def initialize(
|
|
55
|
+
content:,
|
|
56
|
+
uuid: nil,
|
|
57
|
+
session_id: nil,
|
|
58
|
+
parent_tool_use_id: nil,
|
|
59
|
+
is_replay: true,
|
|
60
|
+
is_synthetic: nil,
|
|
61
|
+
tool_use_result: nil
|
|
62
|
+
)
|
|
63
|
+
super
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def type
|
|
67
|
+
:user
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Get text content if content is a string
|
|
71
|
+
# @return [String, nil]
|
|
72
|
+
def text
|
|
73
|
+
content.is_a?(String) ? content : nil
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Check if this is a replayed message
|
|
77
|
+
# @return [Boolean]
|
|
78
|
+
def replay?
|
|
79
|
+
is_replay == true
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Check if this is a synthetic message (system-generated)
|
|
83
|
+
# @return [Boolean]
|
|
84
|
+
def synthetic?
|
|
85
|
+
is_synthetic == true
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Assistant message from Claude
|
|
90
|
+
#
|
|
91
|
+
# @example
|
|
92
|
+
# msg = AssistantMessage.new(
|
|
93
|
+
# content: [TextBlock.new(text: "Hello!")],
|
|
94
|
+
# model: "claude-sonnet-4-5-20250514",
|
|
95
|
+
# uuid: "msg-123",
|
|
96
|
+
# session_id: "session-abc"
|
|
97
|
+
# )
|
|
98
|
+
#
|
|
99
|
+
AssistantMessage = Data.define(:content, :model, :uuid, :session_id, :error, :parent_tool_use_id) do
|
|
100
|
+
def initialize(content:, model:, uuid: nil, session_id: nil, error: nil, parent_tool_use_id: nil)
|
|
101
|
+
super
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def type
|
|
105
|
+
:assistant
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Get all text content concatenated
|
|
109
|
+
# @return [String]
|
|
110
|
+
def text
|
|
111
|
+
content
|
|
112
|
+
.select { |block| block.is_a?(TextBlock) }
|
|
113
|
+
.map(&:text)
|
|
114
|
+
.join
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Get all thinking content concatenated
|
|
118
|
+
# @return [String]
|
|
119
|
+
def thinking
|
|
120
|
+
content
|
|
121
|
+
.select { |block| block.is_a?(ThinkingBlock) }
|
|
122
|
+
.map(&:thinking)
|
|
123
|
+
.join
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Get all tool use blocks
|
|
127
|
+
# @return [Array<ToolUseBlock>]
|
|
128
|
+
def tool_uses
|
|
129
|
+
content.select { |block| block.is_a?(ToolUseBlock) }
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Check if assistant wants to use a tool
|
|
133
|
+
# @return [Boolean]
|
|
134
|
+
def has_tool_use?
|
|
135
|
+
content.any? { |block| block.is_a?(ToolUseBlock) }
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ClaudeAgent
|
|
4
|
+
# Generic message for unknown/future protocol types
|
|
5
|
+
#
|
|
6
|
+
# Wraps unrecognized top-level message types so they can be inspected
|
|
7
|
+
# without crashing the application. Supports dynamic field access via
|
|
8
|
+
# `[]` and `method_missing`.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# msg = GenericMessage.new(message_type: "fancy_new", raw: { data: "hello" })
|
|
12
|
+
# msg.type # => :fancy_new
|
|
13
|
+
# msg[:data] # => "hello"
|
|
14
|
+
# msg.data # => "hello"
|
|
15
|
+
# msg.to_h # => { data: "hello" }
|
|
16
|
+
#
|
|
17
|
+
GenericMessage = Data.define(:message_type, :raw) do
|
|
18
|
+
def type
|
|
19
|
+
message_type&.to_sym || :unknown
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def to_h
|
|
23
|
+
raw
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def [](key)
|
|
27
|
+
raw[key]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def respond_to_missing?(name, include_private = false)
|
|
31
|
+
raw.key?(name) || super
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def method_missing(name, *args)
|
|
35
|
+
return raw[name] if args.empty? && raw.key?(name)
|
|
36
|
+
super
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ClaudeAgent
|
|
4
|
+
# Hook started message (TypeScript SDK parity)
|
|
5
|
+
#
|
|
6
|
+
# Sent when a hook execution starts.
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# msg = HookStartedMessage.new(
|
|
10
|
+
# uuid: "msg-123",
|
|
11
|
+
# session_id: "session-abc",
|
|
12
|
+
# hook_id: "hook-456",
|
|
13
|
+
# hook_name: "my-hook",
|
|
14
|
+
# hook_event: "PreToolUse"
|
|
15
|
+
# )
|
|
16
|
+
#
|
|
17
|
+
HookStartedMessage = Data.define(
|
|
18
|
+
:uuid,
|
|
19
|
+
:session_id,
|
|
20
|
+
:hook_id,
|
|
21
|
+
:hook_name,
|
|
22
|
+
:hook_event
|
|
23
|
+
) do
|
|
24
|
+
def initialize(
|
|
25
|
+
uuid:,
|
|
26
|
+
session_id:,
|
|
27
|
+
hook_id:,
|
|
28
|
+
hook_name:,
|
|
29
|
+
hook_event:
|
|
30
|
+
)
|
|
31
|
+
super
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def type
|
|
35
|
+
:hook_started
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Hook progress message (TypeScript SDK parity)
|
|
40
|
+
#
|
|
41
|
+
# Reports progress during hook execution.
|
|
42
|
+
#
|
|
43
|
+
# @example
|
|
44
|
+
# msg = HookProgressMessage.new(
|
|
45
|
+
# uuid: "msg-123",
|
|
46
|
+
# session_id: "session-abc",
|
|
47
|
+
# hook_id: "hook-456",
|
|
48
|
+
# hook_name: "my-hook",
|
|
49
|
+
# hook_event: "PreToolUse",
|
|
50
|
+
# stdout: "Hook output so far...",
|
|
51
|
+
# stderr: "",
|
|
52
|
+
# output: "Combined output"
|
|
53
|
+
# )
|
|
54
|
+
#
|
|
55
|
+
HookProgressMessage = Data.define(
|
|
56
|
+
:uuid,
|
|
57
|
+
:session_id,
|
|
58
|
+
:hook_id,
|
|
59
|
+
:hook_name,
|
|
60
|
+
:hook_event,
|
|
61
|
+
:stdout,
|
|
62
|
+
:stderr,
|
|
63
|
+
:output
|
|
64
|
+
) do
|
|
65
|
+
def initialize(
|
|
66
|
+
uuid:,
|
|
67
|
+
session_id:,
|
|
68
|
+
hook_id:,
|
|
69
|
+
hook_name:,
|
|
70
|
+
hook_event:,
|
|
71
|
+
stdout: "",
|
|
72
|
+
stderr: "",
|
|
73
|
+
output: ""
|
|
74
|
+
)
|
|
75
|
+
super
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def type
|
|
79
|
+
:hook_progress
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Hook response message (TypeScript SDK parity)
|
|
84
|
+
#
|
|
85
|
+
# Contains output from hook executions.
|
|
86
|
+
#
|
|
87
|
+
# @example
|
|
88
|
+
# msg = HookResponseMessage.new(
|
|
89
|
+
# uuid: "msg-123",
|
|
90
|
+
# session_id: "session-abc",
|
|
91
|
+
# hook_id: "hook-456",
|
|
92
|
+
# hook_name: "my-hook",
|
|
93
|
+
# hook_event: "PreToolUse",
|
|
94
|
+
# stdout: "Hook output",
|
|
95
|
+
# stderr: "",
|
|
96
|
+
# output: "Combined output",
|
|
97
|
+
# exit_code: 0,
|
|
98
|
+
# outcome: "success"
|
|
99
|
+
# )
|
|
100
|
+
# msg.success? # => true
|
|
101
|
+
# msg.error? # => false
|
|
102
|
+
# msg.cancelled? # => false
|
|
103
|
+
#
|
|
104
|
+
# Outcome values:
|
|
105
|
+
# - "success" - Hook completed successfully
|
|
106
|
+
# - "error" - Hook encountered an error
|
|
107
|
+
# - "cancelled" - Hook was cancelled
|
|
108
|
+
#
|
|
109
|
+
HookResponseMessage = Data.define(
|
|
110
|
+
:uuid,
|
|
111
|
+
:session_id,
|
|
112
|
+
:hook_id,
|
|
113
|
+
:hook_name,
|
|
114
|
+
:hook_event,
|
|
115
|
+
:stdout,
|
|
116
|
+
:stderr,
|
|
117
|
+
:output,
|
|
118
|
+
:exit_code,
|
|
119
|
+
:outcome
|
|
120
|
+
) do
|
|
121
|
+
def initialize(
|
|
122
|
+
uuid:,
|
|
123
|
+
session_id:,
|
|
124
|
+
hook_id: nil,
|
|
125
|
+
hook_name:,
|
|
126
|
+
hook_event:,
|
|
127
|
+
stdout: "",
|
|
128
|
+
stderr: "",
|
|
129
|
+
output: "",
|
|
130
|
+
exit_code: nil,
|
|
131
|
+
outcome: nil
|
|
132
|
+
)
|
|
133
|
+
super
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def type
|
|
137
|
+
:hook_response
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Check if hook completed successfully
|
|
141
|
+
# @return [Boolean]
|
|
142
|
+
def success?
|
|
143
|
+
outcome == "success"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Check if hook encountered an error
|
|
147
|
+
# @return [Boolean]
|
|
148
|
+
def error?
|
|
149
|
+
outcome == "error"
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Check if hook was cancelled
|
|
153
|
+
# @return [Boolean]
|
|
154
|
+
def cancelled?
|
|
155
|
+
outcome == "cancelled"
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|