claude_agent 0.7.8 → 0.7.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +73 -0
- data/README.md +603 -47
- data/SPEC.md +92 -28
- data/lib/claude_agent/client.rb +181 -7
- data/lib/claude_agent/content_blocks.rb +193 -5
- data/lib/claude_agent/control_protocol.rb +97 -11
- data/lib/claude_agent/conversation.rb +248 -0
- data/lib/claude_agent/cumulative_usage.rb +106 -0
- data/lib/claude_agent/event_handler.rb +152 -0
- data/lib/claude_agent/get_session_messages.rb +236 -0
- data/lib/claude_agent/hooks.rb +104 -253
- data/lib/claude_agent/list_sessions.rb +398 -0
- data/lib/claude_agent/mcp/server.rb +3 -3
- data/lib/claude_agent/mcp/tool.rb +4 -4
- data/lib/claude_agent/message_parser.rb +201 -185
- data/lib/claude_agent/messages.rb +86 -13
- data/lib/claude_agent/options.rb +5 -4
- data/lib/claude_agent/permission_queue.rb +87 -0
- data/lib/claude_agent/permission_request.rb +151 -0
- data/lib/claude_agent/permissions.rb +4 -2
- data/lib/claude_agent/query.rb +34 -0
- data/lib/claude_agent/session.rb +71 -3
- data/lib/claude_agent/session_message_relation.rb +59 -0
- data/lib/claude_agent/session_paths.rb +120 -0
- data/lib/claude_agent/tool_activity.rb +78 -0
- data/lib/claude_agent/turn_result.rb +239 -0
- data/lib/claude_agent/types.rb +45 -0
- data/lib/claude_agent/version.rb +1 -1
- data/lib/claude_agent.rb +58 -2
- data/sig/claude_agent.rbs +336 -7
- metadata +12 -1
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "timeout"
|
|
4
|
+
|
|
5
|
+
module ClaudeAgent
|
|
6
|
+
# Shared path/directory infrastructure for session discovery.
|
|
7
|
+
#
|
|
8
|
+
# Provides methods to locate Claude Code session files on disk,
|
|
9
|
+
# encode project directory paths, and resolve git worktrees.
|
|
10
|
+
# Used by both ListSessions and GetSessionMessages.
|
|
11
|
+
#
|
|
12
|
+
module SessionPaths
|
|
13
|
+
MAX_SLUG_LENGTH = 200
|
|
14
|
+
UUID_PATTERN = /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i
|
|
15
|
+
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
# @return [String] Claude config directory
|
|
19
|
+
def config_dir
|
|
20
|
+
(ENV["CLAUDE_CONFIG_DIR"] || File.join(Dir.home, ".claude"))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @return [String] Projects directory within config
|
|
24
|
+
def projects_dir
|
|
25
|
+
File.join(config_dir, "projects")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Get the expected project directory path for a given working directory.
|
|
29
|
+
#
|
|
30
|
+
# @param path [String] Working directory
|
|
31
|
+
# @return [String] Full path to project sessions directory
|
|
32
|
+
def project_dir_for(path)
|
|
33
|
+
File.join(projects_dir, encode_project_dir(path))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Encode a project directory path to a slug for the projects directory.
|
|
37
|
+
# Matches the TypeScript SDK's q9 function exactly.
|
|
38
|
+
#
|
|
39
|
+
# @param path [String] Absolute directory path
|
|
40
|
+
# @return [String] Encoded slug
|
|
41
|
+
def encode_project_dir(path)
|
|
42
|
+
slug = path.gsub(/[^a-zA-Z0-9]/, "-")
|
|
43
|
+
return slug if slug.length <= MAX_SLUG_LENGTH
|
|
44
|
+
|
|
45
|
+
hash = java_string_hash(path)
|
|
46
|
+
"#{slug[0, MAX_SLUG_LENGTH]}-#{hash}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Java-style string hash (matches TypeScript DM function).
|
|
50
|
+
# Computes hash = ((hash << 5) - hash + charCode) as 32-bit signed integer,
|
|
51
|
+
# then returns absolute value in base 36.
|
|
52
|
+
#
|
|
53
|
+
# @param str [String]
|
|
54
|
+
# @return [String] Base-36 hash
|
|
55
|
+
def java_string_hash(str)
|
|
56
|
+
hash = 0
|
|
57
|
+
str.each_char do |c|
|
|
58
|
+
hash = ((hash << 5) - hash + c.ord) & 0xFFFFFFFF
|
|
59
|
+
# Convert to signed 32-bit integer
|
|
60
|
+
hash -= 0x100000000 if hash >= 0x80000000
|
|
61
|
+
end
|
|
62
|
+
hash.abs.to_s(36)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Look up the project directory for a given path, handling hash suffix fallback.
|
|
66
|
+
# Matches TypeScript's tQ function.
|
|
67
|
+
#
|
|
68
|
+
# @param path [String] Working directory
|
|
69
|
+
# @return [String, nil] Project directory path or nil
|
|
70
|
+
def find_project_dir(path)
|
|
71
|
+
expected = project_dir_for(path)
|
|
72
|
+
return expected if File.directory?(expected)
|
|
73
|
+
|
|
74
|
+
# Try prefix matching for hash-suffixed directories
|
|
75
|
+
slug = encode_project_dir(path)
|
|
76
|
+
return nil if slug.length <= MAX_SLUG_LENGTH
|
|
77
|
+
|
|
78
|
+
prefix = slug[0, MAX_SLUG_LENGTH]
|
|
79
|
+
base = projects_dir
|
|
80
|
+
return nil unless File.directory?(base)
|
|
81
|
+
|
|
82
|
+
Dir.entries(base).each do |entry|
|
|
83
|
+
next if entry.start_with?(".")
|
|
84
|
+
next unless File.directory?(File.join(base, entry))
|
|
85
|
+
return File.join(base, entry) if entry.start_with?("#{prefix}-")
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
nil
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Resolve symlinks and normalize a path.
|
|
92
|
+
#
|
|
93
|
+
# @param path [String]
|
|
94
|
+
# @return [String]
|
|
95
|
+
def realpath(path)
|
|
96
|
+
File.realpath(path).unicode_normalize(:nfc)
|
|
97
|
+
rescue SystemCallError
|
|
98
|
+
path.unicode_normalize(:nfc)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Get git worktree paths for a directory.
|
|
102
|
+
#
|
|
103
|
+
# @param dir [String] Working directory
|
|
104
|
+
# @return [Array<String>] Worktree paths
|
|
105
|
+
def git_worktrees(dir)
|
|
106
|
+
output = nil
|
|
107
|
+
IO.popen([ "git", "worktree", "list", "--porcelain" ], chdir: dir, err: File::NULL) do |io|
|
|
108
|
+
Timeout.timeout(5) { output = io.read }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
return [] unless output
|
|
112
|
+
|
|
113
|
+
output.lines
|
|
114
|
+
.select { |line| line.start_with?("worktree ") }
|
|
115
|
+
.map { |line| line[9..].strip.unicode_normalize(:nfc) }
|
|
116
|
+
rescue SystemCallError, Timeout::Error, Errno::ENOENT
|
|
117
|
+
[]
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ClaudeAgent
|
|
4
|
+
# A single tool execution in the conversation timeline.
|
|
5
|
+
#
|
|
6
|
+
# Pairs a ToolUseBlock with its ToolResultBlock and adds context
|
|
7
|
+
# about which turn it occurred in and when.
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
# conversation.tool_activity.each do |activity|
|
|
11
|
+
# puts activity.display_label
|
|
12
|
+
# puts " Turn: #{activity.turn_index}"
|
|
13
|
+
# puts " Duration: #{activity.duration}s" if activity.duration
|
|
14
|
+
# puts " Error!" if activity.error?
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
ToolActivity = Data.define(
|
|
18
|
+
:tool_use,
|
|
19
|
+
:tool_result,
|
|
20
|
+
:turn_index,
|
|
21
|
+
:started_at,
|
|
22
|
+
:completed_at
|
|
23
|
+
) do
|
|
24
|
+
def initialize(tool_use:, tool_result: nil, turn_index:, started_at: nil, completed_at: nil)
|
|
25
|
+
super
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Tool name
|
|
29
|
+
# @return [String]
|
|
30
|
+
def name
|
|
31
|
+
tool_use.name
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Human-readable label (delegates to ToolUseBlock#display_label)
|
|
35
|
+
# @return [String]
|
|
36
|
+
def display_label
|
|
37
|
+
tool_use.display_label
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Detailed summary (delegates to ToolUseBlock#summary)
|
|
41
|
+
# @param max [Integer] Maximum length
|
|
42
|
+
# @return [String]
|
|
43
|
+
def summary(max: 60)
|
|
44
|
+
tool_use.summary(max: max)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# File path if this is a file-based tool
|
|
48
|
+
# @return [String, nil]
|
|
49
|
+
def file_path
|
|
50
|
+
tool_use.file_path
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Tool use ID
|
|
54
|
+
# @return [String]
|
|
55
|
+
def id
|
|
56
|
+
tool_use.id
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Whether the tool produced an error result
|
|
60
|
+
# @return [Boolean]
|
|
61
|
+
def error?
|
|
62
|
+
tool_result&.is_error == true
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Whether the tool execution is complete (has a result)
|
|
66
|
+
# @return [Boolean]
|
|
67
|
+
def complete?
|
|
68
|
+
!tool_result.nil?
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Duration in seconds (nil if timing not available)
|
|
72
|
+
# @return [Float, nil]
|
|
73
|
+
def duration
|
|
74
|
+
return nil unless started_at && completed_at
|
|
75
|
+
completed_at - started_at
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ClaudeAgent
|
|
4
|
+
# Represents a complete agent turn — everything from sending a prompt
|
|
5
|
+
# to receiving the final ResultMessage.
|
|
6
|
+
#
|
|
7
|
+
# Accumulates messages as they flow through and provides convenient
|
|
8
|
+
# accessors for text, tool use, usage data, and more. Eliminates the
|
|
9
|
+
# 5+ message type `case` statement that every app rewrites.
|
|
10
|
+
#
|
|
11
|
+
# @example Via Client
|
|
12
|
+
# turn = client.send_and_receive("Fix the bug in auth.rb")
|
|
13
|
+
# puts turn.text
|
|
14
|
+
# puts "Cost: $#{turn.cost}"
|
|
15
|
+
# puts "Tools used: #{turn.tool_uses.map(&:display_label).join(", ")}"
|
|
16
|
+
#
|
|
17
|
+
# @example With streaming block
|
|
18
|
+
# turn = client.send_and_receive("Fix the bug") do |msg|
|
|
19
|
+
# case msg
|
|
20
|
+
# when ClaudeAgent::AssistantMessage
|
|
21
|
+
# print msg.text
|
|
22
|
+
# end
|
|
23
|
+
# end
|
|
24
|
+
# puts "\nDone! #{turn.tool_uses.size} tools used"
|
|
25
|
+
#
|
|
26
|
+
# @example Via one-shot query
|
|
27
|
+
# turn = ClaudeAgent.query_turn(prompt: "What is 2+2?")
|
|
28
|
+
# puts turn.text
|
|
29
|
+
#
|
|
30
|
+
class TurnResult
|
|
31
|
+
# All messages received during this turn
|
|
32
|
+
# @return [Array<Message>]
|
|
33
|
+
attr_reader :messages
|
|
34
|
+
|
|
35
|
+
def initialize
|
|
36
|
+
@messages = []
|
|
37
|
+
@result = nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Append a message to this turn
|
|
41
|
+
#
|
|
42
|
+
# @param message [Message] Any SDK message
|
|
43
|
+
# @return [self]
|
|
44
|
+
def <<(message)
|
|
45
|
+
@messages << message
|
|
46
|
+
@result = message if message.is_a?(ResultMessage)
|
|
47
|
+
self
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# The final ResultMessage, or nil if the turn is still in progress
|
|
51
|
+
# @return [ResultMessage, nil]
|
|
52
|
+
def result
|
|
53
|
+
@result
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Whether a ResultMessage has been received
|
|
57
|
+
# @return [Boolean]
|
|
58
|
+
def complete?
|
|
59
|
+
!@result.nil?
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# --- Text & Thinking ---
|
|
63
|
+
|
|
64
|
+
# All text content concatenated across assistant messages
|
|
65
|
+
# @return [String]
|
|
66
|
+
def text
|
|
67
|
+
assistant_messages.map(&:text).join
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# All thinking content concatenated across assistant messages
|
|
71
|
+
# @return [String]
|
|
72
|
+
def thinking
|
|
73
|
+
assistant_messages.map(&:thinking).join
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# --- Tool Use ---
|
|
77
|
+
|
|
78
|
+
# All tool use blocks across all assistant messages
|
|
79
|
+
# @return [Array<ToolUseBlock, ServerToolUseBlock>]
|
|
80
|
+
def tool_uses
|
|
81
|
+
assistant_messages.flat_map { |m| m.content.select { |b| b.is_a?(ToolUseBlock) || b.is_a?(ServerToolUseBlock) } }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# All tool result blocks from user messages (system-generated tool responses)
|
|
85
|
+
# @return [Array<ToolResultBlock, ServerToolResultBlock>]
|
|
86
|
+
def tool_results
|
|
87
|
+
user_messages
|
|
88
|
+
.select { |m| m.content.is_a?(Array) }
|
|
89
|
+
.flat_map { |m| m.content.select { |b| b.is_a?(ToolResultBlock) || b.is_a?(ServerToolResultBlock) } }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Tool use/result pairs matched by ID
|
|
93
|
+
#
|
|
94
|
+
# Each entry is a Hash with:
|
|
95
|
+
# - `:tool_use` — the ToolUseBlock or ServerToolUseBlock
|
|
96
|
+
# - `:tool_result` — the matching ToolResultBlock/ServerToolResultBlock (nil if not yet received)
|
|
97
|
+
#
|
|
98
|
+
# @return [Array<Hash>]
|
|
99
|
+
def tool_executions
|
|
100
|
+
results_by_id = tool_results.each_with_object({}) { |r, h| h[r.tool_use_id] = r }
|
|
101
|
+
tool_uses.map do |tool_use|
|
|
102
|
+
{ tool_use: tool_use, tool_result: results_by_id[tool_use.id] }
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# --- Result Accessors ---
|
|
107
|
+
|
|
108
|
+
# Token usage from the ResultMessage
|
|
109
|
+
# @return [Hash, nil]
|
|
110
|
+
def usage
|
|
111
|
+
@result&.usage
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Total cost in USD
|
|
115
|
+
# @return [Float, nil]
|
|
116
|
+
def cost
|
|
117
|
+
@result&.total_cost_usd
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Wall-clock duration in milliseconds
|
|
121
|
+
# @return [Integer, nil]
|
|
122
|
+
def duration_ms
|
|
123
|
+
@result&.duration_ms
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# API-only duration in milliseconds
|
|
127
|
+
# @return [Integer, nil]
|
|
128
|
+
def duration_api_ms
|
|
129
|
+
@result&.duration_api_ms
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Session ID for resumption
|
|
133
|
+
# @return [String, nil]
|
|
134
|
+
def session_id
|
|
135
|
+
@result&.session_id
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Number of turns in the session
|
|
139
|
+
# @return [Integer, nil]
|
|
140
|
+
def num_turns
|
|
141
|
+
@result&.num_turns
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Why the model stopped generating
|
|
145
|
+
# @return [String, nil]
|
|
146
|
+
def stop_reason
|
|
147
|
+
@result&.stop_reason
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Model used for this turn (from first assistant message)
|
|
151
|
+
# @return [String, nil]
|
|
152
|
+
def model
|
|
153
|
+
assistant_messages.first&.model
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Per-model usage breakdown
|
|
157
|
+
# @return [Hash, nil]
|
|
158
|
+
def model_usage
|
|
159
|
+
@result&.model_usage
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Structured output (if requested via options)
|
|
163
|
+
# @return [Object, nil]
|
|
164
|
+
def structured_output
|
|
165
|
+
@result&.structured_output
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Tools that were denied by permission callbacks
|
|
169
|
+
# @return [Array<SDKPermissionDenial>]
|
|
170
|
+
def permission_denials
|
|
171
|
+
@result&.permission_denials || []
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Errors from the result
|
|
175
|
+
# @return [Array<String>]
|
|
176
|
+
def errors
|
|
177
|
+
@result&.errors || []
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# --- Status ---
|
|
181
|
+
|
|
182
|
+
# Whether the turn completed successfully
|
|
183
|
+
# @return [Boolean]
|
|
184
|
+
def success?
|
|
185
|
+
@result&.success? || false
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Whether the turn ended with an error
|
|
189
|
+
# @return [Boolean]
|
|
190
|
+
def error?
|
|
191
|
+
@result&.error? || false
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# The result subtype (e.g. "success", "error_max_turns")
|
|
195
|
+
# @return [String, nil]
|
|
196
|
+
def subtype
|
|
197
|
+
@result&.subtype
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# --- Filtered Message Access ---
|
|
201
|
+
|
|
202
|
+
# All AssistantMessages in this turn
|
|
203
|
+
# @return [Array<AssistantMessage>]
|
|
204
|
+
def assistant_messages
|
|
205
|
+
@messages.select { |m| m.is_a?(AssistantMessage) }
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# All UserMessages in this turn (including system-generated tool result messages)
|
|
209
|
+
# @return [Array<UserMessage, UserMessageReplay>]
|
|
210
|
+
def user_messages
|
|
211
|
+
@messages.select { |m| m.is_a?(UserMessage) || m.is_a?(UserMessageReplay) }
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# All StreamEvents in this turn
|
|
215
|
+
# @return [Array<StreamEvent>]
|
|
216
|
+
def stream_events
|
|
217
|
+
@messages.select { |m| m.is_a?(StreamEvent) }
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# All content blocks across all assistant messages
|
|
221
|
+
# @return [Array<TextBlock, ThinkingBlock, ToolUseBlock, ...>]
|
|
222
|
+
def content_blocks
|
|
223
|
+
assistant_messages.flat_map(&:content)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# --- Inspection ---
|
|
227
|
+
|
|
228
|
+
def inspect
|
|
229
|
+
status = complete? ? (success? ? "success" : "error") : "in_progress"
|
|
230
|
+
parts = [ "#<#{self.class} status=#{status}" ]
|
|
231
|
+
parts << "messages=#{@messages.size}"
|
|
232
|
+
parts << "text=#{text.length}chars" unless text.empty?
|
|
233
|
+
parts << "tools=#{tool_uses.size}" unless tool_uses.empty?
|
|
234
|
+
parts << "cost=$#{cost}" if cost
|
|
235
|
+
parts << "duration=#{duration_ms}ms" if duration_ms
|
|
236
|
+
"#{parts.join(" ")}>"
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
data/lib/claude_agent/types.rb
CHANGED
|
@@ -184,6 +184,51 @@ module ClaudeAgent
|
|
|
184
184
|
# max_turns: 10
|
|
185
185
|
# )
|
|
186
186
|
#
|
|
187
|
+
# Session metadata returned by list_sessions (TypeScript SDK parity: SDKSessionInfo)
|
|
188
|
+
#
|
|
189
|
+
# @example
|
|
190
|
+
# session = SessionInfo.new(
|
|
191
|
+
# session_id: "abc-123",
|
|
192
|
+
# summary: "Fix login bug",
|
|
193
|
+
# last_modified: 1706000000000,
|
|
194
|
+
# file_size: 4096,
|
|
195
|
+
# custom_title: "Login fix",
|
|
196
|
+
# first_prompt: "Help me fix the login page",
|
|
197
|
+
# git_branch: "fix/login",
|
|
198
|
+
# cwd: "/Users/dev/myapp"
|
|
199
|
+
# )
|
|
200
|
+
#
|
|
201
|
+
SessionInfo = Data.define(
|
|
202
|
+
:session_id,
|
|
203
|
+
:summary,
|
|
204
|
+
:last_modified,
|
|
205
|
+
:file_size,
|
|
206
|
+
:custom_title,
|
|
207
|
+
:first_prompt,
|
|
208
|
+
:git_branch,
|
|
209
|
+
:cwd
|
|
210
|
+
) do
|
|
211
|
+
def initialize(session_id:, summary:, last_modified:, file_size:, custom_title: nil, first_prompt: nil, git_branch: nil, cwd: nil)
|
|
212
|
+
super
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Message from a session transcript returned by get_session_messages (TypeScript SDK v0.2.59 parity)
|
|
217
|
+
#
|
|
218
|
+
# @example
|
|
219
|
+
# msg = SessionMessage.new(
|
|
220
|
+
# type: "user",
|
|
221
|
+
# uuid: "abc-123",
|
|
222
|
+
# session_id: "def-456",
|
|
223
|
+
# message: { "role" => "user", "content" => [{ "type" => "text", "text" => "Hello" }] }
|
|
224
|
+
# )
|
|
225
|
+
#
|
|
226
|
+
SessionMessage = Data.define(:type, :uuid, :session_id, :message, :parent_tool_use_id) do
|
|
227
|
+
def initialize(type:, uuid:, session_id:, message:, parent_tool_use_id: nil)
|
|
228
|
+
super
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
187
232
|
AgentDefinition = Data.define(
|
|
188
233
|
:description,
|
|
189
234
|
:prompt,
|
data/lib/claude_agent/version.rb
CHANGED
data/lib/claude_agent.rb
CHANGED
|
@@ -16,15 +16,71 @@ require_relative "claude_agent/messages"
|
|
|
16
16
|
require_relative "claude_agent/message_parser"
|
|
17
17
|
require_relative "claude_agent/hooks"
|
|
18
18
|
require_relative "claude_agent/permissions"
|
|
19
|
+
require_relative "claude_agent/permission_request"
|
|
20
|
+
require_relative "claude_agent/permission_queue"
|
|
19
21
|
require_relative "claude_agent/control_protocol"
|
|
20
22
|
require_relative "claude_agent/transport/base"
|
|
21
23
|
require_relative "claude_agent/transport/subprocess"
|
|
22
24
|
require_relative "claude_agent/mcp/tool"
|
|
23
25
|
require_relative "claude_agent/mcp/server"
|
|
26
|
+
require_relative "claude_agent/cumulative_usage"
|
|
27
|
+
require_relative "claude_agent/event_handler"
|
|
28
|
+
require_relative "claude_agent/turn_result"
|
|
29
|
+
require_relative "claude_agent/tool_activity"
|
|
24
30
|
require_relative "claude_agent/query"
|
|
25
31
|
require_relative "claude_agent/client"
|
|
26
|
-
require_relative "claude_agent/
|
|
32
|
+
require_relative "claude_agent/conversation"
|
|
33
|
+
require_relative "claude_agent/session_paths" # Shared session path infrastructure
|
|
34
|
+
require_relative "claude_agent/list_sessions" # Session discovery (TypeScript SDK v0.2.53 parity)
|
|
35
|
+
require_relative "claude_agent/get_session_messages" # Session transcript reading (TypeScript SDK v0.2.59 parity)
|
|
36
|
+
require_relative "claude_agent/session_message_relation" # Chainable message query object
|
|
37
|
+
require_relative "claude_agent/session" # Session finder + V2 Session API (unstable)
|
|
27
38
|
|
|
28
39
|
module ClaudeAgent
|
|
29
|
-
|
|
40
|
+
class << self
|
|
41
|
+
# Create a new Conversation
|
|
42
|
+
#
|
|
43
|
+
# @see Conversation#initialize
|
|
44
|
+
# @return [Conversation]
|
|
45
|
+
def conversation(**kwargs)
|
|
46
|
+
Conversation.new(**kwargs)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# List past sessions with metadata
|
|
50
|
+
#
|
|
51
|
+
# Reads session metadata directly from disk without spawning a CLI subprocess.
|
|
52
|
+
# Returns SessionInfo objects sorted by last modified time (most recent first).
|
|
53
|
+
#
|
|
54
|
+
# @param dir [String, nil] Directory to scope sessions to (includes git worktrees).
|
|
55
|
+
# When nil, returns sessions from all projects.
|
|
56
|
+
# @param limit [Integer, nil] Maximum number of sessions to return.
|
|
57
|
+
# @return [Array<SessionInfo>]
|
|
58
|
+
def list_sessions(dir: nil, limit: nil)
|
|
59
|
+
ListSessions.call(dir: dir, limit: limit)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Read messages from a past session's transcript
|
|
63
|
+
#
|
|
64
|
+
# Reads the session JSONL file from disk, reconstructs the main conversation
|
|
65
|
+
# thread, and returns user/assistant messages with optional pagination.
|
|
66
|
+
#
|
|
67
|
+
# @param session_id [String] UUID of the session to read
|
|
68
|
+
# @param dir [String, nil] Project directory to find the session in.
|
|
69
|
+
# When nil, searches all projects.
|
|
70
|
+
# @param limit [Integer, nil] Maximum number of messages to return.
|
|
71
|
+
# @param offset [Integer, nil] Number of messages to skip from the start.
|
|
72
|
+
# @return [Array<SessionMessage>]
|
|
73
|
+
def get_session_messages(session_id, dir: nil, limit: nil, offset: nil)
|
|
74
|
+
GetSessionMessages.call(session_id, dir: dir, limit: limit, offset: offset)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Resume a previous Conversation by session ID
|
|
78
|
+
#
|
|
79
|
+
# @param session_id [String] Session ID to resume
|
|
80
|
+
# @see Conversation.resume
|
|
81
|
+
# @return [Conversation]
|
|
82
|
+
def resume_conversation(session_id, **kwargs)
|
|
83
|
+
Conversation.resume(session_id, **kwargs)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
30
86
|
end
|