diff --git a/.xcode/SwiftClaudeAppPackage/Sources/App/Content View.swift b/.xcode/SwiftClaudeAppPackage/Sources/App/Content View.swift index 6b6cc714..427443f0 100644 --- a/.xcode/SwiftClaudeAppPackage/Sources/App/Content View.swift +++ b/.xcode/SwiftClaudeAppPackage/Sources/App/Content View.swift @@ -3,13 +3,11 @@ import ClaudeClient import HaikuGenerator import SwiftUI -/// Currently computer use doesn't work great, probably something to do with the image resizing logic - struct ContentView: View { var body: some View { ClaudeProvider( - defaultModel: .claude35Sonnet20241022 + defaultModel: .default ) { claude in TabView { Tab("Haiku Generator", systemImage: "sparkles.rectangle.stack") { diff --git a/Sources/Claude API/Conversation/Assistant Message.swift b/Sources/Claude API/Conversation/Assistant Message.swift index f1554f07..cc136676 100644 --- a/Sources/Claude API/Conversation/Assistant Message.swift +++ b/Sources/Claude API/Conversation/Assistant Message.swift @@ -196,7 +196,9 @@ extension Claude { guard case .toolUseBlock(_, let toolUse) = block else { continue } - _ = try await toolUse.output(isolation: isolation) + + /// We ignore invocation errors, since those will be reported in the tool use result + _ = try? await toolUse.output(isolation: isolation) } } @@ -560,36 +562,28 @@ extension Claude.ConversationAssistantMessage { ) ) - guard let invocationResult = toolUse.invocationResult else { - throw IncompleteMessage() - } - - let output: Conversation.ToolOutput - do { - output = try invocationResult.get() - } catch { - /// We only errors thrown by the tool to be encoded as "error" results. - toolInvocationResultContent.append( - .toolResult( - id: toolUse.id, - content: "\(error)", - isError: true - ) + let block: ClaudeClient.MessagesEndpoint.Request.Message.Content.Block + if let error = toolUse.currentError { + block = .toolResult( + id: toolUse.id, + content: "\(error)", + isError: true ) - continue - } - - toolInvocationResultContent.append( - .toolResult( + } else { + guard let invocationResult = toolUse.invocationResult else { + throw IncompleteMessage() + } + block = .toolResult( id: toolUse.id, - content: try renderToolOutput(output) + content: try renderToolOutput(invocationResult.get()) .messageContent .messagesRequestMessageContent( for: model, imagePreprocessingMode: imagePreprocessingMode ) ) - ) + } + toolInvocationResultContent.append(block) } } @@ -646,6 +640,16 @@ extension Claude.ToolUse: PrivateToolUseProtocol { func contentBlock( inputDecodingFailureEncodingStrategy: Claude.ToolInputDecodingFailureEncodingStrategy ) throws -> ClaudeClient.MessagesEndpoint.Request.Message.Content.Block { + guard inputDecodingError == nil else { + /// If input decoding failed, we use the JSON string as the input + return .toolUse( + id: id, + name: concreteTool.definition.name, + input: [ + "inputJSON": currentInputJSON + ] + ) + } guard let input = try currentInput else { throw IncompleteMessage() } diff --git a/Sources/Claude API/Conversation/Conversation.swift b/Sources/Claude API/Conversation/Conversation.swift index 4ac7f58a..a3eb5025 100644 --- a/Sources/Claude API/Conversation/Conversation.swift +++ b/Sources/Claude API/Conversation/Conversation.swift @@ -257,6 +257,8 @@ extension Claude.ConversationAssistantMessage { return .user case .toolUse: return .toolUseResult + case .refusal: + throw Claude.StreamingClassifierRefusal() } } } @@ -269,6 +271,8 @@ extension Claude { private struct MaxOutputTokensReached: Error {} + private struct StreamingClassifierRefusal: Error {} + } // MARK: Message diff --git a/Sources/Client/Client.swift b/Sources/Client/Client.swift index 5aef4c0e..920512d6 100644 --- a/Sources/Client/Client.swift +++ b/Sources/Client/Client.swift @@ -79,7 +79,7 @@ public actor ClaudeClient { return ServerSentEvents(client: self, body: response.body) } - package func decode( + package func decode( _ type: T.Type, fromResponseData data: Data ) throws -> sending T { @@ -138,11 +138,12 @@ public actor ClaudeClient { httpRequest = mutableRequest } + let request = HTTPTransportRequest( + http: httpRequest, + body: try requestBodyEncoder.encode(body) + ) let response = try await httpTransport.send( - HTTPTransportRequest( - http: httpRequest, - body: try requestBodyEncoder.encode(body) - ), + request, isolation: self ) return response diff --git a/Sources/Client/Model.swift b/Sources/Client/Model.swift index 81456e5a..0aec235b 100644 --- a/Sources/Client/Model.swift +++ b/Sources/Client/Model.swift @@ -4,30 +4,55 @@ extension ClaudeClient.Model { /// The default model may change without notice /// If using a specific model is important, specify it explicitly. public static var `default`: Self { - .claude35Sonnet20241022 + .claudeOpus4120250805 } - public static var claude35haiku20241022: Self { + // MARK: - Opus Models + + public static var claudeOpus4120250805: Self { Self( - id: "claude-3-5-haiku-20241022", + id: "claude-opus-4-1-20250805", maxOutputTokens: 8192, - vision: .unavailable + vision: .standard ) } - public static var claude35Sonnet20241022: Self { + + public static var claudeOpus420250514: Self { Self( - id: "claude-3-5-sonnet-20241022", + id: "claude-opus-4-20250514", maxOutputTokens: 8192, vision: .standard ) } - public static var claude3Opust20240229: Self { + + // MARK: - Sonnet Models + + public static var claudeSonnet420250514: Self { Self( - id: "claude-3-opus-20240229", - maxOutputTokens: 4096, + id: "claude-sonnet-4-20250514", + maxOutputTokens: 8192, vision: .standard ) } + + public static var claude37Sonnet20250219: Self { + Self( + id: "claude-3-7-sonnet-20250219", + maxOutputTokens: 8192, + vision: .standard + ) + } + + // MARK: - Haiku Models + + public static var claude35haiku20241022: Self { + Self( + id: "claude-3-5-haiku-20241022", + maxOutputTokens: 8192, + vision: .standard + ) + } + public static var claude3haiku20240307: Self { Self( id: "claude-3-haiku-20240307", diff --git a/Sources/Client/Server-Sent Events.swift b/Sources/Client/Server-Sent Events.swift index de00f923..1a89eeaa 100644 --- a/Sources/Client/Server-Sent Events.swift +++ b/Sources/Client/Server-Sent Events.swift @@ -30,7 +30,8 @@ extension ClaudeClient { try await client.decode( AnthropicEnum.self, fromResponseData: rawEvent.data - ).wrappedValue + ) + .wrappedValue ) } catch { return .failure(.decodingFailure(error)) @@ -121,7 +122,8 @@ extension ClaudeClient { } /// Uncomment to print server-sent events - // print("SSE: \(data)") + // print(eventLine) + // print(dataLine) return .success(RawServerSentEvent(name: eventName, data: Data(data.utf8))) } diff --git a/Sources/Client/Support/Coding.swift b/Sources/Client/Support/Coding.swift index 8111ef10..544bf6a9 100644 --- a/Sources/Client/Support/Coding.swift +++ b/Sources/Client/Support/Coding.swift @@ -32,7 +32,10 @@ struct ResponseBodyDecoder { return Self(decoder: .anthropic) } - func decode(_ type: T.Type, from data: Data) throws -> sending T { + func decode( + _ type: T.Type, + from data: Data + ) throws -> sending T { try decoder.decode(type, from: data) } @@ -50,6 +53,12 @@ struct ResponseBodyDecoder { } +#if swift(>=6.2) + package typealias ResponseBodyDecodable = Decodable & SendableMetatype +#else + package typealias ResponseBodyDecodable = Decodable +#endif + // MARK: - Configuration extension JSONDecoder { diff --git a/Sources/Messages Endpoint/Metadata.swift b/Sources/Messages Endpoint/Metadata.swift index 057d1c2b..dbf7178a 100644 --- a/Sources/Messages Endpoint/Metadata.swift +++ b/Sources/Messages Endpoint/Metadata.swift @@ -25,6 +25,7 @@ extension ClaudeClient.MessagesEndpoint { case maxTokens = "max_tokens" case toolUse = "tool_use" case stopSequence = "stop_sequence" + case refusal = "refusal" } public struct Usage: Decodable { diff --git a/Tests/Claude Tests/Claude Tests.swift b/Tests/Claude Tests/Claude Tests.swift index f2e6d115..1a090ceb 100644 --- a/Tests/Claude Tests/Claude Tests.swift +++ b/Tests/Claude Tests/Claude Tests.swift @@ -9,7 +9,7 @@ func userMessageTextConcatenation() throws { let requestContent = try userMessage .messagesRequestMessageContent( - for: .claude35Sonnet20241022, + for: .default, imagePreprocessingMode: .recommended(quality: 1), renderImage: { _ in fatalError()