Anthropic Migration Guide

This guide helps you migrate your Anthropic call sites from versions of AIProxySwift earlier than 0.135.0.

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.