fast-agent-mcp 0.1.10__py3-none-any.whl → 0.1.12__py3-none-any.whl
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.
Potentially problematic release.
This version of fast-agent-mcp might be problematic. Click here for more details.
- {fast_agent_mcp-0.1.10.dist-info → fast_agent_mcp-0.1.12.dist-info}/METADATA +36 -38
- {fast_agent_mcp-0.1.10.dist-info → fast_agent_mcp-0.1.12.dist-info}/RECORD +45 -42
- mcp_agent/agents/agent.py +1 -24
- mcp_agent/app.py +0 -5
- mcp_agent/config.py +9 -0
- mcp_agent/context.py +0 -2
- mcp_agent/core/agent_app.py +29 -0
- mcp_agent/core/agent_types.py +29 -2
- mcp_agent/core/decorators.py +1 -2
- mcp_agent/core/error_handling.py +1 -1
- mcp_agent/core/factory.py +2 -3
- mcp_agent/core/mcp_content.py +2 -3
- mcp_agent/core/proxies.py +3 -0
- mcp_agent/core/request_params.py +43 -0
- mcp_agent/core/types.py +4 -2
- mcp_agent/core/validation.py +14 -15
- mcp_agent/logging/transport.py +2 -2
- mcp_agent/mcp/gen_client.py +4 -4
- mcp_agent/mcp/interfaces.py +186 -0
- mcp_agent/mcp/mcp_agent_client_session.py +10 -2
- mcp_agent/mcp/mcp_aggregator.py +12 -3
- mcp_agent/mcp/sampling.py +140 -0
- mcp_agent/mcp/stdio.py +1 -2
- mcp_agent/mcp_server/__init__.py +1 -1
- mcp_agent/resources/examples/internal/agent.py +1 -1
- mcp_agent/resources/examples/internal/fastagent.config.yaml +3 -0
- mcp_agent/resources/examples/prompting/__init__.py +1 -1
- mcp_agent/ui/console_display.py +2 -2
- mcp_agent/workflows/evaluator_optimizer/evaluator_optimizer.py +2 -2
- mcp_agent/workflows/llm/augmented_llm.py +42 -102
- mcp_agent/workflows/llm/augmented_llm_anthropic.py +4 -3
- mcp_agent/workflows/llm/augmented_llm_openai.py +4 -3
- mcp_agent/workflows/llm/augmented_llm_passthrough.py +119 -37
- mcp_agent/workflows/llm/model_factory.py +1 -1
- mcp_agent/workflows/llm/prompt_utils.py +42 -28
- mcp_agent/workflows/llm/providers/multipart_converter_anthropic.py +244 -140
- mcp_agent/workflows/llm/providers/multipart_converter_openai.py +230 -185
- mcp_agent/workflows/llm/providers/sampling_converter_anthropic.py +5 -204
- mcp_agent/workflows/llm/providers/sampling_converter_openai.py +9 -207
- mcp_agent/workflows/llm/sampling_converter.py +124 -0
- mcp_agent/workflows/llm/sampling_format_converter.py +0 -17
- mcp_agent/workflows/router/router_base.py +10 -10
- mcp_agent/workflows/llm/llm_selector.py +0 -345
- {fast_agent_mcp-0.1.10.dist-info → fast_agent_mcp-0.1.12.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.1.10.dist-info → fast_agent_mcp-0.1.12.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.1.10.dist-info → fast_agent_mcp-0.1.12.dist-info}/licenses/LICENSE +0 -0
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import List, Union, Sequence
|
1
|
+
from typing import List, Union, Sequence, Optional
|
2
2
|
|
3
3
|
from mcp.types import (
|
4
4
|
TextContent,
|
@@ -7,6 +7,7 @@ from mcp.types import (
|
|
7
7
|
CallToolResult,
|
8
8
|
TextResourceContents,
|
9
9
|
BlobResourceContents,
|
10
|
+
PromptMessage,
|
10
11
|
)
|
11
12
|
from pydantic import AnyUrl
|
12
13
|
from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
|
@@ -33,6 +34,7 @@ from mcp_agent.logging.logger import get_logger
|
|
33
34
|
from mcp_agent.mcp.resource_utils import extract_title_from_uri
|
34
35
|
|
35
36
|
_logger = get_logger("multipart_converter_anthropic")
|
37
|
+
|
36
38
|
# List of image MIME types supported by Anthropic API
|
37
39
|
SUPPORTED_IMAGE_MIME_TYPES = {"image/jpeg", "image/png", "image/gif", "image/webp"}
|
38
40
|
|
@@ -41,59 +43,16 @@ class AnthropicConverter:
|
|
41
43
|
"""Converts MCP message types to Anthropic API format."""
|
42
44
|
|
43
45
|
@staticmethod
|
44
|
-
def
|
45
|
-
|
46
|
-
documentMode: bool = True,
|
47
|
-
) -> List[ContentBlockParam]:
|
48
|
-
"""
|
49
|
-
Helper method to convert a list of content items to Anthropic format.
|
46
|
+
def _is_supported_image_type(mime_type: str) -> bool:
|
47
|
+
"""Check if the given MIME type is supported by Anthropic's image API.
|
50
48
|
|
51
49
|
Args:
|
52
|
-
|
53
|
-
documentMode: Whether to convert text resources to document blocks (True) or text blocks (False)
|
50
|
+
mime_type: The MIME type to check
|
54
51
|
|
55
52
|
Returns:
|
56
|
-
|
53
|
+
True if the MIME type is supported, False otherwise
|
57
54
|
"""
|
58
|
-
|
59
|
-
anthropic_blocks: List[ContentBlockParam] = []
|
60
|
-
|
61
|
-
for content_item in content_items:
|
62
|
-
if isinstance(content_item, TextContent):
|
63
|
-
anthropic_block = AnthropicConverter._convert_text_content(content_item)
|
64
|
-
anthropic_blocks.append(anthropic_block)
|
65
|
-
elif isinstance(content_item, ImageContent):
|
66
|
-
# Check if image MIME type is supported
|
67
|
-
if content_item.mimeType not in SUPPORTED_IMAGE_MIME_TYPES:
|
68
|
-
anthropic_block = AnthropicConverter._format_fail_message(
|
69
|
-
content_item, content_item.mimeType
|
70
|
-
)
|
71
|
-
else:
|
72
|
-
anthropic_block = AnthropicConverter._convert_image_content(
|
73
|
-
content_item
|
74
|
-
)
|
75
|
-
anthropic_blocks.append(anthropic_block)
|
76
|
-
elif isinstance(content_item, EmbeddedResource):
|
77
|
-
anthropic_block = AnthropicConverter._convert_embedded_resource(
|
78
|
-
content_item, documentMode
|
79
|
-
)
|
80
|
-
anthropic_blocks.append(anthropic_block)
|
81
|
-
|
82
|
-
return anthropic_blocks
|
83
|
-
|
84
|
-
@staticmethod
|
85
|
-
def _format_fail_message(
|
86
|
-
resource: Union[TextContent, ImageContent, EmbeddedResource], mimetype: str
|
87
|
-
) -> TextBlockParam:
|
88
|
-
"""Create a fallback text block for unsupported resource types"""
|
89
|
-
fallback_text: str = f"Unknown resource with format {mimetype}"
|
90
|
-
if resource.type == "image":
|
91
|
-
fallback_text = f"Image with unsupported format '{mimetype}' ({len(resource.data)} characters)"
|
92
|
-
if isinstance(resource, EmbeddedResource):
|
93
|
-
if isinstance(resource.resource, BlobResourceContents):
|
94
|
-
fallback_text = f"Embedded Resource {resource.resource.uri._url} with unsupported format {resource.resource.mimeType} ({len(resource.resource.blob)} characters)"
|
95
|
-
|
96
|
-
return TextBlockParam(type="text", text=fallback_text)
|
55
|
+
return mime_type in SUPPORTED_IMAGE_MIME_TYPES
|
97
56
|
|
98
57
|
@staticmethod
|
99
58
|
def convert_to_anthropic(multipart_msg: PromptMessageMultipart) -> MessageParam:
|
@@ -106,12 +65,15 @@ class AnthropicConverter:
|
|
106
65
|
Returns:
|
107
66
|
An Anthropic API MessageParam object
|
108
67
|
"""
|
109
|
-
|
110
|
-
|
68
|
+
role = multipart_msg.role
|
69
|
+
|
70
|
+
# Handle empty content case - create an empty list instead of a text block
|
71
|
+
if not multipart_msg.content:
|
72
|
+
return MessageParam(role=role, content=[])
|
111
73
|
|
112
74
|
# Convert content blocks
|
113
|
-
anthropic_blocks
|
114
|
-
|
75
|
+
anthropic_blocks = AnthropicConverter._convert_content_items(
|
76
|
+
multipart_msg.content, document_mode=True
|
115
77
|
)
|
116
78
|
|
117
79
|
# Filter blocks based on role (assistant can only have text blocks)
|
@@ -130,73 +92,111 @@ class AnthropicConverter:
|
|
130
92
|
return MessageParam(role=role, content=anthropic_blocks)
|
131
93
|
|
132
94
|
@staticmethod
|
133
|
-
def
|
134
|
-
"""
|
135
|
-
|
95
|
+
def convert_prompt_message_to_anthropic(message: PromptMessage) -> MessageParam:
|
96
|
+
"""
|
97
|
+
Convert a standard PromptMessage to Anthropic API format.
|
136
98
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
99
|
+
Args:
|
100
|
+
message: The PromptMessage to convert
|
101
|
+
|
102
|
+
Returns:
|
103
|
+
An Anthropic API MessageParam object
|
104
|
+
"""
|
105
|
+
# Convert the PromptMessage to a PromptMessageMultipart containing a single content item
|
106
|
+
multipart = PromptMessageMultipart(role=message.role, content=[message.content])
|
107
|
+
|
108
|
+
# Use the existing conversion method
|
109
|
+
return AnthropicConverter.convert_to_anthropic(multipart)
|
147
110
|
|
148
111
|
@staticmethod
|
149
|
-
def
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
112
|
+
def _convert_content_items(
|
113
|
+
content_items: Sequence[Union[TextContent, ImageContent, EmbeddedResource]],
|
114
|
+
document_mode: bool = True,
|
115
|
+
) -> List[ContentBlockParam]:
|
116
|
+
"""
|
117
|
+
Convert a list of content items to Anthropic content blocks.
|
154
118
|
|
155
|
-
|
156
|
-
|
119
|
+
Args:
|
120
|
+
content_items: Sequence of MCP content items
|
121
|
+
document_mode: Whether to convert text resources to document blocks (True) or text blocks (False)
|
157
122
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
123
|
+
Returns:
|
124
|
+
List of Anthropic content blocks
|
125
|
+
"""
|
126
|
+
anthropic_blocks: List[ContentBlockParam] = []
|
127
|
+
|
128
|
+
for content_item in content_items:
|
129
|
+
if isinstance(content_item, TextContent):
|
130
|
+
anthropic_blocks.append(
|
131
|
+
TextBlockParam(type="text", text=content_item.text)
|
132
|
+
)
|
133
|
+
|
134
|
+
elif isinstance(content_item, ImageContent):
|
135
|
+
# Check if image MIME type is supported
|
136
|
+
if not AnthropicConverter._is_supported_image_type(
|
137
|
+
content_item.mimeType
|
138
|
+
):
|
139
|
+
anthropic_blocks.append(
|
140
|
+
TextBlockParam(
|
141
|
+
type="text",
|
142
|
+
text=f"Image with unsupported format '{content_item.mimeType}' ({len(content_item.data)} bytes)",
|
143
|
+
)
|
144
|
+
)
|
145
|
+
else:
|
146
|
+
anthropic_blocks.append(
|
147
|
+
ImageBlockParam(
|
148
|
+
type="image",
|
149
|
+
source=Base64ImageSourceParam(
|
150
|
+
type="base64",
|
151
|
+
media_type=content_item.mimeType,
|
152
|
+
data=content_item.data,
|
153
|
+
),
|
154
|
+
)
|
155
|
+
)
|
156
|
+
|
157
|
+
elif isinstance(content_item, EmbeddedResource):
|
158
|
+
block = AnthropicConverter._convert_embedded_resource(
|
159
|
+
content_item, document_mode
|
160
|
+
)
|
161
|
+
anthropic_blocks.append(block)
|
162
|
+
|
163
|
+
return anthropic_blocks
|
162
164
|
|
163
165
|
@staticmethod
|
164
166
|
def _convert_embedded_resource(
|
165
167
|
resource: EmbeddedResource,
|
166
|
-
|
168
|
+
document_mode: bool = True,
|
167
169
|
) -> ContentBlockParam:
|
168
|
-
"""
|
170
|
+
"""
|
171
|
+
Convert EmbeddedResource to appropriate Anthropic block type.
|
169
172
|
|
170
173
|
Args:
|
171
174
|
resource: The embedded resource to convert
|
172
|
-
|
175
|
+
document_mode: Whether to convert text resources to Document blocks (True) or Text blocks (False)
|
173
176
|
|
174
177
|
Returns:
|
175
178
|
An appropriate ContentBlockParam for the resource
|
176
179
|
"""
|
177
|
-
resource_content
|
178
|
-
|
179
|
-
)
|
180
|
-
|
181
|
-
|
180
|
+
resource_content = resource.resource
|
181
|
+
uri: Optional[AnyUrl] = getattr(resource_content, "uri", None)
|
182
|
+
is_url: bool = uri and uri.scheme in ("http", "https")
|
183
|
+
|
184
|
+
# Determine MIME type
|
182
185
|
mime_type = AnthropicConverter._determine_mime_type(resource_content)
|
186
|
+
|
183
187
|
# Extract title from URI
|
184
|
-
title = extract_title_from_uri(uri) if uri else
|
188
|
+
title = extract_title_from_uri(uri) if uri else "resource"
|
185
189
|
|
186
|
-
#
|
190
|
+
# Convert based on MIME type
|
187
191
|
if mime_type == "image/svg+xml":
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
if mime_type not in SUPPORTED_IMAGE_MIME_TYPES:
|
197
|
-
return AnthropicConverter._format_fail_message(resource, mime_type)
|
198
|
-
|
199
|
-
# Handle supported image types
|
192
|
+
return AnthropicConverter._convert_svg_resource(resource_content)
|
193
|
+
|
194
|
+
elif is_image_mime_type(mime_type):
|
195
|
+
if not AnthropicConverter._is_supported_image_type(mime_type):
|
196
|
+
return AnthropicConverter._create_fallback_text(
|
197
|
+
f"Image with unsupported format '{mime_type}'", resource
|
198
|
+
)
|
199
|
+
|
200
200
|
if is_url:
|
201
201
|
return ImageBlockParam(
|
202
202
|
type="image", source=URLImageSourceParam(type="url", url=str(uri))
|
@@ -208,8 +208,10 @@ class AnthropicConverter:
|
|
208
208
|
type="base64", media_type=mime_type, data=resource_content.blob
|
209
209
|
),
|
210
210
|
)
|
211
|
+
return AnthropicConverter._create_fallback_text(
|
212
|
+
"Image missing data", resource
|
213
|
+
)
|
211
214
|
|
212
|
-
# Handle PDF resources
|
213
215
|
elif mime_type == "application/pdf":
|
214
216
|
if is_url:
|
215
217
|
return DocumentBlockParam(
|
@@ -227,29 +229,109 @@ class AnthropicConverter:
|
|
227
229
|
data=resource_content.blob,
|
228
230
|
),
|
229
231
|
)
|
232
|
+
return TextBlockParam(
|
233
|
+
type="text", text=f"[PDF resource missing data: {title}]"
|
234
|
+
)
|
230
235
|
|
231
|
-
# Handle text resources (default for all other text mime types)
|
232
236
|
elif is_text_mime_type(mime_type):
|
233
|
-
if
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
237
|
+
if not hasattr(resource_content, "text"):
|
238
|
+
return TextBlockParam(
|
239
|
+
type="text",
|
240
|
+
text=f"[Text content could not be extracted from {title}]",
|
241
|
+
)
|
242
|
+
|
243
|
+
# Create document block when in document mode
|
244
|
+
if document_mode:
|
245
|
+
return DocumentBlockParam(
|
246
|
+
type="document",
|
247
|
+
title=title,
|
248
|
+
source=PlainTextSourceParam(
|
249
|
+
type="text",
|
250
|
+
media_type="text/plain",
|
251
|
+
data=resource_content.text,
|
252
|
+
),
|
253
|
+
)
|
254
|
+
|
255
|
+
# Return as simple text block when not in document mode
|
256
|
+
return TextBlockParam(type="text", text=resource_content.text)
|
247
257
|
|
248
258
|
# Default fallback - convert to text if possible
|
249
259
|
if hasattr(resource_content, "text"):
|
250
260
|
return TextBlockParam(type="text", text=resource_content.text)
|
251
261
|
|
252
|
-
|
262
|
+
# This is for binary resources - match the format expected by the test
|
263
|
+
if isinstance(resource.resource, BlobResourceContents) and hasattr(
|
264
|
+
resource.resource, "blob"
|
265
|
+
):
|
266
|
+
blob_length = len(resource.resource.blob)
|
267
|
+
return TextBlockParam(
|
268
|
+
type="text",
|
269
|
+
text=f"Embedded Resource {uri._url} with unsupported format {mime_type} ({blob_length} characters)",
|
270
|
+
)
|
271
|
+
|
272
|
+
return AnthropicConverter._create_fallback_text(
|
273
|
+
f"Unsupported resource ({mime_type})", resource
|
274
|
+
)
|
275
|
+
|
276
|
+
@staticmethod
|
277
|
+
def _determine_mime_type(
|
278
|
+
resource: Union[TextResourceContents, BlobResourceContents],
|
279
|
+
) -> str:
|
280
|
+
"""
|
281
|
+
Determine the MIME type of a resource.
|
282
|
+
|
283
|
+
Args:
|
284
|
+
resource: The resource to check
|
285
|
+
|
286
|
+
Returns:
|
287
|
+
The MIME type as a string
|
288
|
+
"""
|
289
|
+
if getattr(resource, "mimeType", None):
|
290
|
+
return resource.mimeType
|
291
|
+
|
292
|
+
if getattr(resource, "uri", None):
|
293
|
+
return guess_mime_type(resource.uri.serialize_url)
|
294
|
+
|
295
|
+
if hasattr(resource, "blob"):
|
296
|
+
return "application/octet-stream"
|
297
|
+
|
298
|
+
return "text/plain"
|
299
|
+
|
300
|
+
@staticmethod
|
301
|
+
def _convert_svg_resource(resource_content) -> TextBlockParam:
|
302
|
+
"""
|
303
|
+
Convert SVG resource to text block with XML code formatting.
|
304
|
+
|
305
|
+
Args:
|
306
|
+
resource_content: The resource content containing SVG data
|
307
|
+
|
308
|
+
Returns:
|
309
|
+
A TextBlockParam with formatted SVG content
|
310
|
+
"""
|
311
|
+
if hasattr(resource_content, "text"):
|
312
|
+
svg_content = resource_content.text
|
313
|
+
return TextBlockParam(type="text", text=f"```xml\n{svg_content}\n```")
|
314
|
+
return TextBlockParam(type="text", text="[SVG content could not be extracted]")
|
315
|
+
|
316
|
+
@staticmethod
|
317
|
+
def _create_fallback_text(
|
318
|
+
message: str, resource: Union[TextContent, ImageContent, EmbeddedResource]
|
319
|
+
) -> TextBlockParam:
|
320
|
+
"""
|
321
|
+
Create a fallback text block for unsupported resource types.
|
322
|
+
|
323
|
+
Args:
|
324
|
+
message: The fallback message
|
325
|
+
resource: The resource that couldn't be converted
|
326
|
+
|
327
|
+
Returns:
|
328
|
+
A TextBlockParam with the fallback message
|
329
|
+
"""
|
330
|
+
if isinstance(resource, EmbeddedResource) and hasattr(resource.resource, "uri"):
|
331
|
+
uri = resource.resource.uri
|
332
|
+
return TextBlockParam(type="text", text=f"[{message}: {uri._url}]")
|
333
|
+
|
334
|
+
return TextBlockParam(type="text", text=f"[{message}]")
|
253
335
|
|
254
336
|
@staticmethod
|
255
337
|
def convert_tool_result_to_anthropic(
|
@@ -265,20 +347,20 @@ class AnthropicConverter:
|
|
265
347
|
Returns:
|
266
348
|
An Anthropic ToolResultBlockParam ready to be included in a user message
|
267
349
|
"""
|
268
|
-
# For tool results,
|
350
|
+
# For tool results, always use document_mode=False to get text blocks instead of document blocks
|
269
351
|
anthropic_content = []
|
270
352
|
|
271
353
|
for item in tool_result.content:
|
272
354
|
if isinstance(item, EmbeddedResource):
|
273
355
|
# For embedded resources, always use text mode in tool results
|
274
356
|
resource_block = AnthropicConverter._convert_embedded_resource(
|
275
|
-
item,
|
357
|
+
item, document_mode=False
|
276
358
|
)
|
277
359
|
anthropic_content.append(resource_block)
|
278
|
-
|
279
|
-
# For
|
360
|
+
elif isinstance(item, (TextContent, ImageContent)):
|
361
|
+
# For text and image, use standard conversion
|
280
362
|
blocks = AnthropicConverter._convert_content_items(
|
281
|
-
[item],
|
363
|
+
[item], document_mode=False
|
282
364
|
)
|
283
365
|
anthropic_content.extend(blocks)
|
284
366
|
|
@@ -312,35 +394,57 @@ class AnthropicConverter:
|
|
312
394
|
content_blocks = []
|
313
395
|
|
314
396
|
for tool_use_id, result in tool_results:
|
315
|
-
#
|
316
|
-
|
397
|
+
# Process each tool result
|
398
|
+
tool_result_blocks = []
|
317
399
|
separate_blocks = []
|
318
400
|
|
401
|
+
# Process each content item in the result
|
319
402
|
for item in result.content:
|
320
|
-
# Text and images go in tool results, other resources (PDFs) go as separate blocks
|
321
403
|
if isinstance(item, (TextContent, ImageContent)):
|
322
|
-
|
404
|
+
blocks = AnthropicConverter._convert_content_items(
|
405
|
+
[item], document_mode=False
|
406
|
+
)
|
407
|
+
tool_result_blocks.extend(blocks)
|
323
408
|
elif isinstance(item, EmbeddedResource):
|
324
|
-
|
325
|
-
|
326
|
-
|
409
|
+
resource_content = item.resource
|
410
|
+
|
411
|
+
# Text resources go in tool results, others go as separate blocks
|
412
|
+
if isinstance(resource_content, TextResourceContents):
|
413
|
+
block = AnthropicConverter._convert_embedded_resource(
|
414
|
+
item, document_mode=False
|
415
|
+
)
|
416
|
+
tool_result_blocks.append(block)
|
327
417
|
else:
|
328
|
-
# For binary resources like PDFs,
|
418
|
+
# For binary resources like PDFs, add as separate block
|
329
419
|
block = AnthropicConverter._convert_embedded_resource(
|
330
|
-
item,
|
420
|
+
item, document_mode=True
|
331
421
|
)
|
332
422
|
separate_blocks.append(block)
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
423
|
+
|
424
|
+
# Create the tool result block if we have content
|
425
|
+
if tool_result_blocks:
|
426
|
+
content_blocks.append(
|
427
|
+
ToolResultBlockParam(
|
428
|
+
type="tool_result",
|
429
|
+
tool_use_id=tool_use_id,
|
430
|
+
content=tool_result_blocks,
|
431
|
+
is_error=result.isError,
|
432
|
+
)
|
433
|
+
)
|
434
|
+
else:
|
435
|
+
# If there's no content, still create a placeholder
|
436
|
+
content_blocks.append(
|
437
|
+
ToolResultBlockParam(
|
438
|
+
type="tool_result",
|
439
|
+
tool_use_id=tool_use_id,
|
440
|
+
content=[
|
441
|
+
TextBlockParam(
|
442
|
+
type="text", text="[No content in tool result]"
|
443
|
+
)
|
444
|
+
],
|
445
|
+
is_error=result.isError,
|
446
|
+
)
|
342
447
|
)
|
343
|
-
)
|
344
448
|
|
345
449
|
# Add separate blocks directly to the message
|
346
450
|
content_blocks.extend(separate_blocks)
|