claude-agent-sdk 0.1.1 → 0.1.3
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 +16 -0
- data/README.md +92 -5
- data/lib/claude_agent_sdk/query.rb +66 -1
- data/lib/claude_agent_sdk/sdk_mcp_server.rb +210 -4
- data/lib/claude_agent_sdk/streaming.rb +73 -0
- data/lib/claude_agent_sdk/subprocess_cli_transport.rb +22 -21
- data/lib/claude_agent_sdk/types.rb +25 -0
- data/lib/claude_agent_sdk/version.rb +1 -1
- data/lib/claude_agent_sdk.rb +23 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d1b1a2ec74f15062544d42a0df92058e256bab2ed29754e19133bfc1340cddcf
|
|
4
|
+
data.tar.gz: f01aea04b3f32e51b5d98e6994d52c8e2864259fa86d1c5bdc64104486b63687
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ef15b15c77ceb22fc0b40884ecc1e79a884b9b7d043b21420ef0faa34d638206eec6284ab6afeb015097d721c75c0e00ce0f3db4c63e7f8e90305bda2e468b16
|
|
7
|
+
data.tar.gz: 74ee1d9b848c2e84336fb3a12e8147b35243f64ad6c1d352fe451a39c16a92cdaa92303acb20cf053495c580171bfe36d60f5a53bee98d2781da0b1b261870d3
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.1.3] - 2025-10-15
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **MCP resource support:** Full support for MCP resources (list, read, subscribe operations)
|
|
12
|
+
- **MCP prompt support:** Support for MCP prompts (list, get operations)
|
|
13
|
+
- **Streaming input support:** Added streaming capabilities for input handling
|
|
14
|
+
- Feature complete MCP implementation matching Python SDK functionality
|
|
15
|
+
|
|
16
|
+
## [0.1.2] - 2025-10-14
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- **Critical:** Replaced `Async::Process` with Ruby's built-in `Open3` for subprocess management
|
|
20
|
+
- Fixed "uninitialized constant Async::Process" error that prevented the gem from working
|
|
21
|
+
- Process management now uses standard Ruby threads instead of async tasks
|
|
22
|
+
- All 86 tests passing
|
|
23
|
+
|
|
8
24
|
## [0.1.1] - 2025-10-14
|
|
9
25
|
|
|
10
26
|
### Fixed
|
data/README.md
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# Claude Agent SDK for Ruby
|
|
2
2
|
|
|
3
|
-
> **⚠️ DISCLAIMER**: This is an **unofficial, community-maintained** Ruby SDK for Claude Agent. It is not officially supported or maintained by Anthropic. For official SDK support, please refer to the [Python SDK](https://
|
|
3
|
+
> **⚠️ DISCLAIMER**: This is an **unofficial, community-maintained** Ruby SDK for Claude Agent. It is not officially supported or maintained by Anthropic. For official SDK support, please refer to the [Python SDK](https://docs.claude.com/en/api/agent-sdk/python).
|
|
4
4
|
>
|
|
5
5
|
> This implementation is based on the official Python SDK and aims to provide feature parity for Ruby developers. Use at your own risk.
|
|
6
6
|
|
|
7
7
|
Ruby SDK for Claude Agent. See the [Claude Agent SDK documentation](https://docs.anthropic.com/en/docs/claude-code/sdk) for more information.
|
|
8
8
|
|
|
9
|
+
[](https://badge.fury.io/rb/claude-agent-sdk)
|
|
10
|
+
|
|
9
11
|
## Installation
|
|
10
12
|
|
|
11
13
|
Add this line to your application's Gemfile:
|
|
@@ -92,6 +94,41 @@ options = ClaudeAgentSDK::ClaudeAgentOptions.new(
|
|
|
92
94
|
)
|
|
93
95
|
```
|
|
94
96
|
|
|
97
|
+
### Streaming Input
|
|
98
|
+
|
|
99
|
+
The `query()` function supports streaming input, allowing you to send multiple messages dynamically instead of a single prompt string.
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
require 'claude_agent_sdk'
|
|
103
|
+
|
|
104
|
+
# Create a stream of messages
|
|
105
|
+
messages = ['Hello!', 'What is 2+2?', 'Thanks!']
|
|
106
|
+
stream = ClaudeAgentSDK::Streaming.from_array(messages)
|
|
107
|
+
|
|
108
|
+
# Query with streaming input
|
|
109
|
+
ClaudeAgentSDK.query(prompt: stream) do |message|
|
|
110
|
+
puts message if message.is_a?(ClaudeAgentSDK::AssistantMessage)
|
|
111
|
+
end
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
You can also create custom streaming enumerators:
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
# Dynamic message generation
|
|
118
|
+
stream = Enumerator.new do |yielder|
|
|
119
|
+
yielder << ClaudeAgentSDK::Streaming.user_message("First message")
|
|
120
|
+
# Do some processing...
|
|
121
|
+
yielder << ClaudeAgentSDK::Streaming.user_message("Second message")
|
|
122
|
+
yielder << ClaudeAgentSDK::Streaming.user_message("Third message")
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
ClaudeAgentSDK.query(prompt: stream) do |message|
|
|
126
|
+
# Process responses
|
|
127
|
+
end
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
For a complete example, see [examples/streaming_input_example.rb](examples/streaming_input_example.rb).
|
|
131
|
+
|
|
95
132
|
## Client
|
|
96
133
|
|
|
97
134
|
`ClaudeAgentSDK::Client` supports bidirectional, interactive conversations with Claude Code. Unlike `query()`, `Client` enables **custom tools**, **hooks**, and **permission callbacks**, all of which can be defined as Ruby procs/lambdas. See [lib/claude_agent_sdk.rb](lib/claude_agent_sdk.rb).
|
|
@@ -193,6 +230,58 @@ options = ClaudeAgentSDK::ClaudeAgentOptions.new(
|
|
|
193
230
|
)
|
|
194
231
|
```
|
|
195
232
|
|
|
233
|
+
#### MCP Resources and Prompts
|
|
234
|
+
|
|
235
|
+
SDK MCP servers can also expose **resources** (data sources) and **prompts** (reusable templates):
|
|
236
|
+
|
|
237
|
+
```ruby
|
|
238
|
+
# Create a resource (data source Claude can read)
|
|
239
|
+
config_resource = ClaudeAgentSDK.create_resource(
|
|
240
|
+
uri: 'config://app/settings',
|
|
241
|
+
name: 'Application Settings',
|
|
242
|
+
description: 'Current app configuration',
|
|
243
|
+
mime_type: 'application/json'
|
|
244
|
+
) do
|
|
245
|
+
config_data = { app_name: 'MyApp', version: '1.0.0' }
|
|
246
|
+
{
|
|
247
|
+
contents: [{
|
|
248
|
+
uri: 'config://app/settings',
|
|
249
|
+
mimeType: 'application/json',
|
|
250
|
+
text: JSON.pretty_generate(config_data)
|
|
251
|
+
}]
|
|
252
|
+
}
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Create a prompt template
|
|
256
|
+
review_prompt = ClaudeAgentSDK.create_prompt(
|
|
257
|
+
name: 'code_review',
|
|
258
|
+
description: 'Review code for best practices',
|
|
259
|
+
arguments: [
|
|
260
|
+
{ name: 'code', description: 'Code to review', required: true }
|
|
261
|
+
]
|
|
262
|
+
) do |args|
|
|
263
|
+
{
|
|
264
|
+
messages: [{
|
|
265
|
+
role: 'user',
|
|
266
|
+
content: {
|
|
267
|
+
type: 'text',
|
|
268
|
+
text: "Review this code: #{args[:code]}"
|
|
269
|
+
}
|
|
270
|
+
}]
|
|
271
|
+
}
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Create server with tools, resources, and prompts
|
|
275
|
+
server = ClaudeAgentSDK.create_sdk_mcp_server(
|
|
276
|
+
name: 'dev-tools',
|
|
277
|
+
tools: [my_tool],
|
|
278
|
+
resources: [config_resource],
|
|
279
|
+
prompts: [review_prompt]
|
|
280
|
+
)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
For complete examples, see [examples/mcp_resources_prompts_example.rb](examples/mcp_resources_prompts_example.rb).
|
|
284
|
+
|
|
196
285
|
### Basic Client Usage
|
|
197
286
|
|
|
198
287
|
```ruby
|
|
@@ -415,7 +504,9 @@ See the following examples for complete working code:
|
|
|
415
504
|
|
|
416
505
|
- [examples/quick_start.rb](examples/quick_start.rb) - Basic `query()` usage with options
|
|
417
506
|
- [examples/client_example.rb](examples/client_example.rb) - Interactive Client usage
|
|
507
|
+
- [examples/streaming_input_example.rb](examples/streaming_input_example.rb) - Streaming input for multi-turn conversations
|
|
418
508
|
- [examples/mcp_calculator.rb](examples/mcp_calculator.rb) - Custom tools with SDK MCP servers
|
|
509
|
+
- [examples/mcp_resources_prompts_example.rb](examples/mcp_resources_prompts_example.rb) - MCP resources and prompts
|
|
419
510
|
- [examples/hooks_example.rb](examples/hooks_example.rb) - Using hooks to control tool execution
|
|
420
511
|
- [examples/permission_callback_example.rb](examples/permission_callback_example.rb) - Dynamic tool permission control
|
|
421
512
|
|
|
@@ -423,10 +514,6 @@ See the following examples for complete working code:
|
|
|
423
514
|
|
|
424
515
|
After checking out the repo, run `bundle install` to install dependencies. Then, run `bundle exec rspec` to run the tests.
|
|
425
516
|
|
|
426
|
-
## Contributing
|
|
427
|
-
|
|
428
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/anthropics/claude-agent-sdk-ruby.
|
|
429
|
-
|
|
430
517
|
## License
|
|
431
518
|
|
|
432
519
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
@@ -314,6 +314,14 @@ module ClaudeAgentSDK
|
|
|
314
314
|
handle_mcp_tools_list(server, message)
|
|
315
315
|
when 'tools/call'
|
|
316
316
|
handle_mcp_tools_call(server, message, params)
|
|
317
|
+
when 'resources/list'
|
|
318
|
+
handle_mcp_resources_list(server, message)
|
|
319
|
+
when 'resources/read'
|
|
320
|
+
handle_mcp_resources_read(server, message, params)
|
|
321
|
+
when 'prompts/list'
|
|
322
|
+
handle_mcp_prompts_list(server, message)
|
|
323
|
+
when 'prompts/get'
|
|
324
|
+
handle_mcp_prompts_get(server, message, params)
|
|
317
325
|
when 'notifications/initialized'
|
|
318
326
|
{ jsonrpc: '2.0', result: {} }
|
|
319
327
|
else
|
|
@@ -332,12 +340,17 @@ module ClaudeAgentSDK
|
|
|
332
340
|
end
|
|
333
341
|
|
|
334
342
|
def handle_mcp_initialize(server, message)
|
|
343
|
+
capabilities = {}
|
|
344
|
+
capabilities[:tools] = {} if server.tools && !server.tools.empty?
|
|
345
|
+
capabilities[:resources] = {} if server.resources && !server.resources.empty?
|
|
346
|
+
capabilities[:prompts] = {} if server.prompts && !server.prompts.empty?
|
|
347
|
+
|
|
335
348
|
{
|
|
336
349
|
jsonrpc: '2.0',
|
|
337
350
|
id: message[:id],
|
|
338
351
|
result: {
|
|
339
352
|
protocolVersion: '2024-11-05',
|
|
340
|
-
capabilities:
|
|
353
|
+
capabilities: capabilities,
|
|
341
354
|
serverInfo: {
|
|
342
355
|
name: server.name,
|
|
343
356
|
version: server.version || '1.0.0'
|
|
@@ -384,6 +397,58 @@ module ClaudeAgentSDK
|
|
|
384
397
|
}
|
|
385
398
|
end
|
|
386
399
|
|
|
400
|
+
def handle_mcp_resources_list(server, message)
|
|
401
|
+
# List resources from the SDK MCP server
|
|
402
|
+
resources_data = server.list_resources
|
|
403
|
+
{
|
|
404
|
+
jsonrpc: '2.0',
|
|
405
|
+
id: message[:id],
|
|
406
|
+
result: { resources: resources_data }
|
|
407
|
+
}
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def handle_mcp_resources_read(server, message, params)
|
|
411
|
+
# Read a resource from the SDK MCP server
|
|
412
|
+
uri = params[:uri]
|
|
413
|
+
raise 'Missing uri parameter for resources/read' unless uri
|
|
414
|
+
|
|
415
|
+
# Read the resource
|
|
416
|
+
result = server.read_resource(uri)
|
|
417
|
+
|
|
418
|
+
{
|
|
419
|
+
jsonrpc: '2.0',
|
|
420
|
+
id: message[:id],
|
|
421
|
+
result: result
|
|
422
|
+
}
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
def handle_mcp_prompts_list(server, message)
|
|
426
|
+
# List prompts from the SDK MCP server
|
|
427
|
+
prompts_data = server.list_prompts
|
|
428
|
+
{
|
|
429
|
+
jsonrpc: '2.0',
|
|
430
|
+
id: message[:id],
|
|
431
|
+
result: { prompts: prompts_data }
|
|
432
|
+
}
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
def handle_mcp_prompts_get(server, message, params)
|
|
436
|
+
# Get a prompt from the SDK MCP server
|
|
437
|
+
name = params[:name]
|
|
438
|
+
raise 'Missing name parameter for prompts/get' unless name
|
|
439
|
+
|
|
440
|
+
arguments = params[:arguments] || {}
|
|
441
|
+
|
|
442
|
+
# Get the prompt
|
|
443
|
+
result = server.get_prompt(name, arguments)
|
|
444
|
+
|
|
445
|
+
{
|
|
446
|
+
jsonrpc: '2.0',
|
|
447
|
+
id: message[:id],
|
|
448
|
+
result: result
|
|
449
|
+
}
|
|
450
|
+
end
|
|
451
|
+
|
|
387
452
|
public
|
|
388
453
|
|
|
389
454
|
# Send interrupt control request
|
|
@@ -6,14 +6,23 @@ module ClaudeAgentSDK
|
|
|
6
6
|
# Unlike external MCP servers that run as separate processes, SDK MCP servers
|
|
7
7
|
# run directly in your application's process, providing better performance
|
|
8
8
|
# and simpler deployment.
|
|
9
|
+
#
|
|
10
|
+
# Supports:
|
|
11
|
+
# - Tools: Executable functions that Claude can call
|
|
12
|
+
# - Resources: Data sources that can be read (files, databases, APIs, etc.)
|
|
13
|
+
# - Prompts: Reusable prompt templates with arguments
|
|
9
14
|
class SdkMcpServer
|
|
10
|
-
attr_reader :name, :version, :tools
|
|
15
|
+
attr_reader :name, :version, :tools, :resources, :prompts
|
|
11
16
|
|
|
12
|
-
def initialize(name:, version: '1.0.0', tools: [])
|
|
17
|
+
def initialize(name:, version: '1.0.0', tools: [], resources: [], prompts: [])
|
|
13
18
|
@name = name
|
|
14
19
|
@version = version
|
|
15
20
|
@tools = tools
|
|
21
|
+
@resources = resources
|
|
22
|
+
@prompts = prompts
|
|
16
23
|
@tool_map = tools.each_with_object({}) { |tool, hash| hash[tool.name] = tool }
|
|
24
|
+
@resource_map = resources.each_with_object({}) { |res, hash| hash[res.uri] = res }
|
|
25
|
+
@prompt_map = prompts.each_with_object({}) { |prompt, hash| hash[prompt.name] = prompt }
|
|
17
26
|
end
|
|
18
27
|
|
|
19
28
|
# List all available tools
|
|
@@ -47,6 +56,68 @@ module ClaudeAgentSDK
|
|
|
47
56
|
result
|
|
48
57
|
end
|
|
49
58
|
|
|
59
|
+
# List all available resources
|
|
60
|
+
# @return [Array<Hash>] Array of resource definitions
|
|
61
|
+
def list_resources
|
|
62
|
+
@resources.map do |resource|
|
|
63
|
+
{
|
|
64
|
+
uri: resource.uri,
|
|
65
|
+
name: resource.name,
|
|
66
|
+
description: resource.description,
|
|
67
|
+
mimeType: resource.mime_type
|
|
68
|
+
}.compact
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Read a resource by URI
|
|
73
|
+
# @param uri [String] Resource URI
|
|
74
|
+
# @return [Hash] Resource content
|
|
75
|
+
def read_resource(uri)
|
|
76
|
+
resource = @resource_map[uri]
|
|
77
|
+
raise "Resource '#{uri}' not found" unless resource
|
|
78
|
+
|
|
79
|
+
# Call the resource's reader
|
|
80
|
+
content = resource.reader.call
|
|
81
|
+
|
|
82
|
+
# Ensure content has the expected format
|
|
83
|
+
unless content.is_a?(Hash) && content[:contents]
|
|
84
|
+
raise "Resource '#{uri}' must return a hash with :contents key"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
content
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# List all available prompts
|
|
91
|
+
# @return [Array<Hash>] Array of prompt definitions
|
|
92
|
+
def list_prompts
|
|
93
|
+
@prompts.map do |prompt|
|
|
94
|
+
{
|
|
95
|
+
name: prompt.name,
|
|
96
|
+
description: prompt.description,
|
|
97
|
+
arguments: prompt.arguments
|
|
98
|
+
}.compact
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Get a prompt by name
|
|
103
|
+
# @param name [String] Prompt name
|
|
104
|
+
# @param arguments [Hash] Arguments to fill in the prompt template
|
|
105
|
+
# @return [Hash] Prompt with filled-in arguments
|
|
106
|
+
def get_prompt(name, arguments = {})
|
|
107
|
+
prompt = @prompt_map[name]
|
|
108
|
+
raise "Prompt '#{name}' not found" unless prompt
|
|
109
|
+
|
|
110
|
+
# Call the prompt's generator
|
|
111
|
+
result = prompt.generator.call(arguments)
|
|
112
|
+
|
|
113
|
+
# Ensure result has the expected format
|
|
114
|
+
unless result.is_a?(Hash) && result[:messages]
|
|
115
|
+
raise "Prompt '#{name}' must return a hash with :messages key"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
result
|
|
119
|
+
end
|
|
120
|
+
|
|
50
121
|
private
|
|
51
122
|
|
|
52
123
|
def convert_input_schema(schema)
|
|
@@ -130,11 +201,123 @@ module ClaudeAgentSDK
|
|
|
130
201
|
)
|
|
131
202
|
end
|
|
132
203
|
|
|
204
|
+
# Helper function to create a resource definition
|
|
205
|
+
#
|
|
206
|
+
# @param uri [String] Unique identifier for the resource (e.g., "file:///path/to/file")
|
|
207
|
+
# @param name [String] Human-readable name
|
|
208
|
+
# @param description [String, nil] Optional description
|
|
209
|
+
# @param mime_type [String, nil] Optional MIME type (e.g., "text/plain", "application/json")
|
|
210
|
+
# @param reader [Proc] Block that returns the resource content
|
|
211
|
+
# @return [SdkMcpResource] Resource definition
|
|
212
|
+
#
|
|
213
|
+
# @example File resource
|
|
214
|
+
# resource = create_resource(
|
|
215
|
+
# uri: 'file:///config/settings.json',
|
|
216
|
+
# name: 'Application Settings',
|
|
217
|
+
# description: 'Current application configuration',
|
|
218
|
+
# mime_type: 'application/json'
|
|
219
|
+
# ) do
|
|
220
|
+
# content = File.read('/path/to/settings.json')
|
|
221
|
+
# {
|
|
222
|
+
# contents: [{
|
|
223
|
+
# uri: 'file:///config/settings.json',
|
|
224
|
+
# mimeType: 'application/json',
|
|
225
|
+
# text: content
|
|
226
|
+
# }]
|
|
227
|
+
# }
|
|
228
|
+
# end
|
|
229
|
+
#
|
|
230
|
+
# @example Database resource
|
|
231
|
+
# resource = create_resource(
|
|
232
|
+
# uri: 'db://users/count',
|
|
233
|
+
# name: 'User Count',
|
|
234
|
+
# description: 'Total number of registered users'
|
|
235
|
+
# ) do
|
|
236
|
+
# count = User.count
|
|
237
|
+
# {
|
|
238
|
+
# contents: [{
|
|
239
|
+
# uri: 'db://users/count',
|
|
240
|
+
# mimeType: 'text/plain',
|
|
241
|
+
# text: count.to_s
|
|
242
|
+
# }]
|
|
243
|
+
# }
|
|
244
|
+
# end
|
|
245
|
+
def self.create_resource(uri:, name:, description: nil, mime_type: nil, &reader)
|
|
246
|
+
raise ArgumentError, 'Block required for resource reader' unless reader
|
|
247
|
+
|
|
248
|
+
SdkMcpResource.new(
|
|
249
|
+
uri: uri,
|
|
250
|
+
name: name,
|
|
251
|
+
description: description,
|
|
252
|
+
mime_type: mime_type,
|
|
253
|
+
reader: reader
|
|
254
|
+
)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Helper function to create a prompt definition
|
|
258
|
+
#
|
|
259
|
+
# @param name [String] Unique identifier for the prompt
|
|
260
|
+
# @param description [String, nil] Optional description
|
|
261
|
+
# @param arguments [Array<Hash>, nil] Optional argument definitions
|
|
262
|
+
# @param generator [Proc] Block that generates prompt messages
|
|
263
|
+
# @return [SdkMcpPrompt] Prompt definition
|
|
264
|
+
#
|
|
265
|
+
# @example Simple prompt
|
|
266
|
+
# prompt = create_prompt(
|
|
267
|
+
# name: 'code_review',
|
|
268
|
+
# description: 'Review code for best practices'
|
|
269
|
+
# ) do |args|
|
|
270
|
+
# {
|
|
271
|
+
# messages: [
|
|
272
|
+
# {
|
|
273
|
+
# role: 'user',
|
|
274
|
+
# content: {
|
|
275
|
+
# type: 'text',
|
|
276
|
+
# text: 'Please review this code for best practices and suggest improvements.'
|
|
277
|
+
# }
|
|
278
|
+
# }
|
|
279
|
+
# ]
|
|
280
|
+
# }
|
|
281
|
+
# end
|
|
282
|
+
#
|
|
283
|
+
# @example Prompt with arguments
|
|
284
|
+
# prompt = create_prompt(
|
|
285
|
+
# name: 'git_commit',
|
|
286
|
+
# description: 'Generate a git commit message',
|
|
287
|
+
# arguments: [
|
|
288
|
+
# { name: 'changes', description: 'Description of changes', required: true }
|
|
289
|
+
# ]
|
|
290
|
+
# ) do |args|
|
|
291
|
+
# {
|
|
292
|
+
# messages: [
|
|
293
|
+
# {
|
|
294
|
+
# role: 'user',
|
|
295
|
+
# content: {
|
|
296
|
+
# type: 'text',
|
|
297
|
+
# text: "Generate a concise git commit message for: #{args[:changes]}"
|
|
298
|
+
# }
|
|
299
|
+
# }
|
|
300
|
+
# ]
|
|
301
|
+
# }
|
|
302
|
+
# end
|
|
303
|
+
def self.create_prompt(name:, description: nil, arguments: nil, &generator)
|
|
304
|
+
raise ArgumentError, 'Block required for prompt generator' unless generator
|
|
305
|
+
|
|
306
|
+
SdkMcpPrompt.new(
|
|
307
|
+
name: name,
|
|
308
|
+
description: description,
|
|
309
|
+
arguments: arguments,
|
|
310
|
+
generator: generator
|
|
311
|
+
)
|
|
312
|
+
end
|
|
313
|
+
|
|
133
314
|
# Create an SDK MCP server
|
|
134
315
|
#
|
|
135
316
|
# @param name [String] Unique identifier for the server
|
|
136
317
|
# @param version [String] Server version (default: '1.0.0')
|
|
137
318
|
# @param tools [Array<SdkMcpTool>] List of tool definitions
|
|
319
|
+
# @param resources [Array<SdkMcpResource>] List of resource definitions
|
|
320
|
+
# @param prompts [Array<SdkMcpPrompt>] List of prompt definitions
|
|
138
321
|
# @return [Hash] MCP server configuration for ClaudeAgentOptions
|
|
139
322
|
#
|
|
140
323
|
# @example Simple calculator server
|
|
@@ -152,8 +335,31 @@ module ClaudeAgentSDK
|
|
|
152
335
|
# mcp_servers: { calc: calculator },
|
|
153
336
|
# allowed_tools: ['mcp__calc__add']
|
|
154
337
|
# )
|
|
155
|
-
|
|
156
|
-
|
|
338
|
+
#
|
|
339
|
+
# @example Server with resources and prompts
|
|
340
|
+
# config_resource = ClaudeAgentSDK.create_resource(
|
|
341
|
+
# uri: 'config://app',
|
|
342
|
+
# name: 'App Config'
|
|
343
|
+
# ) { { contents: [{ uri: 'config://app', text: 'config data' }] } }
|
|
344
|
+
#
|
|
345
|
+
# review_prompt = ClaudeAgentSDK.create_prompt(
|
|
346
|
+
# name: 'review',
|
|
347
|
+
# description: 'Code review'
|
|
348
|
+
# ) { { messages: [{ role: 'user', content: { type: 'text', text: 'Review this' } }] } }
|
|
349
|
+
#
|
|
350
|
+
# server = ClaudeAgentSDK.create_sdk_mcp_server(
|
|
351
|
+
# name: 'dev-tools',
|
|
352
|
+
# resources: [config_resource],
|
|
353
|
+
# prompts: [review_prompt]
|
|
354
|
+
# )
|
|
355
|
+
def self.create_sdk_mcp_server(name:, version: '1.0.0', tools: [], resources: [], prompts: [])
|
|
356
|
+
server = SdkMcpServer.new(
|
|
357
|
+
name: name,
|
|
358
|
+
version: version,
|
|
359
|
+
tools: tools,
|
|
360
|
+
resources: resources,
|
|
361
|
+
prompts: prompts
|
|
362
|
+
)
|
|
157
363
|
|
|
158
364
|
# Return configuration for ClaudeAgentOptions
|
|
159
365
|
{
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module ClaudeAgentSDK
|
|
6
|
+
# Streaming input helpers for Claude Agent SDK
|
|
7
|
+
module Streaming
|
|
8
|
+
# Create a user message for streaming input
|
|
9
|
+
#
|
|
10
|
+
# @param content [String] The message content
|
|
11
|
+
# @param session_id [String] Session identifier
|
|
12
|
+
# @param parent_tool_use_id [String, nil] Parent tool use ID if responding to a tool
|
|
13
|
+
# @return [String] JSON-encoded message
|
|
14
|
+
def self.user_message(content, session_id: 'default', parent_tool_use_id: nil)
|
|
15
|
+
message = {
|
|
16
|
+
type: 'user',
|
|
17
|
+
message: {
|
|
18
|
+
role: 'user',
|
|
19
|
+
content: content
|
|
20
|
+
},
|
|
21
|
+
parent_tool_use_id: parent_tool_use_id,
|
|
22
|
+
session_id: session_id
|
|
23
|
+
}
|
|
24
|
+
JSON.generate(message) + "\n"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Create an Enumerator from an array of messages
|
|
28
|
+
#
|
|
29
|
+
# @param messages [Array<String>] Array of message strings
|
|
30
|
+
# @param session_id [String] Session identifier
|
|
31
|
+
# @return [Enumerator] Enumerator yielding JSON-encoded messages
|
|
32
|
+
#
|
|
33
|
+
# @example
|
|
34
|
+
# messages = ['Hello', 'What is 2+2?', 'Thanks!']
|
|
35
|
+
# stream = ClaudeAgentSDK::Streaming.from_array(messages)
|
|
36
|
+
def self.from_array(messages, session_id: 'default')
|
|
37
|
+
Enumerator.new do |yielder|
|
|
38
|
+
messages.each do |content|
|
|
39
|
+
yielder << user_message(content, session_id: session_id)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Create an Enumerator from a block
|
|
45
|
+
#
|
|
46
|
+
# @yield Block that yields message strings
|
|
47
|
+
# @param session_id [String] Session identifier
|
|
48
|
+
# @return [Enumerator] Enumerator yielding JSON-encoded messages
|
|
49
|
+
#
|
|
50
|
+
# @example
|
|
51
|
+
# stream = ClaudeAgentSDK::Streaming.from_block do |yielder|
|
|
52
|
+
# yielder.yield('First message')
|
|
53
|
+
# sleep 1
|
|
54
|
+
# yielder.yield('Second message')
|
|
55
|
+
# end
|
|
56
|
+
def self.from_block(session_id: 'default', &block)
|
|
57
|
+
Enumerator.new do |yielder|
|
|
58
|
+
collector = Object.new
|
|
59
|
+
def collector.yield(content)
|
|
60
|
+
@content = content
|
|
61
|
+
end
|
|
62
|
+
def collector.content
|
|
63
|
+
@content
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
inner_enum = Enumerator.new(&block)
|
|
67
|
+
inner_enum.each do |content|
|
|
68
|
+
yielder << user_message(content, session_id: session_id)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -162,23 +162,14 @@ module ClaudeAgentSDK
|
|
|
162
162
|
should_pipe_stderr = @options.stderr || @options.extra_args.key?('debug-to-stderr')
|
|
163
163
|
|
|
164
164
|
begin
|
|
165
|
-
# Start process
|
|
166
|
-
|
|
167
|
-
*cmd,
|
|
168
|
-
stdin: :pipe,
|
|
169
|
-
stdout: :pipe,
|
|
170
|
-
stderr: should_pipe_stderr ? :pipe : nil,
|
|
171
|
-
chdir: @cwd&.to_s,
|
|
172
|
-
env: process_env
|
|
173
|
-
)
|
|
165
|
+
# Start process using Open3
|
|
166
|
+
opts = { chdir: @cwd&.to_s }.compact
|
|
174
167
|
|
|
175
|
-
@stdout
|
|
176
|
-
@stdin = @process.stdin if @is_streaming
|
|
168
|
+
@stdin, @stdout, @stderr, @process = Open3.popen3(process_env, *cmd, opts)
|
|
177
169
|
|
|
178
170
|
# Handle stderr if piped
|
|
179
|
-
if should_pipe_stderr && @
|
|
180
|
-
@
|
|
181
|
-
@stderr_task = Async do
|
|
171
|
+
if should_pipe_stderr && @stderr
|
|
172
|
+
@stderr_task = Thread.new do
|
|
182
173
|
handle_stderr
|
|
183
174
|
rescue StandardError
|
|
184
175
|
# Ignore errors during stderr reading
|
|
@@ -186,7 +177,8 @@ module ClaudeAgentSDK
|
|
|
186
177
|
end
|
|
187
178
|
|
|
188
179
|
# Close stdin for non-streaming mode
|
|
189
|
-
@
|
|
180
|
+
@stdin.close unless @is_streaming
|
|
181
|
+
@stdin = nil unless @is_streaming
|
|
190
182
|
|
|
191
183
|
@ready = true
|
|
192
184
|
rescue Errno::ENOENT => e
|
|
@@ -223,8 +215,11 @@ module ClaudeAgentSDK
|
|
|
223
215
|
@ready = false
|
|
224
216
|
return unless @process
|
|
225
217
|
|
|
226
|
-
#
|
|
227
|
-
@stderr_task&.
|
|
218
|
+
# Kill stderr thread
|
|
219
|
+
if @stderr_task&.alive?
|
|
220
|
+
@stderr_task.kill
|
|
221
|
+
@stderr_task.join(1) rescue nil
|
|
222
|
+
end
|
|
228
223
|
|
|
229
224
|
# Close streams
|
|
230
225
|
begin
|
|
@@ -232,6 +227,11 @@ module ClaudeAgentSDK
|
|
|
232
227
|
rescue StandardError
|
|
233
228
|
# Ignore
|
|
234
229
|
end
|
|
230
|
+
begin
|
|
231
|
+
@stdout&.close
|
|
232
|
+
rescue StandardError
|
|
233
|
+
# Ignore
|
|
234
|
+
end
|
|
235
235
|
begin
|
|
236
236
|
@stderr&.close
|
|
237
237
|
rescue StandardError
|
|
@@ -240,8 +240,8 @@ module ClaudeAgentSDK
|
|
|
240
240
|
|
|
241
241
|
# Terminate process
|
|
242
242
|
begin
|
|
243
|
-
@process.
|
|
244
|
-
@process.
|
|
243
|
+
Process.kill('TERM', @process.pid) if @process.alive?
|
|
244
|
+
@process.value
|
|
245
245
|
rescue StandardError
|
|
246
246
|
# Ignore
|
|
247
247
|
end
|
|
@@ -250,12 +250,13 @@ module ClaudeAgentSDK
|
|
|
250
250
|
@stdout = nil
|
|
251
251
|
@stdin = nil
|
|
252
252
|
@stderr = nil
|
|
253
|
+
@stderr_task = nil
|
|
253
254
|
@exit_error = nil
|
|
254
255
|
end
|
|
255
256
|
|
|
256
257
|
def write(data)
|
|
257
258
|
raise CLIConnectionError, 'ProcessTransport is not ready for writing' unless @ready && @stdin
|
|
258
|
-
raise CLIConnectionError, "Cannot write to terminated process" if @process &&
|
|
259
|
+
raise CLIConnectionError, "Cannot write to terminated process" if @process && !@process.alive?
|
|
259
260
|
|
|
260
261
|
raise CLIConnectionError, "Cannot write to process that exited with error: #{@exit_error}" if @exit_error
|
|
261
262
|
|
|
@@ -326,7 +327,7 @@ module ClaudeAgentSDK
|
|
|
326
327
|
end
|
|
327
328
|
|
|
328
329
|
# Check process completion
|
|
329
|
-
status = @process.
|
|
330
|
+
status = @process.value
|
|
330
331
|
returncode = status.exitstatus
|
|
331
332
|
|
|
332
333
|
if returncode && returncode != 0
|
|
@@ -355,4 +355,29 @@ module ClaudeAgentSDK
|
|
|
355
355
|
@handler = handler
|
|
356
356
|
end
|
|
357
357
|
end
|
|
358
|
+
|
|
359
|
+
# SDK MCP Resource definition
|
|
360
|
+
class SdkMcpResource
|
|
361
|
+
attr_accessor :uri, :name, :description, :mime_type, :reader
|
|
362
|
+
|
|
363
|
+
def initialize(uri:, name:, description: nil, mime_type: nil, reader:)
|
|
364
|
+
@uri = uri
|
|
365
|
+
@name = name
|
|
366
|
+
@description = description
|
|
367
|
+
@mime_type = mime_type
|
|
368
|
+
@reader = reader
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# SDK MCP Prompt definition
|
|
373
|
+
class SdkMcpPrompt
|
|
374
|
+
attr_accessor :name, :description, :arguments, :generator
|
|
375
|
+
|
|
376
|
+
def initialize(name:, description: nil, arguments: nil, generator:)
|
|
377
|
+
@name = name
|
|
378
|
+
@description = description
|
|
379
|
+
@arguments = arguments
|
|
380
|
+
@generator = generator
|
|
381
|
+
end
|
|
382
|
+
end
|
|
358
383
|
end
|
data/lib/claude_agent_sdk.rb
CHANGED
|
@@ -8,6 +8,7 @@ require_relative 'claude_agent_sdk/subprocess_cli_transport'
|
|
|
8
8
|
require_relative 'claude_agent_sdk/message_parser'
|
|
9
9
|
require_relative 'claude_agent_sdk/query'
|
|
10
10
|
require_relative 'claude_agent_sdk/sdk_mcp_server'
|
|
11
|
+
require_relative 'claude_agent_sdk/streaming'
|
|
11
12
|
require 'async'
|
|
12
13
|
require 'securerandom'
|
|
13
14
|
|
|
@@ -18,7 +19,7 @@ module ClaudeAgentSDK
|
|
|
18
19
|
# This function is ideal for simple, stateless queries where you don't need
|
|
19
20
|
# bidirectional communication or conversation management.
|
|
20
21
|
#
|
|
21
|
-
# @param prompt [String] The prompt to send to Claude
|
|
22
|
+
# @param prompt [String, Enumerator] The prompt to send to Claude, or an Enumerator for streaming input
|
|
22
23
|
# @param options [ClaudeAgentOptions] Optional configuration
|
|
23
24
|
# @yield [Message] Each message from the conversation
|
|
24
25
|
# @return [Enumerator] if no block given
|
|
@@ -40,6 +41,12 @@ module ClaudeAgentSDK
|
|
|
40
41
|
# end
|
|
41
42
|
# end
|
|
42
43
|
# end
|
|
44
|
+
#
|
|
45
|
+
# @example Streaming input
|
|
46
|
+
# messages = Streaming.from_array(['Hello', 'What is 2+2?', 'Thanks!'])
|
|
47
|
+
# ClaudeAgentSDK.query(prompt: messages) do |message|
|
|
48
|
+
# puts message
|
|
49
|
+
# end
|
|
43
50
|
def self.query(prompt:, options: nil, &block)
|
|
44
51
|
return enum_for(:query, prompt: prompt, options: options) unless block
|
|
45
52
|
|
|
@@ -50,6 +57,21 @@ module ClaudeAgentSDK
|
|
|
50
57
|
transport = SubprocessCLITransport.new(prompt, options)
|
|
51
58
|
begin
|
|
52
59
|
transport.connect
|
|
60
|
+
|
|
61
|
+
# If prompt is an Enumerator, write each message to stdin
|
|
62
|
+
if prompt.is_a?(Enumerator) || prompt.respond_to?(:each)
|
|
63
|
+
Async do
|
|
64
|
+
begin
|
|
65
|
+
prompt.each do |message_json|
|
|
66
|
+
transport.write(message_json)
|
|
67
|
+
end
|
|
68
|
+
ensure
|
|
69
|
+
transport.end_input
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Read and yield messages
|
|
53
75
|
transport.read_messages do |data|
|
|
54
76
|
message = MessageParser.parse(data)
|
|
55
77
|
block.call(message)
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: claude-agent-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Community Contributors
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2025-10-
|
|
10
|
+
date: 2025-10-15 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: async
|
|
@@ -94,6 +94,7 @@ files:
|
|
|
94
94
|
- lib/claude_agent_sdk/message_parser.rb
|
|
95
95
|
- lib/claude_agent_sdk/query.rb
|
|
96
96
|
- lib/claude_agent_sdk/sdk_mcp_server.rb
|
|
97
|
+
- lib/claude_agent_sdk/streaming.rb
|
|
97
98
|
- lib/claude_agent_sdk/subprocess_cli_transport.rb
|
|
98
99
|
- lib/claude_agent_sdk/transport.rb
|
|
99
100
|
- lib/claude_agent_sdk/types.rb
|