claude-agent-sdk 0.1.2 → 0.1.4
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 +8 -0
- data/README.md +98 -6
- 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/types.rb +25 -0
- data/lib/claude_agent_sdk/version.rb +1 -1
- data/lib/claude_agent_sdk.rb +29 -4
- 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: 4d8b669f4c3f02dea0763113551838bb0a3a5ec514125876f28dbc5d23ee44aa
|
|
4
|
+
data.tar.gz: 6ce126558a2d99e3b6aba03527d5b4ce8954ff3a99a13ee25f54dd6fa0141980
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 54e48e83a98845d56d6dc4c1843ebf8728fd74ffd3e7670e826c0fdbf3ef297359c18f5b27c2fac0757da0fab5e28d6c19ab084ac1842c5615d06cf52a0a94f8
|
|
7
|
+
data.tar.gz: 224aca0535d196fa3b348da69af9e4cbcc46276af8d36b7b58f6e35811704f93506ff31df7449e9aae9ebd252c8b5c035fa581dc4d500a5bade82d0d15f37ddc
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,14 @@ 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
|
+
|
|
8
16
|
## [0.1.2] - 2025-10-14
|
|
9
17
|
|
|
10
18
|
### 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,9 +94,48 @@ 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
|
-
`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.
|
|
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.
|
|
135
|
+
|
|
136
|
+
**The Client class automatically uses streaming mode** for bidirectional communication, allowing you to send multiple queries dynamically during a single session without closing the connection. This matches the Python SDK's `ClaudeSDKClient` behavior.
|
|
137
|
+
|
|
138
|
+
See [lib/claude_agent_sdk.rb](lib/claude_agent_sdk.rb) for implementation details.
|
|
98
139
|
|
|
99
140
|
### Custom Tools (SDK MCP Servers)
|
|
100
141
|
|
|
@@ -193,6 +234,58 @@ options = ClaudeAgentSDK::ClaudeAgentOptions.new(
|
|
|
193
234
|
)
|
|
194
235
|
```
|
|
195
236
|
|
|
237
|
+
#### MCP Resources and Prompts
|
|
238
|
+
|
|
239
|
+
SDK MCP servers can also expose **resources** (data sources) and **prompts** (reusable templates):
|
|
240
|
+
|
|
241
|
+
```ruby
|
|
242
|
+
# Create a resource (data source Claude can read)
|
|
243
|
+
config_resource = ClaudeAgentSDK.create_resource(
|
|
244
|
+
uri: 'config://app/settings',
|
|
245
|
+
name: 'Application Settings',
|
|
246
|
+
description: 'Current app configuration',
|
|
247
|
+
mime_type: 'application/json'
|
|
248
|
+
) do
|
|
249
|
+
config_data = { app_name: 'MyApp', version: '1.0.0' }
|
|
250
|
+
{
|
|
251
|
+
contents: [{
|
|
252
|
+
uri: 'config://app/settings',
|
|
253
|
+
mimeType: 'application/json',
|
|
254
|
+
text: JSON.pretty_generate(config_data)
|
|
255
|
+
}]
|
|
256
|
+
}
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Create a prompt template
|
|
260
|
+
review_prompt = ClaudeAgentSDK.create_prompt(
|
|
261
|
+
name: 'code_review',
|
|
262
|
+
description: 'Review code for best practices',
|
|
263
|
+
arguments: [
|
|
264
|
+
{ name: 'code', description: 'Code to review', required: true }
|
|
265
|
+
]
|
|
266
|
+
) do |args|
|
|
267
|
+
{
|
|
268
|
+
messages: [{
|
|
269
|
+
role: 'user',
|
|
270
|
+
content: {
|
|
271
|
+
type: 'text',
|
|
272
|
+
text: "Review this code: #{args[:code]}"
|
|
273
|
+
}
|
|
274
|
+
}]
|
|
275
|
+
}
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Create server with tools, resources, and prompts
|
|
279
|
+
server = ClaudeAgentSDK.create_sdk_mcp_server(
|
|
280
|
+
name: 'dev-tools',
|
|
281
|
+
tools: [my_tool],
|
|
282
|
+
resources: [config_resource],
|
|
283
|
+
prompts: [review_prompt]
|
|
284
|
+
)
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
For complete examples, see [examples/mcp_resources_prompts_example.rb](examples/mcp_resources_prompts_example.rb).
|
|
288
|
+
|
|
196
289
|
### Basic Client Usage
|
|
197
290
|
|
|
198
291
|
```ruby
|
|
@@ -203,6 +296,7 @@ Async do
|
|
|
203
296
|
client = ClaudeAgentSDK::Client.new
|
|
204
297
|
|
|
205
298
|
begin
|
|
299
|
+
# Connect automatically uses streaming mode for bidirectional communication
|
|
206
300
|
client.connect
|
|
207
301
|
|
|
208
302
|
# Send a query
|
|
@@ -415,7 +509,9 @@ See the following examples for complete working code:
|
|
|
415
509
|
|
|
416
510
|
- [examples/quick_start.rb](examples/quick_start.rb) - Basic `query()` usage with options
|
|
417
511
|
- [examples/client_example.rb](examples/client_example.rb) - Interactive Client usage
|
|
512
|
+
- [examples/streaming_input_example.rb](examples/streaming_input_example.rb) - Streaming input for multi-turn conversations
|
|
418
513
|
- [examples/mcp_calculator.rb](examples/mcp_calculator.rb) - Custom tools with SDK MCP servers
|
|
514
|
+
- [examples/mcp_resources_prompts_example.rb](examples/mcp_resources_prompts_example.rb) - MCP resources and prompts
|
|
419
515
|
- [examples/hooks_example.rb](examples/hooks_example.rb) - Using hooks to control tool execution
|
|
420
516
|
- [examples/permission_callback_example.rb](examples/permission_callback_example.rb) - Dynamic tool permission control
|
|
421
517
|
|
|
@@ -423,10 +519,6 @@ See the following examples for complete working code:
|
|
|
423
519
|
|
|
424
520
|
After checking out the repo, run `bundle install` to install dependencies. Then, run `bundle exec rspec` to run the tests.
|
|
425
521
|
|
|
426
|
-
## Contributing
|
|
427
|
-
|
|
428
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/anthropics/claude-agent-sdk-ruby.
|
|
429
|
-
|
|
430
522
|
## License
|
|
431
523
|
|
|
432
524
|
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
|
|
@@ -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)
|
|
@@ -64,11 +86,12 @@ module ClaudeAgentSDK
|
|
|
64
86
|
#
|
|
65
87
|
# This client provides full control over the conversation flow with support
|
|
66
88
|
# for streaming, hooks, permission callbacks, and dynamic message sending.
|
|
89
|
+
# The Client class always uses streaming mode for bidirectional communication.
|
|
67
90
|
#
|
|
68
91
|
# @example Basic usage
|
|
69
92
|
# Async do
|
|
70
93
|
# client = ClaudeAgentSDK::Client.new
|
|
71
|
-
# client.connect
|
|
94
|
+
# client.connect # No arguments needed - automatically uses streaming mode
|
|
72
95
|
#
|
|
73
96
|
# client.query("What is the capital of France?")
|
|
74
97
|
# client.receive_response do |msg|
|
|
@@ -128,8 +151,10 @@ module ClaudeAgentSDK
|
|
|
128
151
|
configured_options = @options.dup_with(permission_prompt_tool_name: 'stdio')
|
|
129
152
|
end
|
|
130
153
|
|
|
131
|
-
#
|
|
132
|
-
|
|
154
|
+
# Auto-connect with empty enumerator if no prompt is provided
|
|
155
|
+
# This matches the Python SDK pattern where ClaudeSDKClient always uses streaming mode
|
|
156
|
+
# An empty enumerator keeps stdin open for bidirectional communication
|
|
157
|
+
actual_prompt = prompt || [].to_enum
|
|
133
158
|
@transport = SubprocessCLITransport.new(actual_prompt, configured_options)
|
|
134
159
|
@transport.connect
|
|
135
160
|
|
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.4
|
|
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-17 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
|