claude_agent 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/.claude/commands/spec/complete.md +105 -0
- data/.claude/commands/spec/update.md +95 -0
- data/.claude/rules/conventions.md +622 -0
- data/.claude/rules/git.md +86 -0
- data/.claude/rules/pull-requests.md +31 -0
- data/.claude/rules/releases.md +177 -0
- data/.claude/rules/testing.md +267 -0
- data/.claude/settings.json +49 -0
- data/CHANGELOG.md +13 -0
- data/CLAUDE.md +94 -0
- data/LICENSE.txt +21 -0
- data/README.md +679 -0
- data/Rakefile +63 -0
- data/SPEC.md +558 -0
- data/lib/claude_agent/abort_controller.rb +113 -0
- data/lib/claude_agent/client.rb +298 -0
- data/lib/claude_agent/content_blocks.rb +163 -0
- data/lib/claude_agent/control_protocol.rb +717 -0
- data/lib/claude_agent/errors.rb +103 -0
- data/lib/claude_agent/hooks.rb +228 -0
- data/lib/claude_agent/mcp/server.rb +166 -0
- data/lib/claude_agent/mcp/tool.rb +137 -0
- data/lib/claude_agent/message_parser.rb +262 -0
- data/lib/claude_agent/messages.rb +421 -0
- data/lib/claude_agent/options.rb +264 -0
- data/lib/claude_agent/permissions.rb +164 -0
- data/lib/claude_agent/query.rb +90 -0
- data/lib/claude_agent/sandbox_settings.rb +139 -0
- data/lib/claude_agent/spawn.rb +235 -0
- data/lib/claude_agent/transport/base.rb +61 -0
- data/lib/claude_agent/transport/subprocess.rb +432 -0
- data/lib/claude_agent/types.rb +193 -0
- data/lib/claude_agent/version.rb +5 -0
- data/lib/claude_agent.rb +28 -0
- data/sig/claude_agent.rbs +912 -0
- data/sig/manifest.yaml +5 -0
- metadata +97 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ClaudeAgent
|
|
4
|
+
# Parses raw JSON messages from the CLI into typed message objects
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# parser = MessageParser.new
|
|
8
|
+
# message = parser.parse({"type" => "assistant", "message" => {...}})
|
|
9
|
+
#
|
|
10
|
+
class MessageParser
|
|
11
|
+
# Parse a raw message hash into a typed message object
|
|
12
|
+
#
|
|
13
|
+
# @param raw [Hash] Raw message from CLI
|
|
14
|
+
# @return [UserMessage, UserMessageReplay, AssistantMessage, SystemMessage, ResultMessage, StreamEvent, CompactBoundaryMessage, StatusMessage, ToolProgressMessage, HookResponseMessage, AuthStatusMessage]
|
|
15
|
+
# @raise [MessageParseError] If message cannot be parsed
|
|
16
|
+
def parse(raw)
|
|
17
|
+
type = raw["type"]
|
|
18
|
+
|
|
19
|
+
case type
|
|
20
|
+
when "user"
|
|
21
|
+
parse_user_message(raw)
|
|
22
|
+
when "assistant"
|
|
23
|
+
parse_assistant_message(raw)
|
|
24
|
+
when "system"
|
|
25
|
+
# Check for special system subtypes
|
|
26
|
+
case raw["subtype"]
|
|
27
|
+
when "compact_boundary"
|
|
28
|
+
parse_compact_boundary_message(raw)
|
|
29
|
+
when "status"
|
|
30
|
+
parse_status_message(raw)
|
|
31
|
+
when "hook_response"
|
|
32
|
+
parse_hook_response_message(raw)
|
|
33
|
+
else
|
|
34
|
+
parse_system_message(raw)
|
|
35
|
+
end
|
|
36
|
+
when "result"
|
|
37
|
+
parse_result_message(raw)
|
|
38
|
+
when "stream_event"
|
|
39
|
+
parse_stream_event(raw)
|
|
40
|
+
when "tool_progress"
|
|
41
|
+
parse_tool_progress_message(raw)
|
|
42
|
+
when "auth_status"
|
|
43
|
+
parse_auth_status_message(raw)
|
|
44
|
+
else
|
|
45
|
+
raise MessageParseError.new("Unknown message type: #{type}", raw_message: raw)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
# Fetch a value from a hash, trying both snake_case and camelCase keys
|
|
52
|
+
# @param raw [Hash] The hash to fetch from
|
|
53
|
+
# @param snake_key [Symbol, String] The snake_case key to try
|
|
54
|
+
# @param default [Object] Default value if neither key exists
|
|
55
|
+
# @return [Object] The value or default
|
|
56
|
+
def fetch_dual(raw, snake_key, default = nil)
|
|
57
|
+
snake_str = snake_key.to_s
|
|
58
|
+
camel_str = snake_str.camelize(:lower)
|
|
59
|
+
raw[snake_str] || raw[camel_str] || default
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def parse_user_message(raw)
|
|
63
|
+
message = raw["message"] || {}
|
|
64
|
+
content = parse_user_content(message["content"])
|
|
65
|
+
|
|
66
|
+
is_replay = fetch_dual(raw, :is_replay)
|
|
67
|
+
is_synthetic = fetch_dual(raw, :is_synthetic)
|
|
68
|
+
tool_use_result = fetch_dual(raw, :tool_use_result)
|
|
69
|
+
|
|
70
|
+
if is_replay
|
|
71
|
+
UserMessageReplay.new(
|
|
72
|
+
content: content,
|
|
73
|
+
uuid: raw["uuid"],
|
|
74
|
+
session_id: fetch_dual(raw, :session_id),
|
|
75
|
+
parent_tool_use_id: raw["parent_tool_use_id"],
|
|
76
|
+
is_replay: true,
|
|
77
|
+
is_synthetic: is_synthetic,
|
|
78
|
+
tool_use_result: tool_use_result
|
|
79
|
+
)
|
|
80
|
+
else
|
|
81
|
+
UserMessage.new(
|
|
82
|
+
content: content,
|
|
83
|
+
uuid: raw["uuid"],
|
|
84
|
+
session_id: fetch_dual(raw, :session_id),
|
|
85
|
+
parent_tool_use_id: raw["parent_tool_use_id"]
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def parse_assistant_message(raw)
|
|
91
|
+
message = raw["message"] || {}
|
|
92
|
+
content_raw = message["content"] || []
|
|
93
|
+
content = content_raw.map { |block| parse_content_block(block) }
|
|
94
|
+
|
|
95
|
+
AssistantMessage.new(
|
|
96
|
+
content: content,
|
|
97
|
+
model: message["model"] || raw["model"] || "unknown",
|
|
98
|
+
uuid: raw["uuid"],
|
|
99
|
+
session_id: fetch_dual(raw, :session_id),
|
|
100
|
+
error: message["error"] || raw["error"],
|
|
101
|
+
parent_tool_use_id: raw["parent_tool_use_id"]
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def parse_system_message(raw)
|
|
106
|
+
SystemMessage.new(
|
|
107
|
+
subtype: raw["subtype"] || "unknown",
|
|
108
|
+
data: raw["data"] || raw
|
|
109
|
+
)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def parse_compact_boundary_message(raw)
|
|
113
|
+
CompactBoundaryMessage.new(
|
|
114
|
+
uuid: raw["uuid"] || "",
|
|
115
|
+
session_id: fetch_dual(raw, :session_id, ""),
|
|
116
|
+
compact_metadata: fetch_dual(raw, :compact_metadata, {})
|
|
117
|
+
)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def parse_result_message(raw)
|
|
121
|
+
permission_denials = parse_permission_denials(fetch_dual(raw, :permission_denials))
|
|
122
|
+
|
|
123
|
+
ResultMessage.new(
|
|
124
|
+
subtype: raw["subtype"] || "unknown",
|
|
125
|
+
duration_ms: fetch_dual(raw, :duration_ms, 0),
|
|
126
|
+
duration_api_ms: fetch_dual(raw, :duration_api_ms, 0),
|
|
127
|
+
is_error: fetch_dual(raw, :is_error, false),
|
|
128
|
+
num_turns: fetch_dual(raw, :num_turns, 0),
|
|
129
|
+
session_id: fetch_dual(raw, :session_id, ""),
|
|
130
|
+
total_cost_usd: fetch_dual(raw, :total_cost_usd),
|
|
131
|
+
usage: raw["usage"],
|
|
132
|
+
result: raw["result"],
|
|
133
|
+
structured_output: fetch_dual(raw, :structured_output),
|
|
134
|
+
errors: raw["errors"],
|
|
135
|
+
permission_denials: permission_denials,
|
|
136
|
+
model_usage: fetch_dual(raw, :model_usage)
|
|
137
|
+
)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def parse_permission_denials(denials)
|
|
141
|
+
return nil unless denials.is_a?(Array)
|
|
142
|
+
|
|
143
|
+
denials.map do |denial|
|
|
144
|
+
SDKPermissionDenial.new(
|
|
145
|
+
tool_name: fetch_dual(denial, :tool_name),
|
|
146
|
+
tool_use_id: fetch_dual(denial, :tool_use_id),
|
|
147
|
+
tool_input: fetch_dual(denial, :tool_input)
|
|
148
|
+
)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def parse_stream_event(raw)
|
|
153
|
+
StreamEvent.new(
|
|
154
|
+
uuid: raw["uuid"] || "",
|
|
155
|
+
session_id: fetch_dual(raw, :session_id, ""),
|
|
156
|
+
event: raw["event"] || {},
|
|
157
|
+
parent_tool_use_id: raw["parent_tool_use_id"]
|
|
158
|
+
)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def parse_user_content(content)
|
|
162
|
+
case content
|
|
163
|
+
when String
|
|
164
|
+
content
|
|
165
|
+
when Array
|
|
166
|
+
content.map { |block| parse_content_block(block) }
|
|
167
|
+
else
|
|
168
|
+
content.to_s
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def parse_content_block(block)
|
|
173
|
+
return block unless block.is_a?(Hash)
|
|
174
|
+
|
|
175
|
+
type = block["type"]
|
|
176
|
+
|
|
177
|
+
case type
|
|
178
|
+
when "text"
|
|
179
|
+
TextBlock.new(text: block["text"] || "")
|
|
180
|
+
when "thinking"
|
|
181
|
+
ThinkingBlock.new(
|
|
182
|
+
thinking: block["thinking"] || "",
|
|
183
|
+
signature: block["signature"] || ""
|
|
184
|
+
)
|
|
185
|
+
when "tool_use"
|
|
186
|
+
ToolUseBlock.new(
|
|
187
|
+
id: block["id"] || "",
|
|
188
|
+
name: block["name"] || "",
|
|
189
|
+
input: block["input"] || {}
|
|
190
|
+
)
|
|
191
|
+
when "tool_result"
|
|
192
|
+
ToolResultBlock.new(
|
|
193
|
+
tool_use_id: block["tool_use_id"] || "",
|
|
194
|
+
content: block["content"],
|
|
195
|
+
is_error: block["is_error"]
|
|
196
|
+
)
|
|
197
|
+
when "server_tool_use"
|
|
198
|
+
ServerToolUseBlock.new(
|
|
199
|
+
id: block["id"] || "",
|
|
200
|
+
name: block["name"] || "",
|
|
201
|
+
input: block["input"] || {},
|
|
202
|
+
server_name: block["server_name"] || ""
|
|
203
|
+
)
|
|
204
|
+
when "server_tool_result"
|
|
205
|
+
ServerToolResultBlock.new(
|
|
206
|
+
tool_use_id: block["tool_use_id"] || "",
|
|
207
|
+
content: block["content"],
|
|
208
|
+
is_error: block["is_error"],
|
|
209
|
+
server_name: block["server_name"] || ""
|
|
210
|
+
)
|
|
211
|
+
when "image"
|
|
212
|
+
ImageContentBlock.new(
|
|
213
|
+
source: block["source"] || {}
|
|
214
|
+
)
|
|
215
|
+
else
|
|
216
|
+
# Return raw hash for unknown block types
|
|
217
|
+
block
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def parse_status_message(raw)
|
|
222
|
+
StatusMessage.new(
|
|
223
|
+
uuid: raw["uuid"] || "",
|
|
224
|
+
session_id: fetch_dual(raw, :session_id, ""),
|
|
225
|
+
status: raw["status"]
|
|
226
|
+
)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def parse_tool_progress_message(raw)
|
|
230
|
+
ToolProgressMessage.new(
|
|
231
|
+
uuid: raw["uuid"] || "",
|
|
232
|
+
session_id: fetch_dual(raw, :session_id, ""),
|
|
233
|
+
tool_use_id: fetch_dual(raw, :tool_use_id, ""),
|
|
234
|
+
tool_name: fetch_dual(raw, :tool_name, ""),
|
|
235
|
+
parent_tool_use_id: fetch_dual(raw, :parent_tool_use_id),
|
|
236
|
+
elapsed_time_seconds: fetch_dual(raw, :elapsed_time_seconds, 0)
|
|
237
|
+
)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def parse_hook_response_message(raw)
|
|
241
|
+
HookResponseMessage.new(
|
|
242
|
+
uuid: raw["uuid"] || "",
|
|
243
|
+
session_id: fetch_dual(raw, :session_id, ""),
|
|
244
|
+
hook_name: fetch_dual(raw, :hook_name, ""),
|
|
245
|
+
hook_event: fetch_dual(raw, :hook_event, ""),
|
|
246
|
+
stdout: raw["stdout"] || "",
|
|
247
|
+
stderr: raw["stderr"] || "",
|
|
248
|
+
exit_code: fetch_dual(raw, :exit_code)
|
|
249
|
+
)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def parse_auth_status_message(raw)
|
|
253
|
+
AuthStatusMessage.new(
|
|
254
|
+
uuid: raw["uuid"] || "",
|
|
255
|
+
session_id: fetch_dual(raw, :session_id, ""),
|
|
256
|
+
is_authenticating: fetch_dual(raw, :is_authenticating, false),
|
|
257
|
+
output: raw["output"] || [],
|
|
258
|
+
error: raw["error"]
|
|
259
|
+
)
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
@@ -0,0 +1,421 @@
|
|
|
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
|
+
|
|
139
|
+
# System message (internal events)
|
|
140
|
+
#
|
|
141
|
+
# @example
|
|
142
|
+
# msg = SystemMessage.new(subtype: "init", data: {version: "2.0.0"})
|
|
143
|
+
#
|
|
144
|
+
SystemMessage = Data.define(:subtype, :data) do
|
|
145
|
+
def type
|
|
146
|
+
:system
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Result message (final message with usage/cost info) - TypeScript SDK parity
|
|
151
|
+
#
|
|
152
|
+
# @example Success result
|
|
153
|
+
# msg = ResultMessage.new(
|
|
154
|
+
# subtype: "success",
|
|
155
|
+
# duration_ms: 1500,
|
|
156
|
+
# duration_api_ms: 1200,
|
|
157
|
+
# is_error: false,
|
|
158
|
+
# num_turns: 3,
|
|
159
|
+
# session_id: "session-abc",
|
|
160
|
+
# total_cost_usd: 0.05,
|
|
161
|
+
# usage: {input_tokens: 100, output_tokens: 50}
|
|
162
|
+
# )
|
|
163
|
+
#
|
|
164
|
+
# @example Error result
|
|
165
|
+
# msg = ResultMessage.new(
|
|
166
|
+
# subtype: "error_max_turns",
|
|
167
|
+
# errors: ["Maximum turns exceeded"],
|
|
168
|
+
# ...
|
|
169
|
+
# )
|
|
170
|
+
#
|
|
171
|
+
ResultMessage = Data.define(
|
|
172
|
+
:subtype,
|
|
173
|
+
:duration_ms,
|
|
174
|
+
:duration_api_ms,
|
|
175
|
+
:is_error,
|
|
176
|
+
:num_turns,
|
|
177
|
+
:session_id,
|
|
178
|
+
:total_cost_usd,
|
|
179
|
+
:usage,
|
|
180
|
+
:result,
|
|
181
|
+
:structured_output,
|
|
182
|
+
:errors, # Array<String> for error subtypes
|
|
183
|
+
:permission_denials, # Array<SDKPermissionDenial>
|
|
184
|
+
:model_usage # Hash with per-model usage breakdown
|
|
185
|
+
) do
|
|
186
|
+
def initialize(
|
|
187
|
+
subtype:,
|
|
188
|
+
duration_ms:,
|
|
189
|
+
duration_api_ms:,
|
|
190
|
+
is_error:,
|
|
191
|
+
num_turns:,
|
|
192
|
+
session_id:,
|
|
193
|
+
total_cost_usd: nil,
|
|
194
|
+
usage: nil,
|
|
195
|
+
result: nil,
|
|
196
|
+
structured_output: nil,
|
|
197
|
+
errors: nil,
|
|
198
|
+
permission_denials: nil,
|
|
199
|
+
model_usage: nil
|
|
200
|
+
)
|
|
201
|
+
super
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def type
|
|
205
|
+
:result
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Check if this was an error result
|
|
209
|
+
# @return [Boolean]
|
|
210
|
+
def error?
|
|
211
|
+
is_error
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Check if this was a successful result
|
|
215
|
+
# @return [Boolean]
|
|
216
|
+
def success?
|
|
217
|
+
!is_error
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Stream event (partial message during streaming)
|
|
222
|
+
#
|
|
223
|
+
# @example
|
|
224
|
+
# event = StreamEvent.new(
|
|
225
|
+
# uuid: "evt-123",
|
|
226
|
+
# session_id: "session-abc",
|
|
227
|
+
# event: {type: "content_block_delta", delta: {type: "text_delta", text: "Hello"}}
|
|
228
|
+
# )
|
|
229
|
+
#
|
|
230
|
+
StreamEvent = Data.define(:uuid, :session_id, :event, :parent_tool_use_id) do
|
|
231
|
+
def initialize(uuid:, session_id:, event:, parent_tool_use_id: nil)
|
|
232
|
+
super
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def type
|
|
236
|
+
:stream_event
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Get the event type from the raw event
|
|
240
|
+
# @return [String, nil]
|
|
241
|
+
def event_type
|
|
242
|
+
event["type"]
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Compact boundary message (conversation compaction marker) - TypeScript SDK parity
|
|
247
|
+
#
|
|
248
|
+
# Sent when the conversation is compacted to reduce context size.
|
|
249
|
+
# Contains metadata about the compaction operation.
|
|
250
|
+
#
|
|
251
|
+
# @example
|
|
252
|
+
# msg = CompactBoundaryMessage.new(
|
|
253
|
+
# uuid: "msg-123",
|
|
254
|
+
# session_id: "session-abc",
|
|
255
|
+
# compact_metadata: { trigger: "auto", pre_tokens: 50000 }
|
|
256
|
+
# )
|
|
257
|
+
# msg.trigger # => "auto"
|
|
258
|
+
# msg.pre_tokens # => 50000
|
|
259
|
+
#
|
|
260
|
+
CompactBoundaryMessage = Data.define(:uuid, :session_id, :compact_metadata) do
|
|
261
|
+
def type
|
|
262
|
+
:compact_boundary
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Get the compaction trigger type
|
|
266
|
+
# @return [String] "manual" or "auto"
|
|
267
|
+
def trigger
|
|
268
|
+
compact_metadata[:trigger] || compact_metadata["trigger"]
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Get the token count before compaction
|
|
272
|
+
# @return [Integer, nil]
|
|
273
|
+
def pre_tokens
|
|
274
|
+
compact_metadata[:pre_tokens] || compact_metadata["pre_tokens"]
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Status message (TypeScript SDK parity)
|
|
279
|
+
#
|
|
280
|
+
# Reports session status like 'compacting' during operations.
|
|
281
|
+
#
|
|
282
|
+
# @example
|
|
283
|
+
# msg = StatusMessage.new(
|
|
284
|
+
# uuid: "msg-123",
|
|
285
|
+
# session_id: "session-abc",
|
|
286
|
+
# status: "compacting"
|
|
287
|
+
# )
|
|
288
|
+
#
|
|
289
|
+
StatusMessage = Data.define(:uuid, :session_id, :status) do
|
|
290
|
+
def type
|
|
291
|
+
:status
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Tool progress message (TypeScript SDK parity)
|
|
296
|
+
#
|
|
297
|
+
# Reports progress during long-running tool executions.
|
|
298
|
+
#
|
|
299
|
+
# @example
|
|
300
|
+
# msg = ToolProgressMessage.new(
|
|
301
|
+
# uuid: "msg-123",
|
|
302
|
+
# session_id: "session-abc",
|
|
303
|
+
# tool_use_id: "tool-456",
|
|
304
|
+
# tool_name: "Bash",
|
|
305
|
+
# elapsed_time_seconds: 5.2
|
|
306
|
+
# )
|
|
307
|
+
#
|
|
308
|
+
ToolProgressMessage = Data.define(
|
|
309
|
+
:uuid,
|
|
310
|
+
:session_id,
|
|
311
|
+
:tool_use_id,
|
|
312
|
+
:tool_name,
|
|
313
|
+
:parent_tool_use_id,
|
|
314
|
+
:elapsed_time_seconds
|
|
315
|
+
) do
|
|
316
|
+
def initialize(
|
|
317
|
+
uuid:,
|
|
318
|
+
session_id:,
|
|
319
|
+
tool_use_id:,
|
|
320
|
+
tool_name:,
|
|
321
|
+
elapsed_time_seconds:,
|
|
322
|
+
parent_tool_use_id: nil
|
|
323
|
+
)
|
|
324
|
+
super
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def type
|
|
328
|
+
:tool_progress
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# Hook response message (TypeScript SDK parity)
|
|
333
|
+
#
|
|
334
|
+
# Contains output from hook executions.
|
|
335
|
+
#
|
|
336
|
+
# @example
|
|
337
|
+
# msg = HookResponseMessage.new(
|
|
338
|
+
# uuid: "msg-123",
|
|
339
|
+
# session_id: "session-abc",
|
|
340
|
+
# hook_name: "my-hook",
|
|
341
|
+
# hook_event: "PreToolUse",
|
|
342
|
+
# stdout: "Hook output",
|
|
343
|
+
# stderr: "",
|
|
344
|
+
# exit_code: 0
|
|
345
|
+
# )
|
|
346
|
+
#
|
|
347
|
+
HookResponseMessage = Data.define(
|
|
348
|
+
:uuid,
|
|
349
|
+
:session_id,
|
|
350
|
+
:hook_name,
|
|
351
|
+
:hook_event,
|
|
352
|
+
:stdout,
|
|
353
|
+
:stderr,
|
|
354
|
+
:exit_code
|
|
355
|
+
) do
|
|
356
|
+
def initialize(
|
|
357
|
+
uuid:,
|
|
358
|
+
session_id:,
|
|
359
|
+
hook_name:,
|
|
360
|
+
hook_event:,
|
|
361
|
+
stdout: "",
|
|
362
|
+
stderr: "",
|
|
363
|
+
exit_code: nil
|
|
364
|
+
)
|
|
365
|
+
super
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def type
|
|
369
|
+
:hook_response
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
# Auth status message (TypeScript SDK parity)
|
|
374
|
+
#
|
|
375
|
+
# Reports authentication status during login flows.
|
|
376
|
+
#
|
|
377
|
+
# @example
|
|
378
|
+
# msg = AuthStatusMessage.new(
|
|
379
|
+
# uuid: "msg-123",
|
|
380
|
+
# session_id: "session-abc",
|
|
381
|
+
# is_authenticating: true,
|
|
382
|
+
# output: ["Waiting for browser..."]
|
|
383
|
+
# )
|
|
384
|
+
#
|
|
385
|
+
AuthStatusMessage = Data.define(
|
|
386
|
+
:uuid,
|
|
387
|
+
:session_id,
|
|
388
|
+
:is_authenticating,
|
|
389
|
+
:output,
|
|
390
|
+
:error
|
|
391
|
+
) do
|
|
392
|
+
def initialize(
|
|
393
|
+
uuid:,
|
|
394
|
+
session_id:,
|
|
395
|
+
is_authenticating:,
|
|
396
|
+
output: [],
|
|
397
|
+
error: nil
|
|
398
|
+
)
|
|
399
|
+
super
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def type
|
|
403
|
+
:auth_status
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
# All message types
|
|
408
|
+
MESSAGE_TYPES = [
|
|
409
|
+
UserMessage,
|
|
410
|
+
UserMessageReplay,
|
|
411
|
+
AssistantMessage,
|
|
412
|
+
SystemMessage,
|
|
413
|
+
ResultMessage,
|
|
414
|
+
StreamEvent,
|
|
415
|
+
CompactBoundaryMessage,
|
|
416
|
+
StatusMessage,
|
|
417
|
+
ToolProgressMessage,
|
|
418
|
+
HookResponseMessage,
|
|
419
|
+
AuthStatusMessage
|
|
420
|
+
].freeze
|
|
421
|
+
end
|