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.

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.