Skip to content

Support Swift6 #83

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions Example.swiftpm/Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Example.swiftpm/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import AppleProductTypes
let package = Package(
name: "Example",
platforms: [
.iOS("17.0")
.iOS("18.0")
],
products: [
.iOSApplication(
Expand Down
7 changes: 4 additions & 3 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// swift-tools-version: 5.9
// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "AnthropicSwiftSDK",
platforms: [
.iOS(.v17),
.macOS(.v14)
.iOS(.v18),
.macOS(.v15)
],
products: [
// Products define the executables and libraries a package produces, making them visible to other packages.
Expand Down
2 changes: 1 addition & 1 deletion Sources/AnthropicSwiftSDK-TestUtils/HTTPMock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public enum MockInspectType {
}

public class HTTPMock: URLProtocol {
public static var inspectType: MockInspectType = .none
nonisolated(unsafe) public static var inspectType: MockInspectType = .none

public override class func canInit(with request: URLRequest) -> Bool {
return true
Expand Down
2 changes: 1 addition & 1 deletion Sources/AnthropicSwiftSDK/AnthropicAPIError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation
/// The error returned from the Anthropic API
///
/// for more detail, see https://docs.anthropic.com/claude/reference/errors
public enum AnthropicAPIError: String, Decodable, Error {
public enum AnthropicAPIError: String, Decodable, Error, Sendable {
case invalidRequestError = "invalid_request_error"
case authenticationError = "authentication_error"
case permissionError = "permission_error"
Expand Down
2 changes: 1 addition & 1 deletion Sources/AnthropicSwiftSDK/ClientError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Foundation

/// Errors in swift SDK
public enum ClientError: Error {
public enum ClientError: Error, @unchecked Sendable {
/// Received unknown event in Stream.
case unknownStreamingEvent(String)
/// Received in Stream with unknown line type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public struct BatchRequestCounts: Decodable {
/// Number of requests in the Message Batch that have completed successfully.
///
/// This is zero until processing of the entire Message Batch has ended.
let succeeeded: Int
let succeeded: Int
Copy link
Preview

Copilot AI Jul 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed spelling correction from "succeeeded" to "succeeded".

Copilot uses AI. Check for mistakes.

/// Number of requests in the Message Batch that encountered an error.
///
/// This is zero until processing of the entire Message Batch has ended.
Expand Down
4 changes: 2 additions & 2 deletions Sources/AnthropicSwiftSDK/Entity/Content/Content.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Foundation

/// Type of content block.
public enum ContentType: String {
public enum ContentType: String, Sendable {
/// single string
case text
/// image content
Expand All @@ -26,7 +26,7 @@ public enum ContentType: String {
/// Each input message `content` may be either a single `string` or an array of content blocks, where each block has a specific `type`. Using a `string` for `content` is shorthand for an array of one content block of type `text`.
///
/// Starting with Claude 3 models, you can also send `image` content blocks.
public enum Content {
public enum Content: Sendable {
/// a single string
case text(String, cacheControl: CacheControl? = nil)
/// currently supported the `base64` source type for images, and the `image/jpeg`, `image/png`, `image/gif`, and `image/webp` media types.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
//
import Foundation

public struct DocumentContent {
public enum DocumentContentType: String, Codable {
public struct DocumentContent: Sendable {
public enum DocumentContentType: String, Codable, Sendable {
case base64
}

public enum DocumentContentMediaType: String, Codable {
public enum DocumentContentMediaType: String, Codable, Sendable {
case pdf = "application/pdf"
}

Expand Down
6 changes: 3 additions & 3 deletions Sources/AnthropicSwiftSDK/Entity/Content/ImageContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
import Foundation

/// Object for image content.
public struct ImageContent {
public struct ImageContent: Sendable {
/// currently support the base64 source type for images
public enum ImageContentType: String, Codable {
public enum ImageContentType: String, Codable, Sendable {
case base64
}

/// currently support the image/jpeg, image/png, image/gif, and image/webp media types.
public enum ImageContentMediaType: String, Codable {
public enum ImageContentMediaType: String, Codable, Sendable {
case jpeg = "image/jpeg"
case png = "image/png"
case gif = "image/gif"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

public struct ToolResultContent {
public struct ToolResultContent: Sendable {
/// The `id of the tool use request this is a result for
public let toolUseId: String
/// The result of the tool, as a string (e.g. `"content": "15 degrees"` ) or
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

public struct ToolUseContent {
public struct ToolUseContent: @unchecked Sendable {
/// A unique identifier for this particular tool use block.
/// This will be used to match up the tool results later.
public let id: String
Expand Down
2 changes: 1 addition & 1 deletion Sources/AnthropicSwiftSDK/Entity/Message.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import Foundation
/// Each input message must be an object with a `role` and `content`. You can specify a single `user`-role message, or you can include multiple `user` and `assistant` messages. The first message must always use the `user` role.
///
/// If the final message uses the `assistant` role, the response content will continue immediately from the content in that message. This can be used to constrain part of the model's response.
public struct Message: Codable {
public struct Message: Codable, Sendable {
/// role of the message
public let role: Role
/// content of the message
Expand Down
2 changes: 1 addition & 1 deletion Sources/AnthropicSwiftSDK/Entity/Model.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation
/// The model that will complete your prompt.
///
/// See [models](https://docs.anthropic.com/claude/docs/models-overview) for additional details and options.
public enum Model {
public enum Model: Sendable {
/// Most powerful model for highly complex tasks
// swiftlint:disable:next identifier_name
case claude_3_Opus
Expand Down
2 changes: 1 addition & 1 deletion Sources/AnthropicSwiftSDK/Entity/Role.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

public enum Role: String, Codable {
public enum Role: String, Codable, Sendable {
case user
case assistant
}
2 changes: 1 addition & 1 deletion Sources/AnthropicSwiftSDK/Entity/StopReason.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Foundation

/// The reason that we stopped.
public enum StopReason: String, Decodable {
public enum StopReason: String, Decodable, Sendable {
/// the model reached a natural stopping point
case endTurn = "end_turn"
/// we exceeded the requested max_tokens or the model's maximum
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

public struct ContentBlockDelta: Decodable {
public struct ContentBlockDelta: Decodable, Sendable {
public let type: ContentBlockDeltaType
public let text: String?
public let partialJson: String?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

public enum ContentBlockDeltaType: String, Decodable {
public enum ContentBlockDeltaType: String, Decodable, Sendable {
case text = "text_delta"
case inputJSON = "input_json_delta"
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

public struct MessageDelta: Decodable {
public struct MessageDelta: Decodable, Sendable {
public let stopReason: StopReason
public let stopSequence: String?
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

public struct StreamingError: Decodable {
public struct StreamingError: Decodable, Sendable {
public let type: AnthropicAPIError
public let message: String
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

public enum StreamingEvent: String, Decodable {
public enum StreamingEvent: String, Decodable, Sendable {
/// contains a `Message` object with empty content
case messageStart = "message_start"
/// A series of content blocks, each of which have a `content_block_start`, one or more `content_block_delta` events, and a `content_block_stop` event. Each content block will have an `index` that corresponds to its index in the final `Message` content array.
Expand Down
4 changes: 2 additions & 2 deletions Sources/AnthropicSwiftSDK/Entity/SystemPrompt.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

public enum CacheControl: String {
public enum CacheControl: String, Sendable {
/// corresponds to this 5-minute lifetime.
case ephemeral
}
Expand All @@ -23,7 +23,7 @@ extension CacheControl: Encodable {
}
}

public enum SystemPrompt {
public enum SystemPrompt: Sendable {
case text(String, CacheControl?)

private var type: String {
Expand Down
2 changes: 1 addition & 1 deletion Sources/AnthropicSwiftSDK/Entity/TokenUsage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Foundation

/// Billing and rate-limit usage.
public struct TokenUsage: Decodable {
public struct TokenUsage: Decodable, Sendable {
/// The number of input tokens which were used.
public let inputTokens: Int?
/// The number of output tokens which were used.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,14 @@ extension AsyncThrowingStream where Element == StreamingResponse {
let accumulator = InputJSONDeltaAccumulator()
let accumulativeStream = accumulator.createAccumulativeStream()

Task {
Task { @Sendable in
do {
defer {
accumulator.finish()
}

for try await value in self {
try accumulator.accumulateIfNeeded(value)
}
accumulator.finish()
} catch {
throw error
accumulator.finish(throwing: error)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import Foundation
/// Object type.
///
/// For Messages, this is always "message".
public enum MessagesResponseType: String, Decodable {
public enum MessagesResponseType: String, Decodable, Sendable {
case message
}

/// Messages API response
public struct MessagesResponse: Decodable {
public struct MessagesResponse: Decodable, Sendable {
/// Unique object identifier.
public let id: String
/// Object type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

public protocol StreamingResponse: Decodable {
public protocol StreamingResponse: Decodable, Sendable {
var type: StreamingEvent { get }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Foundation

public enum AnthropicStreamingParser {
// swiftlint:disable:next cyclomatic_complexity
public static func parse<T: AsyncSequence>(stream: T) async throws -> AsyncThrowingStream<StreamingResponse, Error> where T.Element == String {
public static func parse<T: AsyncSequence>(stream: T) async throws -> AsyncThrowingStream<StreamingResponse, Error> where T.Element == String, T: Sendable {
return AsyncThrowingStream.init { continuation in
let task = Task {
var currentEvent: StreamingEvent?
Expand Down
19 changes: 12 additions & 7 deletions Sources/AnthropicSwiftSDK/Util/InputJSONDeltaAccumulator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,36 @@

import Foundation

class InputJSONDeltaAccumulator {
class InputJSONDeltaAccumulator: @unchecked Sendable {
private var partialJson: [StreamingContentBlockDeltaResponse] = []
private var toolUseInfo: StreamingContentBlockStartResponse?
private var accumulativeStream: AsyncThrowingStream<StreamingResponse, Error>.Continuation?

/// Receive StreamingResponse and collect `tool_use` related information if it exists. When `message_stop` is detected, the collected `tool_use`-related information is compiled and sent together with the `StreamingMessageDeltaResponse`.
/// - Parameter response: `StreamingResponse` to collect data
func accumulateIfNeeded(_ response: StreamingResponse) throws {
var modifiedResponse = response

switch response {
case let contentBlockStart as StreamingContentBlockStartResponse:
if case .toolUse = contentBlockStart.contentBlock {
toolUseInfo = contentBlockStart
}
accumulativeStream?.yield(response)
case let contentBlockDelta as StreamingContentBlockDeltaResponse:
if contentBlockDelta.delta.type == .inputJSON {
partialJson.append(contentBlockDelta)
}
accumulativeStream?.yield(response)
case let messageDelta as StreamingMessageDeltaResponse:
if messageDelta.delta.stopReason == .toolUse {
let toolUseContent = try aggregateToolUseContent(from: partialJson, with: toolUseInfo)
modifiedResponse = messageDelta.added(toolUseContent: toolUseContent)
let modifiedResponse = messageDelta.added(toolUseContent: toolUseContent)
accumulativeStream?.yield(modifiedResponse)
} else {
accumulativeStream?.yield(response)
}
default:
break
accumulativeStream?.yield(response)
}

accumulativeStream?.yield(modifiedResponse)
}

/// Aggregate the `input_json_delta` of the collected `tool_use` to generate a JSON String and return it as a `ToolUseContent` with other information from the `tool_use`.
Expand Down Expand Up @@ -70,4 +71,8 @@ class InputJSONDeltaAccumulator {
func finish() {
accumulativeStream?.finish()
}

func finish(throwing error: Error) {
accumulativeStream?.finish(throwing: error)
}
}
Loading