From 881f151d4d77eeb3a20893405d797e7bfe6f5790 Mon Sep 17 00:00:00 2001 From: Fumito Ito Date: Thu, 19 Jun 2025 00:54:11 +0900 Subject: [PATCH 1/4] update dependencies --- Package.resolved | 187 ++++++++++++++++++++++++++++++++++++++++++++++- Package.swift | 10 ++- 2 files changed, 190 insertions(+), 7 deletions(-) diff --git a/Package.resolved b/Package.resolved index a91bbf1..1e40296 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,14 +1,195 @@ { + "originHash" : "9d83d40c421e8ba0da5a795833ed5b1e6b504a138f14b0a1bce0f350838ac57c", "pins" : [ + { + "identity" : "async-http-client", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swift-server/async-http-client.git", + "state" : { + "revision" : "60235983163d040f343a489f7e2e77c1918a8bd9", + "version" : "1.26.1" + } + }, + { + "identity" : "asynchttpkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/fumito-ito/AsyncHTTPKit.git", + "state" : { + "revision" : "2a8b270746e7e1fe7b350db26e04be43eb3030e1", + "version" : "0.0.1" + } + }, + { + "identity" : "swift-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-algorithms.git", + "state" : { + "revision" : "87e50f483c54e6efd60e885f7f5aa946cee68023", + "version" : "1.2.1" + } + }, + { + "identity" : "swift-asn1", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-asn1.git", + "state" : { + "revision" : "a54383ada6cecde007d374f58f864e29370ba5c3", + "version" : "1.3.2" + } + }, + { + "identity" : "swift-async-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-async-algorithms.git", + "state" : { + "revision" : "042e1c4d9d19748c9c228f8d4ebc97bb1e339b0b", + "version" : "1.0.4" + } + }, + { + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "b601256eab081c0f92f059e12818ac1d4f178ff7", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-certificates", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-certificates.git", + "state" : { + "revision" : "999fd70c7803da89f3904d635a6815a2a7cd7585", + "version" : "1.10.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "c1805596154bb3a265fd91b8ac0c4433b4348fb0", + "version" : "1.2.0" + } + }, + { + "identity" : "swift-crypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-crypto.git", + "state" : { + "revision" : "e8d6eba1fef23ae5b359c46b03f7d94be2f41fed", + "version" : "3.12.3" + } + }, + { + "identity" : "swift-http-structured-headers", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-http-structured-headers.git", + "state" : { + "revision" : "db6eea3692638a65e2124990155cd220c2915903", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-http-types", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-http-types.git", + "state" : { + "revision" : "a0a57e949a8903563aba4615869310c0ebf14c03", + "version" : "1.4.0" + } + }, + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log.git", + "state" : { + "revision" : "3d8596ed08bd13520157f0355e35caed215ffbfa", + "version" : "1.6.3" + } + }, + { + "identity" : "swift-nio", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio.git", + "state" : { + "revision" : "34d486b01cd891297ac615e40d5999536a1e138d", + "version" : "2.83.0" + } + }, + { + "identity" : "swift-nio-extras", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-extras.git", + "state" : { + "revision" : "145db1962f4f33a4ea07a32e751d5217602eea29", + "version" : "1.28.0" + } + }, + { + "identity" : "swift-nio-http2", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-http2.git", + "state" : { + "revision" : "4281466512f63d1bd530e33f4aa6993ee7864be0", + "version" : "1.36.0" + } + }, + { + "identity" : "swift-nio-ssl", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-ssl.git", + "state" : { + "revision" : "36b48956eb6c0569215dc15a587b491d2bb36122", + "version" : "2.32.0" + } + }, + { + "identity" : "swift-nio-transport-services", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-transport-services.git", + "state" : { + "revision" : "cd1e89816d345d2523b11c55654570acd5cd4c56", + "version" : "1.24.0" + } + }, + { + "identity" : "swift-numerics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-numerics.git", + "state" : { + "revision" : "e0ec0f5f3af6f3e4d5e7a19d2af26b481acb6ba8", + "version" : "1.0.3" + } + }, + { + "identity" : "swift-service-lifecycle", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swift-server/swift-service-lifecycle.git", + "state" : { + "revision" : "e7187309187695115033536e8fc9b2eb87fd956d", + "version" : "2.8.0" + } + }, + { + "identity" : "swift-system", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-system.git", + "state" : { + "revision" : "61e4ca4b81b9e09e2ec863b00c340eb13497dac6", + "version" : "1.5.0" + } + }, { "identity" : "swiftyjsonlines", "kind" : "remoteSourceControl", "location" : "https://github.com/fumito-ito/SwiftyJSONLines.git", "state" : { - "revision" : "fb957dada1e920b6916c7083e99895f8f7fc885a", - "version" : "0.0.3" + "revision" : "a96f39c20f9d9ed5abf3a4dc3294e69d0e3521cc", + "version" : "0.0.4" } } ], - "version" : 2 + "version" : 3 } diff --git a/Package.swift b/Package.swift index 7049c13..ba6d37b 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// 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 @@ -7,7 +7,7 @@ let package = Package( name: "AnthropicSwiftSDK", platforms: [ .iOS(.v17), - .macOS(.v14) + .macOS(.v15) ], products: [ // Products define the executables and libraries a package produces, making them visible to other packages. @@ -16,7 +16,8 @@ let package = Package( targets: ["AnthropicSwiftSDK"]), ], dependencies: [ - .package(url: "https://github.com/fumito-ito/SwiftyJSONLines.git", from: "0.0.4") + .package(url: "https://github.com/fumito-ito/SwiftyJSONLines.git", from: "0.0.4"), + .package(url: "https://github.com/fumito-ito/AsyncHTTPKit.git", from: "0.0.1") ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. @@ -24,7 +25,8 @@ let package = Package( .target( name: "AnthropicSwiftSDK", dependencies: [ - .product(name: "SwiftyJSONLines", package: "SwiftyJSONLines") + .product(name: "SwiftyJSONLines", package: "SwiftyJSONLines"), + .product(name: "AsyncHTTPKit", package: "AsyncHTTPKit") ] ), .testTarget( From d6ef614b5cb1a4e6d7c9405b5eeaea07cc45ce56 Mon Sep 17 00:00:00 2001 From: Fumito Ito Date: Thu, 19 Jun 2025 01:17:36 +0900 Subject: [PATCH 2/4] fix concurrency problems for Swift 6 --- Package.resolved | 173 +----------------- Package.swift | 2 +- .../HTTPMock.swift | 2 +- .../AnthropicSwiftSDK/AnthropicAPIError.swift | 2 +- Sources/AnthropicSwiftSDK/ClientError.swift | 2 +- .../Entity/Batch/BatchRequestCounts.swift | 2 +- .../Entity/Content/Content.swift | 4 +- .../Entity/Content/DocumentContent.swift | 6 +- .../Entity/Content/ImageContent.swift | 6 +- .../Entity/Content/ToolResultContent.swift | 2 +- .../Entity/Content/ToolUseContent.swift | 2 +- .../AnthropicSwiftSDK/Entity/Message.swift | 2 +- Sources/AnthropicSwiftSDK/Entity/Model.swift | 2 +- Sources/AnthropicSwiftSDK/Entity/Role.swift | 2 +- .../AnthropicSwiftSDK/Entity/StopReason.swift | 2 +- .../Entity/Streaming/ContentBlockDelta.swift | 2 +- .../Streaming/ContentBlockDeltaType.swift | 2 +- .../Entity/Streaming/MessageDelta.swift | 2 +- .../Entity/Streaming/StreamingError.swift | 2 +- .../Entity/Streaming/StreamingEvent.swift | 2 +- .../Entity/SystemPrompt.swift | 4 +- .../AnthropicSwiftSDK/Entity/TokenUsage.swift | 2 +- .../StreamingResponse+Extension.swift | 9 +- .../Network/Response/MessagesResponse.swift | 4 +- .../Network/Response/StreamingResponse.swift | 2 +- .../AnthropicStreamingParser.swift | 2 +- .../Util/InputJSONDeltaAccumulator.swift | 19 +- .../API/MessageBatchesTests.swift | 12 +- .../Network/Response/BatchResponseTests.swift | 10 +- .../Response/ObjectListResponseTests.swift | 8 +- 30 files changed, 62 insertions(+), 231 deletions(-) diff --git a/Package.resolved b/Package.resolved index 1e40296..65c9736 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,15 +1,6 @@ { - "originHash" : "9d83d40c421e8ba0da5a795833ed5b1e6b504a138f14b0a1bce0f350838ac57c", + "originHash" : "e507edd1ad78200d7a132348207d914eee1bafac2cc39be2e82c95b96253d9d9", "pins" : [ - { - "identity" : "async-http-client", - "kind" : "remoteSourceControl", - "location" : "https://github.com/swift-server/async-http-client.git", - "state" : { - "revision" : "60235983163d040f343a489f7e2e77c1918a8bd9", - "version" : "1.26.1" - } - }, { "identity" : "asynchttpkit", "kind" : "remoteSourceControl", @@ -19,168 +10,6 @@ "version" : "0.0.1" } }, - { - "identity" : "swift-algorithms", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-algorithms.git", - "state" : { - "revision" : "87e50f483c54e6efd60e885f7f5aa946cee68023", - "version" : "1.2.1" - } - }, - { - "identity" : "swift-asn1", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-asn1.git", - "state" : { - "revision" : "a54383ada6cecde007d374f58f864e29370ba5c3", - "version" : "1.3.2" - } - }, - { - "identity" : "swift-async-algorithms", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-async-algorithms.git", - "state" : { - "revision" : "042e1c4d9d19748c9c228f8d4ebc97bb1e339b0b", - "version" : "1.0.4" - } - }, - { - "identity" : "swift-atomics", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-atomics.git", - "state" : { - "revision" : "b601256eab081c0f92f059e12818ac1d4f178ff7", - "version" : "1.3.0" - } - }, - { - "identity" : "swift-certificates", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-certificates.git", - "state" : { - "revision" : "999fd70c7803da89f3904d635a6815a2a7cd7585", - "version" : "1.10.0" - } - }, - { - "identity" : "swift-collections", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-collections.git", - "state" : { - "revision" : "c1805596154bb3a265fd91b8ac0c4433b4348fb0", - "version" : "1.2.0" - } - }, - { - "identity" : "swift-crypto", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-crypto.git", - "state" : { - "revision" : "e8d6eba1fef23ae5b359c46b03f7d94be2f41fed", - "version" : "3.12.3" - } - }, - { - "identity" : "swift-http-structured-headers", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-http-structured-headers.git", - "state" : { - "revision" : "db6eea3692638a65e2124990155cd220c2915903", - "version" : "1.3.0" - } - }, - { - "identity" : "swift-http-types", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-http-types.git", - "state" : { - "revision" : "a0a57e949a8903563aba4615869310c0ebf14c03", - "version" : "1.4.0" - } - }, - { - "identity" : "swift-log", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-log.git", - "state" : { - "revision" : "3d8596ed08bd13520157f0355e35caed215ffbfa", - "version" : "1.6.3" - } - }, - { - "identity" : "swift-nio", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-nio.git", - "state" : { - "revision" : "34d486b01cd891297ac615e40d5999536a1e138d", - "version" : "2.83.0" - } - }, - { - "identity" : "swift-nio-extras", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-nio-extras.git", - "state" : { - "revision" : "145db1962f4f33a4ea07a32e751d5217602eea29", - "version" : "1.28.0" - } - }, - { - "identity" : "swift-nio-http2", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-nio-http2.git", - "state" : { - "revision" : "4281466512f63d1bd530e33f4aa6993ee7864be0", - "version" : "1.36.0" - } - }, - { - "identity" : "swift-nio-ssl", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-nio-ssl.git", - "state" : { - "revision" : "36b48956eb6c0569215dc15a587b491d2bb36122", - "version" : "2.32.0" - } - }, - { - "identity" : "swift-nio-transport-services", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-nio-transport-services.git", - "state" : { - "revision" : "cd1e89816d345d2523b11c55654570acd5cd4c56", - "version" : "1.24.0" - } - }, - { - "identity" : "swift-numerics", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-numerics.git", - "state" : { - "revision" : "e0ec0f5f3af6f3e4d5e7a19d2af26b481acb6ba8", - "version" : "1.0.3" - } - }, - { - "identity" : "swift-service-lifecycle", - "kind" : "remoteSourceControl", - "location" : "https://github.com/swift-server/swift-service-lifecycle.git", - "state" : { - "revision" : "e7187309187695115033536e8fc9b2eb87fd956d", - "version" : "2.8.0" - } - }, - { - "identity" : "swift-system", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-system.git", - "state" : { - "revision" : "61e4ca4b81b9e09e2ec863b00c340eb13497dac6", - "version" : "1.5.0" - } - }, { "identity" : "swiftyjsonlines", "kind" : "remoteSourceControl", diff --git a/Package.swift b/Package.swift index ba6d37b..d800dd4 100644 --- a/Package.swift +++ b/Package.swift @@ -6,7 +6,7 @@ import PackageDescription let package = Package( name: "AnthropicSwiftSDK", platforms: [ - .iOS(.v17), + .iOS(.v18), .macOS(.v15) ], products: [ diff --git a/Sources/AnthropicSwiftSDK-TestUtils/HTTPMock.swift b/Sources/AnthropicSwiftSDK-TestUtils/HTTPMock.swift index 225a885..c120fe0 100644 --- a/Sources/AnthropicSwiftSDK-TestUtils/HTTPMock.swift +++ b/Sources/AnthropicSwiftSDK-TestUtils/HTTPMock.swift @@ -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 diff --git a/Sources/AnthropicSwiftSDK/AnthropicAPIError.swift b/Sources/AnthropicSwiftSDK/AnthropicAPIError.swift index e815c9f..8de32b2 100644 --- a/Sources/AnthropicSwiftSDK/AnthropicAPIError.swift +++ b/Sources/AnthropicSwiftSDK/AnthropicAPIError.swift @@ -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" diff --git a/Sources/AnthropicSwiftSDK/ClientError.swift b/Sources/AnthropicSwiftSDK/ClientError.swift index f7720ea..a9c3bc7 100644 --- a/Sources/AnthropicSwiftSDK/ClientError.swift +++ b/Sources/AnthropicSwiftSDK/ClientError.swift @@ -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. diff --git a/Sources/AnthropicSwiftSDK/Entity/Batch/BatchRequestCounts.swift b/Sources/AnthropicSwiftSDK/Entity/Batch/BatchRequestCounts.swift index ef8591c..11b36cc 100644 --- a/Sources/AnthropicSwiftSDK/Entity/Batch/BatchRequestCounts.swift +++ b/Sources/AnthropicSwiftSDK/Entity/Batch/BatchRequestCounts.swift @@ -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 /// Number of requests in the Message Batch that encountered an error. /// /// This is zero until processing of the entire Message Batch has ended. diff --git a/Sources/AnthropicSwiftSDK/Entity/Content/Content.swift b/Sources/AnthropicSwiftSDK/Entity/Content/Content.swift index ee9d941..da212c7 100644 --- a/Sources/AnthropicSwiftSDK/Entity/Content/Content.swift +++ b/Sources/AnthropicSwiftSDK/Entity/Content/Content.swift @@ -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 @@ -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. diff --git a/Sources/AnthropicSwiftSDK/Entity/Content/DocumentContent.swift b/Sources/AnthropicSwiftSDK/Entity/Content/DocumentContent.swift index 4534f5b..a22db8d 100644 --- a/Sources/AnthropicSwiftSDK/Entity/Content/DocumentContent.swift +++ b/Sources/AnthropicSwiftSDK/Entity/Content/DocumentContent.swift @@ -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" } diff --git a/Sources/AnthropicSwiftSDK/Entity/Content/ImageContent.swift b/Sources/AnthropicSwiftSDK/Entity/Content/ImageContent.swift index d265393..6fa4f8a 100644 --- a/Sources/AnthropicSwiftSDK/Entity/Content/ImageContent.swift +++ b/Sources/AnthropicSwiftSDK/Entity/Content/ImageContent.swift @@ -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" diff --git a/Sources/AnthropicSwiftSDK/Entity/Content/ToolResultContent.swift b/Sources/AnthropicSwiftSDK/Entity/Content/ToolResultContent.swift index f2cc40f..e5ed8f6 100644 --- a/Sources/AnthropicSwiftSDK/Entity/Content/ToolResultContent.swift +++ b/Sources/AnthropicSwiftSDK/Entity/Content/ToolResultContent.swift @@ -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 diff --git a/Sources/AnthropicSwiftSDK/Entity/Content/ToolUseContent.swift b/Sources/AnthropicSwiftSDK/Entity/Content/ToolUseContent.swift index 0d7ac3a..15469f5 100644 --- a/Sources/AnthropicSwiftSDK/Entity/Content/ToolUseContent.swift +++ b/Sources/AnthropicSwiftSDK/Entity/Content/ToolUseContent.swift @@ -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 diff --git a/Sources/AnthropicSwiftSDK/Entity/Message.swift b/Sources/AnthropicSwiftSDK/Entity/Message.swift index c15fe10..3f5a6e9 100644 --- a/Sources/AnthropicSwiftSDK/Entity/Message.swift +++ b/Sources/AnthropicSwiftSDK/Entity/Message.swift @@ -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 diff --git a/Sources/AnthropicSwiftSDK/Entity/Model.swift b/Sources/AnthropicSwiftSDK/Entity/Model.swift index 8e3f193..effe43b 100644 --- a/Sources/AnthropicSwiftSDK/Entity/Model.swift +++ b/Sources/AnthropicSwiftSDK/Entity/Model.swift @@ -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 diff --git a/Sources/AnthropicSwiftSDK/Entity/Role.swift b/Sources/AnthropicSwiftSDK/Entity/Role.swift index a5f5689..b89f3d2 100644 --- a/Sources/AnthropicSwiftSDK/Entity/Role.swift +++ b/Sources/AnthropicSwiftSDK/Entity/Role.swift @@ -7,7 +7,7 @@ import Foundation -public enum Role: String, Codable { +public enum Role: String, Codable, Sendable { case user case assistant } diff --git a/Sources/AnthropicSwiftSDK/Entity/StopReason.swift b/Sources/AnthropicSwiftSDK/Entity/StopReason.swift index 9f28dcb..624820e 100644 --- a/Sources/AnthropicSwiftSDK/Entity/StopReason.swift +++ b/Sources/AnthropicSwiftSDK/Entity/StopReason.swift @@ -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 diff --git a/Sources/AnthropicSwiftSDK/Entity/Streaming/ContentBlockDelta.swift b/Sources/AnthropicSwiftSDK/Entity/Streaming/ContentBlockDelta.swift index 77d8bab..691c5f4 100644 --- a/Sources/AnthropicSwiftSDK/Entity/Streaming/ContentBlockDelta.swift +++ b/Sources/AnthropicSwiftSDK/Entity/Streaming/ContentBlockDelta.swift @@ -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? diff --git a/Sources/AnthropicSwiftSDK/Entity/Streaming/ContentBlockDeltaType.swift b/Sources/AnthropicSwiftSDK/Entity/Streaming/ContentBlockDeltaType.swift index 720729c..e5297f7 100644 --- a/Sources/AnthropicSwiftSDK/Entity/Streaming/ContentBlockDeltaType.swift +++ b/Sources/AnthropicSwiftSDK/Entity/Streaming/ContentBlockDeltaType.swift @@ -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" } diff --git a/Sources/AnthropicSwiftSDK/Entity/Streaming/MessageDelta.swift b/Sources/AnthropicSwiftSDK/Entity/Streaming/MessageDelta.swift index e8d6a45..00b1f2a 100644 --- a/Sources/AnthropicSwiftSDK/Entity/Streaming/MessageDelta.swift +++ b/Sources/AnthropicSwiftSDK/Entity/Streaming/MessageDelta.swift @@ -7,7 +7,7 @@ import Foundation -public struct MessageDelta: Decodable { +public struct MessageDelta: Decodable, Sendable { public let stopReason: StopReason public let stopSequence: String? } diff --git a/Sources/AnthropicSwiftSDK/Entity/Streaming/StreamingError.swift b/Sources/AnthropicSwiftSDK/Entity/Streaming/StreamingError.swift index 4b5a087..ebf5716 100644 --- a/Sources/AnthropicSwiftSDK/Entity/Streaming/StreamingError.swift +++ b/Sources/AnthropicSwiftSDK/Entity/Streaming/StreamingError.swift @@ -7,7 +7,7 @@ import Foundation -public struct StreamingError: Decodable { +public struct StreamingError: Decodable, Sendable { public let type: AnthropicAPIError public let message: String } diff --git a/Sources/AnthropicSwiftSDK/Entity/Streaming/StreamingEvent.swift b/Sources/AnthropicSwiftSDK/Entity/Streaming/StreamingEvent.swift index 11db6ed..5af1671 100644 --- a/Sources/AnthropicSwiftSDK/Entity/Streaming/StreamingEvent.swift +++ b/Sources/AnthropicSwiftSDK/Entity/Streaming/StreamingEvent.swift @@ -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. diff --git a/Sources/AnthropicSwiftSDK/Entity/SystemPrompt.swift b/Sources/AnthropicSwiftSDK/Entity/SystemPrompt.swift index fb7f3f8..544cdc0 100644 --- a/Sources/AnthropicSwiftSDK/Entity/SystemPrompt.swift +++ b/Sources/AnthropicSwiftSDK/Entity/SystemPrompt.swift @@ -7,7 +7,7 @@ import Foundation -public enum CacheControl: String { +public enum CacheControl: String, Sendable { /// corresponds to this 5-minute lifetime. case ephemeral } @@ -23,7 +23,7 @@ extension CacheControl: Encodable { } } -public enum SystemPrompt { +public enum SystemPrompt: Sendable { case text(String, CacheControl?) private var type: String { diff --git a/Sources/AnthropicSwiftSDK/Entity/TokenUsage.swift b/Sources/AnthropicSwiftSDK/Entity/TokenUsage.swift index 00d2d0e..41668ba 100644 --- a/Sources/AnthropicSwiftSDK/Entity/TokenUsage.swift +++ b/Sources/AnthropicSwiftSDK/Entity/TokenUsage.swift @@ -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. diff --git a/Sources/AnthropicSwiftSDK/Network/Extension/StreamingResponse+Extension.swift b/Sources/AnthropicSwiftSDK/Network/Extension/StreamingResponse+Extension.swift index 899bd00..da32276 100644 --- a/Sources/AnthropicSwiftSDK/Network/Extension/StreamingResponse+Extension.swift +++ b/Sources/AnthropicSwiftSDK/Network/Extension/StreamingResponse+Extension.swift @@ -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) } } diff --git a/Sources/AnthropicSwiftSDK/Network/Response/MessagesResponse.swift b/Sources/AnthropicSwiftSDK/Network/Response/MessagesResponse.swift index 0fae2e7..94665c6 100644 --- a/Sources/AnthropicSwiftSDK/Network/Response/MessagesResponse.swift +++ b/Sources/AnthropicSwiftSDK/Network/Response/MessagesResponse.swift @@ -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. diff --git a/Sources/AnthropicSwiftSDK/Network/Response/StreamingResponse.swift b/Sources/AnthropicSwiftSDK/Network/Response/StreamingResponse.swift index 4be1aa3..21d292f 100644 --- a/Sources/AnthropicSwiftSDK/Network/Response/StreamingResponse.swift +++ b/Sources/AnthropicSwiftSDK/Network/Response/StreamingResponse.swift @@ -7,7 +7,7 @@ import Foundation -public protocol StreamingResponse: Decodable { +public protocol StreamingResponse: Decodable, Sendable { var type: StreamingEvent { get } } diff --git a/Sources/AnthropicSwiftSDK/Network/StreamingParser/AnthropicStreamingParser.swift b/Sources/AnthropicSwiftSDK/Network/StreamingParser/AnthropicStreamingParser.swift index a44cbbe..fc722f2 100644 --- a/Sources/AnthropicSwiftSDK/Network/StreamingParser/AnthropicStreamingParser.swift +++ b/Sources/AnthropicSwiftSDK/Network/StreamingParser/AnthropicStreamingParser.swift @@ -9,7 +9,7 @@ import Foundation public enum AnthropicStreamingParser { // swiftlint:disable:next cyclomatic_complexity - public static func parse(stream: T) async throws -> AsyncThrowingStream where T.Element == String { + public static func parse(stream: T) async throws -> AsyncThrowingStream where T.Element == String, T: Sendable { return AsyncThrowingStream.init { continuation in let task = Task { var currentEvent: StreamingEvent? diff --git a/Sources/AnthropicSwiftSDK/Util/InputJSONDeltaAccumulator.swift b/Sources/AnthropicSwiftSDK/Util/InputJSONDeltaAccumulator.swift index f25537d..9337014 100644 --- a/Sources/AnthropicSwiftSDK/Util/InputJSONDeltaAccumulator.swift +++ b/Sources/AnthropicSwiftSDK/Util/InputJSONDeltaAccumulator.swift @@ -7,7 +7,7 @@ import Foundation -class InputJSONDeltaAccumulator { +class InputJSONDeltaAccumulator: @unchecked Sendable { private var partialJson: [StreamingContentBlockDeltaResponse] = [] private var toolUseInfo: StreamingContentBlockStartResponse? private var accumulativeStream: AsyncThrowingStream.Continuation? @@ -15,27 +15,28 @@ class InputJSONDeltaAccumulator { /// 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`. @@ -70,4 +71,8 @@ class InputJSONDeltaAccumulator { func finish() { accumulativeStream?.finish() } + + func finish(throwing error: Error) { + accumulativeStream?.finish(throwing: error) + } } diff --git a/Tests/AnthropicSwiftSDKTests/API/MessageBatchesTests.swift b/Tests/AnthropicSwiftSDKTests/API/MessageBatchesTests.swift index 25967a1..50b63ac 100644 --- a/Tests/AnthropicSwiftSDKTests/API/MessageBatchesTests.swift +++ b/Tests/AnthropicSwiftSDKTests/API/MessageBatchesTests.swift @@ -34,7 +34,7 @@ final class MessageBatchesTests: XCTestCase { "processing_status": "ended", "request_counts": { "processing": 0, - "succeeeded": 95, + "succeeded": 95, "errored": 3, "canceled": 1, "expired": 1 @@ -77,7 +77,7 @@ final class MessageBatchesTests: XCTestCase { "processing_status": "ended", "request_counts": { "processing": 0, - "succeeeded": 95, + "succeeded": 95, "errored": 3, "canceled": 1, "expired": 1 @@ -99,7 +99,7 @@ final class MessageBatchesTests: XCTestCase { XCTAssertEqual(response.type, .message) XCTAssertEqual(response.processingStatus, .ended) XCTAssertEqual(response.requestCounts.processing, 0) - XCTAssertEqual(response.requestCounts.succeeeded, 95) + XCTAssertEqual(response.requestCounts.succeeded, 95) XCTAssertEqual(response.requestCounts.errored, 3) XCTAssertEqual(response.requestCounts.canceled, 1) XCTAssertEqual(response.requestCounts.expired, 1) @@ -120,7 +120,7 @@ final class MessageBatchesTests: XCTestCase { "processing_status": "in_progress", "request_counts": { "processing": 50, - "succeeeded": 0, + "succeeded": 0, "errored": 0, "canceled": 0, "expired": 0 @@ -153,7 +153,7 @@ final class MessageBatchesTests: XCTestCase { "processing_status": "canceling", "request_counts": { "processing": 25, - "succeeeded": 25, + "succeeded": 25, "errored": 0, "canceled": 0, "expired": 0 @@ -197,7 +197,7 @@ final class MessageBatchesTests: XCTestCase { "processing_status": "in_progress", "request_counts": { "processing": 100, - "succeeeded": 0, + "succeeded": 0, "errored": 0, "canceled": 0, "expired": 0 diff --git a/Tests/AnthropicSwiftSDKTests/Network/Response/BatchResponseTests.swift b/Tests/AnthropicSwiftSDKTests/Network/Response/BatchResponseTests.swift index 49dcb79..1acbde3 100644 --- a/Tests/AnthropicSwiftSDKTests/Network/Response/BatchResponseTests.swift +++ b/Tests/AnthropicSwiftSDKTests/Network/Response/BatchResponseTests.swift @@ -17,7 +17,7 @@ final class BatchResponseTests: XCTestCase { "processing_status": "ended", "request_counts": { "processing": 0, - "succeeeded": 95, + "succeeded": 95, "errored": 3, "canceled": 1, "expired": 1 @@ -41,7 +41,7 @@ final class BatchResponseTests: XCTestCase { XCTAssertEqual(batchResponse.processingStatus, .ended) XCTAssertEqual(batchResponse.requestCounts.processing, 0) - XCTAssertEqual(batchResponse.requestCounts.succeeeded, 95) + XCTAssertEqual(batchResponse.requestCounts.succeeded, 95) XCTAssertEqual(batchResponse.requestCounts.errored, 3) XCTAssertEqual(batchResponse.requestCounts.canceled, 1) XCTAssertEqual(batchResponse.requestCounts.expired, 1) @@ -61,7 +61,7 @@ final class BatchResponseTests: XCTestCase { "processing_status": "canceling", "request_counts": { "processing": 50, - "succeeeded": 40, + "succeeded": 40, "errored": 10, "canceled": 0, "expired": 0 @@ -85,7 +85,7 @@ final class BatchResponseTests: XCTestCase { XCTAssertEqual(batchResponse.processingStatus, .canceling) XCTAssertEqual(batchResponse.requestCounts.processing, 50) - XCTAssertEqual(batchResponse.requestCounts.succeeeded, 40) + XCTAssertEqual(batchResponse.requestCounts.succeeded, 40) XCTAssertEqual(batchResponse.requestCounts.errored, 10) XCTAssertEqual(batchResponse.requestCounts.canceled, 0) XCTAssertEqual(batchResponse.requestCounts.expired, 0) @@ -96,4 +96,4 @@ final class BatchResponseTests: XCTestCase { XCTAssertEqual(batchResponse.cancelInitiatedAt, "2024-10-18T16:30:00Z") XCTAssertNil(batchResponse.resultsUrl) } -} \ No newline at end of file +} diff --git a/Tests/AnthropicSwiftSDKTests/Network/Response/ObjectListResponseTests.swift b/Tests/AnthropicSwiftSDKTests/Network/Response/ObjectListResponseTests.swift index 88203aa..3d97fc4 100644 --- a/Tests/AnthropicSwiftSDKTests/Network/Response/ObjectListResponseTests.swift +++ b/Tests/AnthropicSwiftSDKTests/Network/Response/ObjectListResponseTests.swift @@ -19,7 +19,7 @@ final class ObjectListResponseTests: XCTestCase { "processing_status": "ended", "request_counts": { "processing": 0, - "succeeeded": 95, + "succeeded": 95, "errored": 5, "canceled": 0, "expired": 0 @@ -36,7 +36,7 @@ final class ObjectListResponseTests: XCTestCase { "processing_status": "in_progress", "request_counts": { "processing": 100, - "succeeeded": 0, + "succeeded": 0, "errored": 0, "canceled": 0, "expired": 0 @@ -66,7 +66,7 @@ final class ObjectListResponseTests: XCTestCase { XCTAssertEqual(firstBatch.type, .message) XCTAssertEqual(firstBatch.processingStatus, .ended) XCTAssertEqual(firstBatch.requestCounts.processing, 0) - XCTAssertEqual(firstBatch.requestCounts.succeeeded, 95) + XCTAssertEqual(firstBatch.requestCounts.succeeded, 95) XCTAssertEqual(firstBatch.requestCounts.errored, 5) XCTAssertEqual(firstBatch.requestCounts.canceled, 0) XCTAssertEqual(firstBatch.requestCounts.expired, 0) @@ -82,7 +82,7 @@ final class ObjectListResponseTests: XCTestCase { XCTAssertEqual(secondBatch.type, .message) XCTAssertEqual(secondBatch.processingStatus, .inProgress) XCTAssertEqual(secondBatch.requestCounts.processing, 100) - XCTAssertEqual(secondBatch.requestCounts.succeeeded, 0) + XCTAssertEqual(secondBatch.requestCounts.succeeded, 0) XCTAssertEqual(secondBatch.requestCounts.errored, 0) XCTAssertEqual(secondBatch.requestCounts.canceled, 0) XCTAssertEqual(secondBatch.requestCounts.expired, 0) From d067588d886a5e76506c69b07429305552b4c5a2 Mon Sep 17 00:00:00 2001 From: Fumito Ito Date: Wed, 25 Jun 2025 22:41:49 +0900 Subject: [PATCH 3/4] update example platform version --- Example.swiftpm/Package.resolved | 13 +++++++++++-- Example.swiftpm/Package.swift | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Example.swiftpm/Package.resolved b/Example.swiftpm/Package.resolved index a91bbf1..5ebc559 100644 --- a/Example.swiftpm/Package.resolved +++ b/Example.swiftpm/Package.resolved @@ -1,12 +1,21 @@ { "pins" : [ + { + "identity" : "asynchttpkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/fumito-ito/AsyncHTTPKit.git", + "state" : { + "revision" : "2a8b270746e7e1fe7b350db26e04be43eb3030e1", + "version" : "0.0.1" + } + }, { "identity" : "swiftyjsonlines", "kind" : "remoteSourceControl", "location" : "https://github.com/fumito-ito/SwiftyJSONLines.git", "state" : { - "revision" : "fb957dada1e920b6916c7083e99895f8f7fc885a", - "version" : "0.0.3" + "revision" : "a96f39c20f9d9ed5abf3a4dc3294e69d0e3521cc", + "version" : "0.0.4" } } ], diff --git a/Example.swiftpm/Package.swift b/Example.swiftpm/Package.swift index f1a4a58..de98a57 100644 --- a/Example.swiftpm/Package.swift +++ b/Example.swiftpm/Package.swift @@ -10,7 +10,7 @@ import AppleProductTypes let package = Package( name: "Example", platforms: [ - .iOS("17.0") + .iOS("18.0") ], products: [ .iOSApplication( From a7880da76b89ec5cf8c0abbf3660a3930471f552 Mon Sep 17 00:00:00 2001 From: Fumito Ito Date: Wed, 16 Jul 2025 22:26:23 +0900 Subject: [PATCH 4/4] remove unused library --- Package.resolved | 11 +---------- Package.swift | 6 ++---- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/Package.resolved b/Package.resolved index 65c9736..baedf1c 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,15 +1,6 @@ { - "originHash" : "e507edd1ad78200d7a132348207d914eee1bafac2cc39be2e82c95b96253d9d9", + "originHash" : "d61a94dd8be384d28c66ae7d8255335932a2cc4a4de7aed63c898ec3ce7da233", "pins" : [ - { - "identity" : "asynchttpkit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/fumito-ito/AsyncHTTPKit.git", - "state" : { - "revision" : "2a8b270746e7e1fe7b350db26e04be43eb3030e1", - "version" : "0.0.1" - } - }, { "identity" : "swiftyjsonlines", "kind" : "remoteSourceControl", diff --git a/Package.swift b/Package.swift index d800dd4..ef2644f 100644 --- a/Package.swift +++ b/Package.swift @@ -16,8 +16,7 @@ let package = Package( targets: ["AnthropicSwiftSDK"]), ], dependencies: [ - .package(url: "https://github.com/fumito-ito/SwiftyJSONLines.git", from: "0.0.4"), - .package(url: "https://github.com/fumito-ito/AsyncHTTPKit.git", from: "0.0.1") + .package(url: "https://github.com/fumito-ito/SwiftyJSONLines.git", from: "0.0.4") ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. @@ -25,8 +24,7 @@ let package = Package( .target( name: "AnthropicSwiftSDK", dependencies: [ - .product(name: "SwiftyJSONLines", package: "SwiftyJSONLines"), - .product(name: "AsyncHTTPKit", package: "AsyncHTTPKit") + .product(name: "SwiftyJSONLines", package: "SwiftyJSONLines") ] ), .testTarget(