Anthropic Migration Guide
This guide helps you migrate your Anthropic call sites across the 0.135.0 AIProxySwift version boundary, which includes a rework of the Anthropic client.
Select your use case below to see the required changes.
Table of Contents
Message creation
Replace:
AnthropicInputMessage(content: [.text("hello world")], role: .user)
with one of:
// You can choose your level of sugar here. The most concise option is:
AnthropicMessageParam(content: "hello world", role: .user)
// Or, for more flexibility (e.g. adding images, tools, etc.):
AnthropicMessageParam(content: [.textBlock("hello world")], role: .user)
// Or, to fully spell out the call (this helps with Xcode's cmd-click to jump to source):
AnthropicInputMessage(
content: .blocks([
.textBlock(AnthropicTextBlockParam(text: "hello world"))
]),
role: .user
)
Tool definitions
Your existing tool calls should now use .customTool(AnthropicTool(...)). The added level of indirection allows for usage of Anthropic's built-in tools like .bashTool20250124, textEditorTool20250728, and .webSearchTool20250305
Replace:
tools: [
.init(
description: "Call this function when the user wants a stock symbol",
inputSchema: [
"type": "object",
"properties": [
"ticker": [
"type": "string",
"description": "The stock ticker symbol"
]
],
"required": ["ticker"]
],
name: "get_stock_symbol"
)
]
with:
tools: [
.customTool(
AnthropicTool(
description: "Call this function when the user wants a stock symbol",
inputSchema: [
"type": "object",
"properties": [
"ticker": [
"type": "string",
"description": "The stock ticker symbol"
]
],
"required": ["ticker"]
],
name: "get_stock_symbol"
)
)
]
Buffered response handling
Replace:
for content in response.content {
switch content {
case .text(let message):
print("Claude sent a message: \(message)")
case .toolUse(id: _, name: let toolName, input: let toolInput):
print("Claude used a tool \(toolName) with input: \(toolInput)")
}
}
with:
// Option 1: Switch statement with new case names
for content in response.content {
switch content {
case .textBlock(let textBlock):
print("Claude sent a message: \(textBlock.text)")
case .toolUseBlock(let toolUseBlock):
print("Claude used a tool \(toolUseBlock.name) with input: \(toolUseBlock.input)")
default:
continue
}
}
// Option 2: Pattern matching (when you only need text)
for case let .textBlock(textBlock) in response.content {
print("Received text from Claude: \(textBlock.text)")
}
Streaming response handling
For text deltas, replace:
let stream = try await anthropicService.streamingMessageRequest(body: requestBody)
for try await chunk in stream {
switch chunk {
case .text(let text):
print("Received a text delta from Claude: \(text)")
case .toolUse(name: let toolName, input: let toolInput):
print("Claude wants to call tool \(toolName) with input \(toolInput)")
}
}
with:
let stream = try await anthropicService.streamingMessageRequest(
body: requestBody,
secondsToWait: 120
)
for try await case .contentBlockDelta(let contentBlockDelta) in stream {
if case .textDelta(let textDelta) = contentBlockDelta.delta {
print("Received a text delta from Claude: \(textDelta.text)")
}
}
Streaming with tool calls
Replace:
let stream = try await anthropicService.streamingMessageRequest(body: requestBody)
for try await chunk in stream {
switch chunk {
case .text(let text):
print("Received a text delta from Claude: \(text)")
case .toolUse(name: let toolName, input: let toolInput):
print("Claude wants to call tool \(toolName) with input \(toolInput)")
}
}
with:
let stream = try await anthropicService.streamingMessageRequest(
body: requestBody,
secondsToWait: 120
)
var toolCallAccumulator = AnthropicToolCallAccumulator()
for try await event in stream {
// Handle completed tool calls
if let (toolName, toolInput) = try toolCallAccumulator.append(event) {
print("Claude wants to call tool \(toolName) with input \(toolInput)")
}
// Handle text deltas
if case .contentBlockDelta(let contentBlockDelta) = event {
if case .textDelta(let textDelta) = contentBlockDelta.delta {
print("Received a text delta from Anthropic: \(textDelta.text)")
}
}
}
and if you would like to use Anthropic's fine-grained tool call feature:
let stream = try await anthropicService.streamingMessageRequest(
body: requestBody,
secondsToWait: 120,
additionalHeaders: [
"anthropic-beta": "fine-grained-tool-streaming-2025-05-14"
]
)
Including images in a message request
Replace:
AnthropicInputMessage(content: [
.text("Describe this image"),
.image(mediaType: .jpeg, data: jpegData.base64EncodedString())
], role: .user)
with:
let imageBlockParam = AnthropicImageBlockParam(
source: .base64(data: jpegData.base64EncodedString(), mediaType: .jpeg),
cacheControl: nil
)
AnthropicMessageParam(
content: [
.textBlock("Describe this image"),
.imageBlock(imageBlockParam),
],
role: .user
)
Including PDFs in a message request
Replace:
AnthropicInputMessage(content: [.pdf(data: pdfData.base64EncodedString())], role: .user),
AnthropicInputMessage(content: [.text("Summarize this")], role: .user)
with:
let documentBlockParam = AnthropicDocumentBlockParam(
source: .base64PDF(AnthropicBase64PDFSource(data: pdfData.base64EncodedString()))
)
AnthropicMessageParam(
content: [
.textBlock("Summarize this pdf"),
.documentBlock(documentBlockParam),
],
role: .user
)
Additional changes
- The secondsToWait parameter is now available on messageRequest and streamingMessageRequest to control timeout behavior
- The system parameter on AnthropicMessageRequestBody can now be a string or use .blocks([...]) for cached prompts
For complete, copy-pasteable snippets, see the Anthropic Swift Examples.