Skip to content
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
4 changes: 1 addition & 3 deletions .xcode/SwiftClaudeAppPackage/Sources/App/Content View.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand Down
50 changes: 27 additions & 23 deletions Sources/Claude API/Conversation/Assistant Message.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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()
}
Expand Down
4 changes: 4 additions & 0 deletions Sources/Claude API/Conversation/Conversation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@ extension Claude.ConversationAssistantMessage {
return .user
case .toolUse:
return .toolUseResult
case .refusal:
throw Claude.StreamingClassifierRefusal()
}
}
}
Expand All @@ -269,6 +271,8 @@ extension Claude {

private struct MaxOutputTokensReached: Error {}

private struct StreamingClassifierRefusal: Error {}

}

// MARK: Message
Expand Down
11 changes: 6 additions & 5 deletions Sources/Client/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public actor ClaudeClient {
return ServerSentEvents(client: self, body: response.body)
}

package func decode<T: Decodable>(
package func decode<T: ResponseBodyDecodable>(
_ type: T.Type,
fromResponseData data: Data
) throws -> sending T {
Expand Down Expand Up @@ -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
Expand Down
43 changes: 34 additions & 9 deletions Sources/Client/Model.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 4 additions & 2 deletions Sources/Client/Server-Sent Events.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ extension ClaudeClient {
try await client.decode(
AnthropicEnum<Event>.self,
fromResponseData: rawEvent.data
).wrappedValue
)
.wrappedValue
)
} catch {
return .failure(.decodingFailure(error))
Expand Down Expand Up @@ -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)))
}
Expand Down
11 changes: 10 additions & 1 deletion Sources/Client/Support/Coding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ struct ResponseBodyDecoder {
return Self(decoder: .anthropic)
}

func decode<T: Decodable>(_ type: T.Type, from data: Data) throws -> sending T {
func decode<T: ResponseBodyDecodable>(
_ type: T.Type,
from data: Data
) throws -> sending T {
try decoder.decode(type, from: data)
}

Expand All @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions Sources/Messages Endpoint/Metadata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion Tests/Claude Tests/Claude Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ func userMessageTextConcatenation() throws {
let requestContent =
try userMessage
.messagesRequestMessageContent(
for: .claude35Sonnet20241022,
for: .default,
imagePreprocessingMode: .recommended(quality: 1),
renderImage: { _ in
fatalError()
Expand Down