anthropic 1.40.0 → 1.41.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 +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +1 -1
- data/lib/anthropic/credentials/workload_identity.rb +1 -1
- data/lib/anthropic/helpers/messages.rb +13 -0
- data/lib/anthropic/helpers/tools/base_tool.rb +18 -0
- data/lib/anthropic/helpers/tools/mcp.rb +485 -0
- data/lib/anthropic/internal/transport/base_client.rb +2 -0
- data/lib/anthropic/mcp.rb +5 -0
- data/lib/anthropic/models/anthropic_beta.rb +3 -0
- data/lib/anthropic/models/beta/beta_cache_miss_messages_changed.rb +31 -0
- data/lib/anthropic/models/beta/beta_cache_miss_model_changed.rb +31 -0
- data/lib/anthropic/models/beta/beta_cache_miss_previous_message_not_found.rb +19 -0
- data/lib/anthropic/models/beta/beta_cache_miss_system_changed.rb +31 -0
- data/lib/anthropic/models/beta/beta_cache_miss_tools_changed.rb +31 -0
- data/lib/anthropic/models/beta/beta_cache_miss_unavailable.rb +19 -0
- data/lib/anthropic/models/beta/beta_diagnostics.rb +60 -0
- data/lib/anthropic/models/beta/beta_diagnostics_param.rb +30 -0
- data/lib/anthropic/models/beta/beta_managed_agents_outcome_evaluation_resource.rb +4 -4
- data/lib/anthropic/models/beta/beta_message.rb +10 -1
- data/lib/anthropic/models/beta/message_create_params.rb +10 -1
- data/lib/anthropic/models/beta/messages/batch_create_params.rb +10 -1
- data/lib/anthropic/models/beta/sessions/beta_managed_agents_agent_mcp_tool_result_event.rb +6 -3
- data/lib/anthropic/models/beta/sessions/beta_managed_agents_agent_tool_result_event.rb +6 -3
- data/lib/anthropic/models/beta/sessions/beta_managed_agents_search_result_block.rb +64 -0
- data/lib/anthropic/models/beta/sessions/beta_managed_agents_search_result_citations.rb +22 -0
- data/lib/anthropic/models/beta/sessions/beta_managed_agents_search_result_content.rb +39 -0
- data/lib/anthropic/models/beta/sessions/beta_managed_agents_user_custom_tool_result_event.rb +6 -3
- data/lib/anthropic/models/beta/sessions/beta_managed_agents_user_custom_tool_result_event_params.rb +6 -3
- data/lib/anthropic/resources/beta/messages.rb +7 -2
- data/lib/anthropic/resources/messages.rb +2 -2
- data/lib/anthropic/version.rb +1 -1
- data/lib/anthropic.rb +13 -0
- data/rbi/anthropic/helpers/aws/client.rbi +3 -2
- data/rbi/anthropic/models/anthropic_beta.rbi +5 -0
- data/rbi/anthropic/models/beta/beta_cache_miss_messages_changed.rbi +46 -0
- data/rbi/anthropic/models/beta/beta_cache_miss_model_changed.rbi +46 -0
- data/rbi/anthropic/models/beta/beta_cache_miss_previous_message_not_found.rbi +31 -0
- data/rbi/anthropic/models/beta/beta_cache_miss_system_changed.rbi +46 -0
- data/rbi/anthropic/models/beta/beta_cache_miss_tools_changed.rbi +46 -0
- data/rbi/anthropic/models/beta/beta_cache_miss_unavailable.rbi +30 -0
- data/rbi/anthropic/models/beta/beta_diagnostics.rbi +101 -0
- data/rbi/anthropic/models/beta/beta_diagnostics_param.rbi +48 -0
- data/rbi/anthropic/models/beta/beta_managed_agents_outcome_evaluation_resource.rbi +6 -6
- data/rbi/anthropic/models/beta/beta_message.rbi +17 -0
- data/rbi/anthropic/models/beta/message_create_params.rbi +19 -0
- data/rbi/anthropic/models/beta/messages/batch_create_params.rbi +20 -0
- data/rbi/anthropic/models/beta/sessions/beta_managed_agents_agent_mcp_tool_result_event.rbi +6 -3
- data/rbi/anthropic/models/beta/sessions/beta_managed_agents_agent_tool_result_event.rbi +6 -3
- data/rbi/anthropic/models/beta/sessions/beta_managed_agents_search_result_block.rbi +136 -0
- data/rbi/anthropic/models/beta/sessions/beta_managed_agents_search_result_citations.rbi +35 -0
- data/rbi/anthropic/models/beta/sessions/beta_managed_agents_search_result_content.rbi +86 -0
- data/rbi/anthropic/models/beta/sessions/beta_managed_agents_user_custom_tool_result_event.rbi +6 -3
- data/rbi/anthropic/models/beta/sessions/beta_managed_agents_user_custom_tool_result_event_params.rbi +10 -5
- data/rbi/anthropic/resources/beta/messages.rbi +10 -0
- data/sig/anthropic/models/anthropic_beta.rbs +2 -0
- data/sig/anthropic/models/beta/beta_cache_miss_messages_changed.rbs +26 -0
- data/sig/anthropic/models/beta/beta_cache_miss_model_changed.rbs +26 -0
- data/sig/anthropic/models/beta/beta_cache_miss_previous_message_not_found.rbs +18 -0
- data/sig/anthropic/models/beta/beta_cache_miss_system_changed.rbs +26 -0
- data/sig/anthropic/models/beta/beta_cache_miss_tools_changed.rbs +26 -0
- data/sig/anthropic/models/beta/beta_cache_miss_unavailable.rbs +17 -0
- data/sig/anthropic/models/beta/beta_diagnostics.rbs +38 -0
- data/sig/anthropic/models/beta/beta_diagnostics_param.rbs +17 -0
- data/sig/anthropic/models/beta/beta_message.rbs +5 -0
- data/sig/anthropic/models/beta/message_create_params.rbs +5 -0
- data/sig/anthropic/models/beta/messages/batch_create_params.rbs +5 -0
- data/sig/anthropic/models/beta/sessions/beta_managed_agents_agent_mcp_tool_result_event.rbs +1 -0
- data/sig/anthropic/models/beta/sessions/beta_managed_agents_agent_tool_result_event.rbs +1 -0
- data/sig/anthropic/models/beta/sessions/beta_managed_agents_search_result_block.rbs +54 -0
- data/sig/anthropic/models/beta/sessions/beta_managed_agents_search_result_citations.rbs +17 -0
- data/sig/anthropic/models/beta/sessions/beta_managed_agents_search_result_content.rbs +39 -0
- data/sig/anthropic/models/beta/sessions/beta_managed_agents_user_custom_tool_result_event.rbs +1 -0
- data/sig/anthropic/models/beta/sessions/beta_managed_agents_user_custom_tool_result_event_params.rbs +1 -0
- data/sig/anthropic/resources/beta/messages.rbs +2 -0
- metadata +37 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fc1a7f57c129c15f70a549ddb40df06a00fab53532406c86722ec253de5247cc
|
|
4
|
+
data.tar.gz: e70e7a9bcd6e5fbc5f9bc904a7475cde990e7e9f393d232c2f2e4e53ec74cc62
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 76e09bc1ab37e2aed7659956e5cfa1c0511e5947940e76c1c465b5098c6bb50526d8b918f4d4c7a677a7eabfb71e557118e02dd3b55cf639c3a6958433706627
|
|
7
|
+
data.tar.gz: bcf9f66aae233ecb1dde11d4468cfc557529688a0490bde5d01036a1c87b480fc2a2814e487f34ff27640ef089f3ccbb7a3395bc0757d3d99fac5b0245479cbd
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.41.0 (2026-05-13)
|
|
4
|
+
|
|
5
|
+
Full Changelog: [v1.40.0...v1.41.0](https://github.com/anthropics/anthropic-sdk-ruby/compare/v1.40.0...v1.41.0)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
* **api:** Add BetaManagedAgentsSearchResultBlock types ([1432542](https://github.com/anthropics/anthropic-sdk-ruby/commit/1432542b303269f23d35e5b6891518a32b5fa934))
|
|
10
|
+
* **api:** Add support for cache diagnostics beta ([7ada493](https://github.com/anthropics/anthropic-sdk-ruby/commit/7ada4935e83b955b2f0b19e2940bc7dc3d336bc1))
|
|
11
|
+
* **mcp:** add mcp helpers ([#934](https://github.com/anthropics/anthropic-sdk-ruby/issues/934)) ([de5b608](https://github.com/anthropics/anthropic-sdk-ruby/commit/de5b60875486a62534fb95ca13153819c601af92))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
* avoid gzip buffering during streaming on non-beta messages ([#936](https://github.com/anthropics/anthropic-sdk-ruby/issues/936)) ([8b7f779](https://github.com/anthropics/anthropic-sdk-ruby/commit/8b7f779bd9bdfc7693de679832531c32c215909a))
|
|
17
|
+
* **client:** elide content type header on requests without body ([c58745a](https://github.com/anthropics/anthropic-sdk-ruby/commit/c58745aea5725531ba89aac2d64b3914232833c2))
|
|
18
|
+
* handle syntax updates for ruby 4's PRISM parser ([#189](https://github.com/anthropics/anthropic-sdk-ruby/issues/189)) ([772dd65](https://github.com/anthropics/anthropic-sdk-ruby/commit/772dd65733bfd2869f846b6a9734776ed87ee7d6))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Chores
|
|
22
|
+
|
|
23
|
+
* **api:** spec updates ([0e85c3f](https://github.com/anthropics/anthropic-sdk-ruby/commit/0e85c3f8064dd1951ef00c06ac9a709f7067eb70))
|
|
24
|
+
|
|
3
25
|
## 1.40.0 (2026-05-11)
|
|
4
26
|
|
|
5
27
|
Full Changelog: [v1.39.0...v1.40.0](https://github.com/anthropics/anthropic-sdk-ruby/compare/v1.39.0...v1.40.0)
|
data/README.md
CHANGED
|
@@ -158,7 +158,7 @@ module Anthropic
|
|
|
158
158
|
data = JSON.parse(response.body, symbolize_names: true)
|
|
159
159
|
token, expires_in =
|
|
160
160
|
case data
|
|
161
|
-
in {access_token: String => token, expires_in: Integer | String => expires_in}
|
|
161
|
+
in {access_token: String => token, expires_in: (Integer | String) => expires_in}
|
|
162
162
|
[token, expires_in.to_i]
|
|
163
163
|
in {access_token: String => token}
|
|
164
164
|
[token, 3600]
|
|
@@ -35,8 +35,20 @@ module Anthropic
|
|
|
35
35
|
|
|
36
36
|
case data
|
|
37
37
|
in {tools: Array => tool_array}
|
|
38
|
+
# rubocop:disable Metrics/BlockLength
|
|
38
39
|
mapped = tool_array.map do |tool|
|
|
39
40
|
case tool
|
|
41
|
+
# BaseTool whose class declares an explicit `tool_name` (e.g. MCP-built tools):
|
|
42
|
+
in Anthropic::Helpers::Tools::BaseTool if tool.class.tool_name
|
|
43
|
+
name = tool.class.tool_name
|
|
44
|
+
description = tool.class.doc_string
|
|
45
|
+
tools.store(name, tool)
|
|
46
|
+
input_schema = Anthropic::Helpers::InputSchema::JsonSchemaConverter.to_json_schema(tool)
|
|
47
|
+
extras = tool.class.tool_extra_props || {}
|
|
48
|
+
result = {name:, input_schema:, **extras}
|
|
49
|
+
result[:description] = description if description
|
|
50
|
+
result[:strict] = strict if strict
|
|
51
|
+
result
|
|
40
52
|
# Direct tool class:
|
|
41
53
|
in Anthropic::Helpers::InputSchema::JsonSchemaConverter
|
|
42
54
|
classname = tool.is_a?(Anthropic::Helpers::Tools::BaseTool) ? tool.class.name : tool.name
|
|
@@ -68,6 +80,7 @@ module Anthropic
|
|
|
68
80
|
tool
|
|
69
81
|
end
|
|
70
82
|
end
|
|
83
|
+
# rubocop:enable Metrics/BlockLength
|
|
71
84
|
tool_array.replace(mapped)
|
|
72
85
|
# GA: output_config with BaseModel class as format
|
|
73
86
|
in {output_config: {format: Anthropic::Helpers::InputSchema::JsonSchemaConverter => model} => output_config}
|
|
@@ -16,6 +16,24 @@ module Anthropic
|
|
|
16
16
|
# @return [String]
|
|
17
17
|
attr_reader :doc_string
|
|
18
18
|
|
|
19
|
+
# @api private
|
|
20
|
+
#
|
|
21
|
+
# When set, the runner uses this literal string as the API tool name instead of
|
|
22
|
+
# snake-casing the class name. Used by helpers (e.g. MCP) that build tools
|
|
23
|
+
# dynamically from an external definition.
|
|
24
|
+
#
|
|
25
|
+
# @return [String, nil]
|
|
26
|
+
attr_accessor :tool_name
|
|
27
|
+
|
|
28
|
+
# @api private
|
|
29
|
+
#
|
|
30
|
+
# Extra tool-definition properties merged into the API payload (e.g.
|
|
31
|
+
# `cache_control`, `defer_loading`, `allowed_callers`, `eager_input_streaming`,
|
|
32
|
+
# `input_examples`, `strict`).
|
|
33
|
+
#
|
|
34
|
+
# @return [Hash{Symbol=>Object}, nil]
|
|
35
|
+
attr_accessor :tool_extra_props
|
|
36
|
+
|
|
19
37
|
# @api public
|
|
20
38
|
#
|
|
21
39
|
# @param description [String]
|
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Anthropic
|
|
4
|
+
module Helpers
|
|
5
|
+
module Tools
|
|
6
|
+
# Helpers for integrating Model Context Protocol (MCP) servers with the
|
|
7
|
+
# Anthropic SDK.
|
|
8
|
+
#
|
|
9
|
+
# These helpers convert types returned by the `mcp` gem into the shapes
|
|
10
|
+
# the Beta Messages API accepts, so you don't have to write glue code
|
|
11
|
+
# yourself.
|
|
12
|
+
#
|
|
13
|
+
# The `mcp` gem is an optional dependency; install it separately:
|
|
14
|
+
#
|
|
15
|
+
# gem install mcp
|
|
16
|
+
#
|
|
17
|
+
# @example Convert MCP tools and run them through `tool_runner`
|
|
18
|
+
# require "mcp"
|
|
19
|
+
# require "anthropic"
|
|
20
|
+
#
|
|
21
|
+
# transport = MCP::Client::HTTP.new(url: "https://example.com/mcp")
|
|
22
|
+
# mcp_client = MCP::Client.new(transport: transport)
|
|
23
|
+
# anthropic = Anthropic::Client.new
|
|
24
|
+
#
|
|
25
|
+
# runner = anthropic.beta.messages.tool_runner(
|
|
26
|
+
# model: "claude-sonnet-4-5",
|
|
27
|
+
# max_tokens: 1024,
|
|
28
|
+
# messages: [{role: "user", content: "Use the available tools"}],
|
|
29
|
+
# tools: Anthropic::Mcp.tools(mcp_client.tools, mcp_client)
|
|
30
|
+
# )
|
|
31
|
+
# runner.run_until_finished
|
|
32
|
+
module Mcp
|
|
33
|
+
SUPPORTED_IMAGE_TYPES = Set.new(%w[image/jpeg image/png image/gif image/webp]).freeze
|
|
34
|
+
|
|
35
|
+
# Raised when an MCP value cannot be converted to a format supported by
|
|
36
|
+
# the Claude API.
|
|
37
|
+
class UnsupportedMCPValueError < Anthropic::Errors::Error; end
|
|
38
|
+
|
|
39
|
+
# @api private
|
|
40
|
+
#
|
|
41
|
+
# Runnable tool backing {Anthropic::Mcp.tool}. Each call to {build} produces
|
|
42
|
+
# a fresh anonymous subclass — that subclass owns a unique inner "parsed
|
|
43
|
+
# input" class so the runner can dispatch tool calls via
|
|
44
|
+
# `class.model === tool_use.parsed`, the same path used by hand-written
|
|
45
|
+
# {Anthropic::BaseTool} subclasses.
|
|
46
|
+
class Tool < Anthropic::Helpers::Tools::BaseTool
|
|
47
|
+
class << self
|
|
48
|
+
attr_accessor :mcp_input_schema, :mcp_client
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.build(
|
|
52
|
+
mcp_tool:,
|
|
53
|
+
mcp_client:,
|
|
54
|
+
cache_control: nil,
|
|
55
|
+
defer_loading: nil,
|
|
56
|
+
allowed_callers: nil,
|
|
57
|
+
eager_input_streaming: nil,
|
|
58
|
+
input_examples: nil,
|
|
59
|
+
strict: nil
|
|
60
|
+
)
|
|
61
|
+
api_name, description, raw_schema = Mcp.send(:extract_tool_fields, mcp_tool)
|
|
62
|
+
raise ArgumentError, "MCP tool is missing a `name`" if api_name.to_s.empty?
|
|
63
|
+
|
|
64
|
+
extras = {
|
|
65
|
+
cache_control: cache_control,
|
|
66
|
+
defer_loading: defer_loading,
|
|
67
|
+
allowed_callers: allowed_callers,
|
|
68
|
+
eager_input_streaming: eager_input_streaming,
|
|
69
|
+
input_examples: input_examples,
|
|
70
|
+
strict: strict
|
|
71
|
+
}.compact
|
|
72
|
+
|
|
73
|
+
input_class = Class.new(Hash)
|
|
74
|
+
|
|
75
|
+
klass = Class.new(self)
|
|
76
|
+
klass.description(description) if description
|
|
77
|
+
klass.tool_name = api_name
|
|
78
|
+
klass.tool_extra_props = extras
|
|
79
|
+
klass.mcp_input_schema = Mcp.send(:normalize_schema, raw_schema)
|
|
80
|
+
klass.mcp_client = mcp_client
|
|
81
|
+
klass.instance_variable_set(:@model, input_class)
|
|
82
|
+
klass.new
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Return a deep copy of the raw schema; `SupportedSchemas.transform_schema!`
|
|
86
|
+
# mutates the hash it receives.
|
|
87
|
+
def to_json_schema_inner(state:)
|
|
88
|
+
_ = state
|
|
89
|
+
Marshal.load(Marshal.dump(self.class.mcp_input_schema))
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def to_json_schema
|
|
93
|
+
Anthropic::Helpers::InputSchema::JsonSchemaConverter.to_json_schema(self)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# MCP arguments are opaque JSON objects — pass through unchanged. The
|
|
97
|
+
# returned object is an instance of this subclass's per-tool `model`,
|
|
98
|
+
# which is both a Hash (for downstream serialization) and uniquely
|
|
99
|
+
# typed so the runner can identify which MCP tool the parsed input
|
|
100
|
+
# belongs to.
|
|
101
|
+
def coerce(value, state:)
|
|
102
|
+
state.fetch(:exactness)[:yes] += 1
|
|
103
|
+
wrapper = self.class.model.new
|
|
104
|
+
input = if value.is_a?(Hash)
|
|
105
|
+
value
|
|
106
|
+
else
|
|
107
|
+
(value.respond_to?(:to_h) ? value.to_h : {})
|
|
108
|
+
end
|
|
109
|
+
input.each { |k, v| wrapper[k] = v }
|
|
110
|
+
wrapper
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def call(parsed)
|
|
114
|
+
args = parsed.is_a?(Hash) ? parsed.to_h : parsed
|
|
115
|
+
response = self.class.mcp_client.call_tool(name: self.class.tool_name, arguments: args)
|
|
116
|
+
Mcp.send(:convert_tool_result, response)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
class << self
|
|
121
|
+
# Convert an MCP tool definition into a runnable tool for `tool_runner`.
|
|
122
|
+
#
|
|
123
|
+
# @param mcp_tool [MCP::Client::Tool, Hash] An MCP tool, typically from
|
|
124
|
+
# `mcp_client.list_tools` / `mcp_client.tools`. May be a typed
|
|
125
|
+
# `MCP::Client::Tool` or a hash with `:name`, `:description`,
|
|
126
|
+
# `:input_schema` (or `:inputSchema`).
|
|
127
|
+
# @param mcp_client [#call_tool] The MCP client used to invoke the tool.
|
|
128
|
+
# @param cache_control [Hash, nil] Prompt-caching control passed through
|
|
129
|
+
# to the tool definition.
|
|
130
|
+
# @param defer_loading [Boolean, nil] If true, the tool is excluded
|
|
131
|
+
# from the initial system prompt.
|
|
132
|
+
# @param allowed_callers [Array<Symbol, String>, nil] Restricts which
|
|
133
|
+
# callers may invoke the tool.
|
|
134
|
+
# @param eager_input_streaming [Boolean, nil] Enables eager input
|
|
135
|
+
# streaming for the tool.
|
|
136
|
+
# @param input_examples [Array<Hash>, nil] Example inputs for the tool.
|
|
137
|
+
# @param strict [Boolean, nil] When true, guarantees schema validation
|
|
138
|
+
# on tool names and inputs.
|
|
139
|
+
# @return [Anthropic::Helpers::Tools::Mcp::Tool]
|
|
140
|
+
def tool(
|
|
141
|
+
mcp_tool,
|
|
142
|
+
mcp_client,
|
|
143
|
+
cache_control: nil,
|
|
144
|
+
defer_loading: nil,
|
|
145
|
+
allowed_callers: nil,
|
|
146
|
+
eager_input_streaming: nil,
|
|
147
|
+
input_examples: nil,
|
|
148
|
+
strict: nil
|
|
149
|
+
)
|
|
150
|
+
require_mcp!
|
|
151
|
+
Tool.build(
|
|
152
|
+
mcp_tool: mcp_tool,
|
|
153
|
+
mcp_client: mcp_client,
|
|
154
|
+
cache_control: cache_control,
|
|
155
|
+
defer_loading: defer_loading,
|
|
156
|
+
allowed_callers: allowed_callers,
|
|
157
|
+
eager_input_streaming: eager_input_streaming,
|
|
158
|
+
input_examples: input_examples,
|
|
159
|
+
strict: strict
|
|
160
|
+
)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Convert a list of MCP tools into runnable tools.
|
|
164
|
+
#
|
|
165
|
+
# @param mcp_tools [Array<MCP::Client::Tool, Hash>]
|
|
166
|
+
# @param mcp_client [#call_tool]
|
|
167
|
+
# @return [Array<Anthropic::Helpers::Tools::Mcp::Tool>]
|
|
168
|
+
def tools(mcp_tools, mcp_client, **opts)
|
|
169
|
+
mcp_tools.map { tool(_1, mcp_client, **opts) }
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Convert an MCP prompt message into a Beta message parameter.
|
|
173
|
+
#
|
|
174
|
+
# @param mcp_message [MCP::Prompt::Message, Hash]
|
|
175
|
+
# @param cache_control [Hash, nil] Forwarded to the produced content block.
|
|
176
|
+
# @return [Hash]
|
|
177
|
+
def message(mcp_message, cache_control: nil)
|
|
178
|
+
require_mcp!
|
|
179
|
+
h = to_hash!(mcp_message, "MCP prompt message")
|
|
180
|
+
role = hkey(h, :role)
|
|
181
|
+
role = role.to_sym if role.respond_to?(:to_sym)
|
|
182
|
+
{
|
|
183
|
+
role: role,
|
|
184
|
+
content: [content(hkey(h, :content), cache_control: cache_control)]
|
|
185
|
+
}
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Convert a single MCP content item into a Beta content block.
|
|
189
|
+
#
|
|
190
|
+
# Handles text, image, and embedded resource content types. Raises
|
|
191
|
+
# {UnsupportedMCPValueError} for audio or resource_link types.
|
|
192
|
+
#
|
|
193
|
+
# @param mcp_content [MCP::Content::Text, MCP::Content::Image,
|
|
194
|
+
# MCP::Content::EmbeddedResource, Hash]
|
|
195
|
+
# @param cache_control [Hash, nil]
|
|
196
|
+
# @return [Hash]
|
|
197
|
+
def content(mcp_content, cache_control: nil)
|
|
198
|
+
require_mcp!
|
|
199
|
+
h = to_hash!(mcp_content, "MCP content")
|
|
200
|
+
block = convert_content(h)
|
|
201
|
+
block[:cache_control] = cache_control if cache_control
|
|
202
|
+
block
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Convert MCP resource read results into Beta content blocks — one
|
|
206
|
+
# per item in `contents`. Raises {UnsupportedMCPValueError} on any
|
|
207
|
+
# item whose MIME type is unsupported.
|
|
208
|
+
#
|
|
209
|
+
# @param result [Hash, Array, #contents] The result from
|
|
210
|
+
# `mcp_client.read_resource(uri: ...)`. The `mcp` gem returns just
|
|
211
|
+
# the contents array — both that and a `{contents: [...]}` hash are
|
|
212
|
+
# accepted.
|
|
213
|
+
# @param cache_control [Hash, nil] Forwarded to each produced block.
|
|
214
|
+
# @return [Array<Hash>]
|
|
215
|
+
def resource_to_contents(result, cache_control: nil)
|
|
216
|
+
require_mcp!
|
|
217
|
+
contents = extract_resource_contents(result)
|
|
218
|
+
if contents.empty?
|
|
219
|
+
raise UnsupportedMCPValueError,
|
|
220
|
+
"Resource contents array must contain at least one item"
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
contents.map do |item|
|
|
224
|
+
block = resource_contents_to_block(to_hash!(item, "resource"))
|
|
225
|
+
block[:cache_control] = cache_control if cache_control
|
|
226
|
+
block
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Convert MCP resource read results into {Anthropic::FilePart}
|
|
231
|
+
# instances suitable for `client.beta.files.upload(file: ...)`. No
|
|
232
|
+
# MIME filtering — every item in `contents` becomes a file.
|
|
233
|
+
#
|
|
234
|
+
# @param result [Hash, Array, #contents]
|
|
235
|
+
# @return [Array<Anthropic::FilePart>]
|
|
236
|
+
def resource_to_files(result)
|
|
237
|
+
require_mcp!
|
|
238
|
+
contents = extract_resource_contents(result)
|
|
239
|
+
if contents.empty?
|
|
240
|
+
raise UnsupportedMCPValueError,
|
|
241
|
+
"Resource contents array must contain at least one item"
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
contents.map do |item|
|
|
245
|
+
resource = to_hash!(item, "resource")
|
|
246
|
+
Anthropic::FilePart.new(
|
|
247
|
+
StringIO.new(resource_bytes(resource)),
|
|
248
|
+
filename: filename_from_uri(hkey(resource, :uri)),
|
|
249
|
+
content_type: hkey(resource, :mimeType)
|
|
250
|
+
)
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# @api private
|
|
255
|
+
# Called by {Tool#call}; converts a JSON-RPC `tools/call` response
|
|
256
|
+
# into the value the tool runner expects.
|
|
257
|
+
def convert_tool_result(response)
|
|
258
|
+
result = nested_result(response)
|
|
259
|
+
is_error = hkey(result, :isError)
|
|
260
|
+
content_items = hkey(result, :content) || []
|
|
261
|
+
structured = hkey(result, :structuredContent)
|
|
262
|
+
|
|
263
|
+
if is_error
|
|
264
|
+
blocks = content_items.map { content(_1) }
|
|
265
|
+
raise Anthropic::Errors::Error, render_error_blocks(blocks)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
return JSON.generate(structured) if content_items.empty? && structured
|
|
269
|
+
|
|
270
|
+
content_items.map { content(_1) }
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# -- conversion internals -------------------------------------------
|
|
274
|
+
|
|
275
|
+
private def convert_content(h)
|
|
276
|
+
type = hkey(h, :type).to_s
|
|
277
|
+
case type
|
|
278
|
+
when "text"
|
|
279
|
+
text = hkey(h, :text)
|
|
280
|
+
{type: :text, text: text.to_s}
|
|
281
|
+
when "image"
|
|
282
|
+
data = hkey(h, :data)
|
|
283
|
+
mime = hkey(h, :mimeType)
|
|
284
|
+
unless supported_image_mime?(mime)
|
|
285
|
+
raise UnsupportedMCPValueError, "Unsupported image MIME type: #{mime}"
|
|
286
|
+
end
|
|
287
|
+
{type: :image, source: {type: :base64, data: data, media_type: mime}}
|
|
288
|
+
when "resource"
|
|
289
|
+
resource = hkey(h, :resource)
|
|
290
|
+
resource_contents_to_block(to_hash!(resource, "embedded resource"))
|
|
291
|
+
else
|
|
292
|
+
# Covers "audio", "resource_link", and any unrecognized type.
|
|
293
|
+
raise UnsupportedMCPValueError, "Unsupported MCP content type: #{type}"
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
private def resource_contents_to_block(resource)
|
|
298
|
+
mime = hkey(resource, :mimeType)
|
|
299
|
+
uri = hkey(resource, :uri)
|
|
300
|
+
text = hkey(resource, :text)
|
|
301
|
+
# `mcp/sdk` schema uses `blob`; the Ruby `mcp` gem's BlobContents#to_h also uses `blob`.
|
|
302
|
+
blob = hkey(resource, :blob)
|
|
303
|
+
|
|
304
|
+
if mime && supported_image_mime?(mime)
|
|
305
|
+
if blob.nil?
|
|
306
|
+
raise UnsupportedMCPValueError,
|
|
307
|
+
"Image resource must have blob data, not text. URI: #{uri}"
|
|
308
|
+
end
|
|
309
|
+
return {type: :image, source: {type: :base64, data: blob, media_type: mime}}
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
if mime == "application/pdf"
|
|
313
|
+
if blob.nil?
|
|
314
|
+
raise UnsupportedMCPValueError,
|
|
315
|
+
"PDF resource must have blob data, not text. URI: #{uri}"
|
|
316
|
+
end
|
|
317
|
+
return {type: :document, source: {type: :base64, data: blob, media_type: "application/pdf"}}
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
if mime.nil? || mime.start_with?("text/")
|
|
321
|
+
data = if !text.nil?
|
|
322
|
+
text.to_s
|
|
323
|
+
elsif !blob.nil?
|
|
324
|
+
Base64.decode64(blob.to_s).force_encoding(Encoding::UTF_8)
|
|
325
|
+
else
|
|
326
|
+
""
|
|
327
|
+
end
|
|
328
|
+
return {type: :document, source: {type: :text, data: data, media_type: "text/plain"}}
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
raise UnsupportedMCPValueError, "Unsupported MIME type \"#{mime}\" for resource: #{uri}"
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
private def extract_resource_contents(result)
|
|
335
|
+
case result
|
|
336
|
+
when Array
|
|
337
|
+
result
|
|
338
|
+
when Hash
|
|
339
|
+
result[:contents] || result["contents"] || []
|
|
340
|
+
else
|
|
341
|
+
if result.respond_to?(:contents)
|
|
342
|
+
result.contents
|
|
343
|
+
elsif result.respond_to?(:to_h)
|
|
344
|
+
h = result.to_h
|
|
345
|
+
h[:contents] || h["contents"] || []
|
|
346
|
+
else
|
|
347
|
+
[]
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
private def resource_bytes(resource)
|
|
353
|
+
text = hkey(resource, :text)
|
|
354
|
+
blob = hkey(resource, :blob)
|
|
355
|
+
|
|
356
|
+
return Base64.decode64(blob.to_s) unless blob.nil?
|
|
357
|
+
return text.to_s.dup.force_encoding(Encoding::UTF_8) unless text.nil?
|
|
358
|
+
|
|
359
|
+
"".dup
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
private def filename_from_uri(uri)
|
|
363
|
+
return "file" if uri.nil? || uri.to_s.empty?
|
|
364
|
+
|
|
365
|
+
path =
|
|
366
|
+
begin
|
|
367
|
+
URI.parse(uri.to_s).path
|
|
368
|
+
rescue URI::InvalidURIError
|
|
369
|
+
uri.to_s
|
|
370
|
+
end
|
|
371
|
+
base = path.to_s.split("/").last
|
|
372
|
+
base.nil? || base.empty? ? "file" : base
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
private def render_error_blocks(blocks)
|
|
376
|
+
parts = blocks.map do |b|
|
|
377
|
+
if b[:type] == :text
|
|
378
|
+
b[:text].to_s
|
|
379
|
+
else
|
|
380
|
+
JSON.generate(b)
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
joined = parts.reject(&:empty?).join("\n")
|
|
384
|
+
joined.empty? ? "MCP tool reported an error" : joined
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
private def nested_result(response)
|
|
388
|
+
return {} if response.nil?
|
|
389
|
+
|
|
390
|
+
r = response
|
|
391
|
+
r = r["result"] || r[:result] || r if r.is_a?(Hash) && (r.key?("result") || r.key?(:result))
|
|
392
|
+
r.is_a?(Hash) ? r : {}
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
# -- input shape helpers --------------------------------------------
|
|
396
|
+
|
|
397
|
+
private def extract_tool_fields(mcp_tool)
|
|
398
|
+
if mcp_tool.is_a?(Hash)
|
|
399
|
+
[
|
|
400
|
+
hkey(mcp_tool, :name),
|
|
401
|
+
hkey(mcp_tool, :description),
|
|
402
|
+
hkey(mcp_tool, :input_schema) || hkey(mcp_tool, :inputSchema)
|
|
403
|
+
]
|
|
404
|
+
else
|
|
405
|
+
name = mcp_tool.respond_to?(:name) ? mcp_tool.name : nil
|
|
406
|
+
description = mcp_tool.respond_to?(:description) ? mcp_tool.description : nil
|
|
407
|
+
schema =
|
|
408
|
+
if mcp_tool.respond_to?(:input_schema)
|
|
409
|
+
mcp_tool.input_schema
|
|
410
|
+
elsif mcp_tool.respond_to?(:inputSchema)
|
|
411
|
+
mcp_tool.inputSchema
|
|
412
|
+
end
|
|
413
|
+
[name, description, schema]
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
# `mcp/sdk` requires `properties` and `required` to be present (even
|
|
418
|
+
# if null) on the API side. Normalize the gem's string-keyed
|
|
419
|
+
# JSON-Schema hashes into the symbol-keyed shape the SDK serializes.
|
|
420
|
+
private def normalize_schema(raw)
|
|
421
|
+
schema = raw.nil? ? {} : deep_symbolize_keys(raw)
|
|
422
|
+
schema[:type] ||= :object
|
|
423
|
+
schema[:properties] = nil unless schema.key?(:properties)
|
|
424
|
+
schema[:required] = nil unless schema.key?(:required)
|
|
425
|
+
schema
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
private def deep_symbolize_keys(obj)
|
|
429
|
+
case obj
|
|
430
|
+
in Hash
|
|
431
|
+
obj.each_with_object({}) do |(k, v), acc|
|
|
432
|
+
key = k.is_a?(String) ? k.to_sym : k
|
|
433
|
+
acc[key] = deep_symbolize_keys(v)
|
|
434
|
+
end
|
|
435
|
+
in Array
|
|
436
|
+
obj.map { deep_symbolize_keys(_1) }
|
|
437
|
+
else
|
|
438
|
+
obj
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
# Reads a value from a hash that may use either symbol or string keys.
|
|
443
|
+
# The Ruby `mcp` gem returns string-keyed hashes from transport layers
|
|
444
|
+
# but symbol-keyed hashes from typed objects' `to_h`.
|
|
445
|
+
private def hkey(h, key)
|
|
446
|
+
return nil unless h.is_a?(Hash)
|
|
447
|
+
|
|
448
|
+
sym = key.to_sym
|
|
449
|
+
str = key.to_s
|
|
450
|
+
return h[sym] if h.key?(sym)
|
|
451
|
+
return h[str] if h.key?(str)
|
|
452
|
+
|
|
453
|
+
nil
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
private def to_hash!(obj, label)
|
|
457
|
+
return obj if obj.is_a?(Hash)
|
|
458
|
+
return obj.to_h if obj.respond_to?(:to_h)
|
|
459
|
+
|
|
460
|
+
raise UnsupportedMCPValueError,
|
|
461
|
+
"Expected #{label} to be a Hash or to-hashable object, got #{obj.class}"
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
private def supported_image_mime?(mime)
|
|
465
|
+
return false unless mime.is_a?(String)
|
|
466
|
+
|
|
467
|
+
SUPPORTED_IMAGE_TYPES.include?(mime)
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
private def require_mcp!
|
|
471
|
+
return if defined?(::MCP)
|
|
472
|
+
|
|
473
|
+
begin
|
|
474
|
+
require("mcp")
|
|
475
|
+
rescue LoadError
|
|
476
|
+
raise LoadError,
|
|
477
|
+
"The `mcp` gem is required to use Anthropic's MCP helpers. " \
|
|
478
|
+
"Install it by adding `gem \"mcp\"` to your Gemfile (or running `gem install mcp`)."
|
|
479
|
+
end
|
|
480
|
+
end
|
|
481
|
+
end
|
|
482
|
+
end
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
end
|
|
@@ -310,6 +310,8 @@ module Anthropic
|
|
|
310
310
|
Anthropic::Internal::Util.deep_merge(*[req[:body], opts[:extra_body]].compact)
|
|
311
311
|
end
|
|
312
312
|
|
|
313
|
+
headers.delete("content-type") if body.nil?
|
|
314
|
+
|
|
313
315
|
url = Anthropic::Internal::Util.join_parsed_uri(
|
|
314
316
|
@base_url_components,
|
|
315
317
|
{**req, path: path, query: query}
|
|
@@ -55,6 +55,8 @@ module Anthropic
|
|
|
55
55
|
|
|
56
56
|
variant const: -> { Anthropic::Models::AnthropicBeta::MANAGED_AGENTS_2026_04_01 }
|
|
57
57
|
|
|
58
|
+
variant const: -> { Anthropic::Models::AnthropicBeta::CACHE_DIAGNOSIS_2026_04_07 }
|
|
59
|
+
|
|
58
60
|
# @!method self.variants
|
|
59
61
|
# @return [Array(String, Symbol)]
|
|
60
62
|
|
|
@@ -88,6 +90,7 @@ module Anthropic
|
|
|
88
90
|
USER_PROFILES_2026_03_24 = :"user-profiles-2026-03-24"
|
|
89
91
|
ADVISOR_TOOL_2026_03_01 = :"advisor-tool-2026-03-01"
|
|
90
92
|
MANAGED_AGENTS_2026_04_01 = :"managed-agents-2026-04-01"
|
|
93
|
+
CACHE_DIAGNOSIS_2026_04_07 = :"cache-diagnosis-2026-04-07"
|
|
91
94
|
|
|
92
95
|
# @!endgroup
|
|
93
96
|
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Anthropic
|
|
4
|
+
module Models
|
|
5
|
+
module Beta
|
|
6
|
+
class BetaCacheMissMessagesChanged < Anthropic::Internal::Type::BaseModel
|
|
7
|
+
# @!attribute cache_missed_input_tokens
|
|
8
|
+
# Approximate number of input tokens that would have been read from cache had the
|
|
9
|
+
# prefix matched the previous request.
|
|
10
|
+
#
|
|
11
|
+
# @return [Integer]
|
|
12
|
+
required :cache_missed_input_tokens, Integer
|
|
13
|
+
|
|
14
|
+
# @!attribute type
|
|
15
|
+
#
|
|
16
|
+
# @return [Symbol, :messages_changed]
|
|
17
|
+
required :type, const: :messages_changed
|
|
18
|
+
|
|
19
|
+
# @!method initialize(cache_missed_input_tokens:, type: :messages_changed)
|
|
20
|
+
# Some parameter documentations has been truncated, see
|
|
21
|
+
# {Anthropic::Models::Beta::BetaCacheMissMessagesChanged} for more details.
|
|
22
|
+
#
|
|
23
|
+
# @param cache_missed_input_tokens [Integer] Approximate number of input tokens that would have been read from cache had the
|
|
24
|
+
#
|
|
25
|
+
# @param type [Symbol, :messages_changed]
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
BetaCacheMissMessagesChanged = Beta::BetaCacheMissMessagesChanged
|
|
30
|
+
end
|
|
31
|
+
end
|