diff --git a/Sources/AnthropicSwiftSDK/Entity/Content.swift b/Sources/AnthropicSwiftSDK/Entity/Content/Content.swift similarity index 86% rename from Sources/AnthropicSwiftSDK/Entity/Content.swift rename to Sources/AnthropicSwiftSDK/Entity/Content/Content.swift index 65e98a6..8bbaeca 100644 --- a/Sources/AnthropicSwiftSDK/Entity/Content.swift +++ b/Sources/AnthropicSwiftSDK/Entity/Content/Content.swift @@ -17,6 +17,8 @@ public enum ContentType: String { case toolUse = "tool_use" /// result of tool use case toolResult = "tool_result" + /// document, like pdf + case document } /// The content of message. @@ -31,18 +33,21 @@ public enum Content { case image(ImageContent) case toolUse(ToolUseContent) case toolResult(ToolResultContent) + case document(DocumentContent) /// The type of content block. public var contentType: ContentType { switch self { case .text: - return ContentType.text + return .text case .image: - return ContentType.image + return .image case .toolUse: - return ContentType.toolUse + return .toolUse case .toolResult: - return ContentType.toolResult + return .toolResult + case .document: + return .document } } } @@ -92,6 +97,10 @@ extension Content: Encodable { if toolResult.isError != nil { try container.encode(toolResult.isError, forKey: .isError) } + case let .document(document): + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.contentType.rawValue, forKey: .type) + try container.encode(document, forKey: .source) } } } @@ -114,6 +123,9 @@ extension Content: Decodable { self = .toolUse(content) case .toolResult: fatalError("ContentType: `tool_result` is only used by user, not by assistant") + case .document: + let document = try container.decode(DocumentContent.self, forKey: .source) + self = .document(document) case .none: throw ClientError.failedToParseContentType(contentTypeString) } diff --git a/Sources/AnthropicSwiftSDK/Entity/Content/DocumentContent.swift b/Sources/AnthropicSwiftSDK/Entity/Content/DocumentContent.swift new file mode 100644 index 0000000..4534f5b --- /dev/null +++ b/Sources/AnthropicSwiftSDK/Entity/Content/DocumentContent.swift @@ -0,0 +1,42 @@ +// +// DocumentContent.swift +// AnthropicSwiftSDK +// +// Created by 伊藤史 on 2024/11/05. +// +import Foundation + +public struct DocumentContent { + public enum DocumentContentType: String, Codable { + case base64 + } + + public enum DocumentContentMediaType: String, Codable { + case pdf = "application/pdf" + } + + public let type: DocumentContentType + public let mediaType: DocumentContentMediaType + public let data: Data + + public init(type: DocumentContentType, mediaType: DocumentContentMediaType, data: Data) { + self.type = type + self.mediaType = mediaType + self.data = data + } +} + +extension DocumentContent: Codable { + enum CodingKeys: String, CodingKey { + case type + case mediaType = "media_type" + case data + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.type = try container.decode(DocumentContent.DocumentContentType.self, forKey: .type) + self.mediaType = try container.decode(DocumentContent.DocumentContentMediaType.self, forKey: .mediaType) + self.data = try container.decode(Data.self, forKey: .data) + } +} diff --git a/Sources/AnthropicSwiftSDK/Entity/ImageContent.swift b/Sources/AnthropicSwiftSDK/Entity/Content/ImageContent.swift similarity index 100% rename from Sources/AnthropicSwiftSDK/Entity/ImageContent.swift rename to Sources/AnthropicSwiftSDK/Entity/Content/ImageContent.swift diff --git a/Sources/AnthropicSwiftSDK/Entity/ToolResultContent.swift b/Sources/AnthropicSwiftSDK/Entity/Content/ToolResultContent.swift similarity index 100% rename from Sources/AnthropicSwiftSDK/Entity/ToolResultContent.swift rename to Sources/AnthropicSwiftSDK/Entity/Content/ToolResultContent.swift diff --git a/Sources/AnthropicSwiftSDK/Entity/ToolUseContent.swift b/Sources/AnthropicSwiftSDK/Entity/Content/ToolUseContent.swift similarity index 100% rename from Sources/AnthropicSwiftSDK/Entity/ToolUseContent.swift rename to Sources/AnthropicSwiftSDK/Entity/Content/ToolUseContent.swift diff --git a/Sources/AnthropicSwiftSDK/Network/HeaderProvider/AnthropicHeaderProvider.swift b/Sources/AnthropicSwiftSDK/Network/HeaderProvider/AnthropicHeaderProvider.swift index 5c7a1e3..67d3b56 100644 --- a/Sources/AnthropicSwiftSDK/Network/HeaderProvider/AnthropicHeaderProvider.swift +++ b/Sources/AnthropicSwiftSDK/Network/HeaderProvider/AnthropicHeaderProvider.swift @@ -58,4 +58,8 @@ public enum BetaFeatures: String, CaseIterable { /// /// https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching case promptCaching = "prompt-caching-2024-07-31" + /// PDF Support (beta) + /// + /// https://docs.anthropic.com/en/docs/build-with-claude/pdf-support + case pdfSupport = "pdfs-2024-09-25" } diff --git a/Tests/AnthropicSwiftSDKTests/Entity/ContentTests.swift b/Tests/AnthropicSwiftSDKTests/Entity/ContentTests.swift index f7b1115..cdf4e03 100644 --- a/Tests/AnthropicSwiftSDKTests/Entity/ContentTests.swift +++ b/Tests/AnthropicSwiftSDKTests/Entity/ContentTests.swift @@ -40,6 +40,26 @@ final class ContentTests: XCTestCase { ) } + func testEncodeContentDocument() throws { + let expect = Content.document( + .init( + type: .base64, + mediaType: .pdf, + data: "data".data(using: .utf8)! + ) + ) + + let dictionary = try XCTUnwrap(expect.toDictionary(encoder)) + XCTAssertEqual(dictionary["type"] as! String, "document") + let source = try XCTUnwrap(dictionary["source"] as? [String: Any]) + XCTAssertEqual(source["type"] as! String, "base64") + XCTAssertEqual(source["media_type"] as! String, "application/pdf") + XCTAssertEqual( + source["data"] as! String, + "data".data(using: .utf8)!.base64EncodedString() + ) + } + func testEncodeContentToolResult() throws { let expect = Content.toolResult( .init( diff --git a/Tests/AnthropicSwiftSDKTests/Network/AnthropicAPIClientTests.swift b/Tests/AnthropicSwiftSDKTests/Network/AnthropicAPIClientTests.swift index d9278fa..c61889b 100644 --- a/Tests/AnthropicSwiftSDKTests/Network/AnthropicAPIClientTests.swift +++ b/Tests/AnthropicSwiftSDKTests/Network/AnthropicAPIClientTests.swift @@ -69,7 +69,7 @@ final class AnthropicAPIClientTests: XCTestCase { XCTAssertEqual(headers!["x-api-key"], "test-api-key") XCTAssertEqual(headers!["anthropic-version"], "2023-06-01") XCTAssertEqual(headers!["Content-Type"], "application/json") - XCTAssertEqual(headers!["anthropic-beta"], "message-batches-2024-09-24,computer-use-2024-10-22,prompt-caching-2024-07-31") + XCTAssertEqual(headers!["anthropic-beta"], "message-batches-2024-09-24,computer-use-2024-10-22,prompt-caching-2024-07-31,pdfs-2024-09-25") expectation.fulfill() }, nil) @@ -90,7 +90,7 @@ final class AnthropicAPIClientTests: XCTestCase { XCTAssertEqual(headers!["x-api-key"], "test-api-key") XCTAssertEqual(headers!["anthropic-version"], "2023-06-01") XCTAssertEqual(headers!["Content-Type"], "application/json") - XCTAssertEqual(headers!["anthropic-beta"], "message-batches-2024-09-24,computer-use-2024-10-22,prompt-caching-2024-07-31") + XCTAssertEqual(headers!["anthropic-beta"], "message-batches-2024-09-24,computer-use-2024-10-22,prompt-caching-2024-07-31,pdfs-2024-09-25") expectation.fulfill() }, nil) diff --git a/Tests/AnthropicSwiftSDKTests/Network/HeaderProvider/DefaultAnthropicHeaderProviderTests.swift b/Tests/AnthropicSwiftSDKTests/Network/HeaderProvider/DefaultAnthropicHeaderProviderTests.swift index 6f5e809..d45268e 100644 --- a/Tests/AnthropicSwiftSDKTests/Network/HeaderProvider/DefaultAnthropicHeaderProviderTests.swift +++ b/Tests/AnthropicSwiftSDKTests/Network/HeaderProvider/DefaultAnthropicHeaderProviderTests.swift @@ -29,7 +29,7 @@ final class DefaultAnthropicHeaderProviderTests: XCTestCase { let provider = DefaultAnthropicHeaderProvider(useBeta: true) let headers = provider.getAnthropicAPIHeaders() - XCTAssertEqual(headers["anthropic-beta"], "message-batches-2024-09-24,computer-use-2024-10-22,prompt-caching-2024-07-31") + XCTAssertEqual(headers["anthropic-beta"], "message-batches-2024-09-24,computer-use-2024-10-22,prompt-caching-2024-07-31,pdfs-2024-09-25") } func testBetaHeaderShouldNotBeProvidedIfUseBeta() {