diff --git a/Examples/SwiftOpenAIExample/SwiftOpenAIExample/Files/AttachmentView.swift b/Examples/SwiftOpenAIExample/SwiftOpenAIExample/Files/AttachmentView.swift index 5d29c7e8..c83b9a11 100644 --- a/Examples/SwiftOpenAIExample/SwiftOpenAIExample/Files/AttachmentView.swift +++ b/Examples/SwiftOpenAIExample/SwiftOpenAIExample/Files/AttachmentView.swift @@ -10,6 +10,7 @@ import SwiftUI struct AttachmentView: View { let fileName: String @Binding var actionTrigger: Bool + let isLoading: Bool var body: some View { diff --git a/Examples/SwiftOpenAIExample/SwiftOpenAIExample/ResponseAPIDemo/ResponseStreamDemoView.swift b/Examples/SwiftOpenAIExample/SwiftOpenAIExample/ResponseAPIDemo/ResponseStreamDemoView.swift index 84b561a7..195c7310 100644 --- a/Examples/SwiftOpenAIExample/SwiftOpenAIExample/ResponseAPIDemo/ResponseStreamDemoView.swift +++ b/Examples/SwiftOpenAIExample/SwiftOpenAIExample/ResponseAPIDemo/ResponseStreamDemoView.swift @@ -145,6 +145,7 @@ struct ResponseStreamDemoView: View { struct MessageBubbleView: View { let message: ResponseStreamProvider.ResponseMessage + @Environment(\.colorScheme) var colorScheme var body: some View { diff --git a/Examples/SwiftOpenAIExample/SwiftOpenAIExample/SharedUI/LoadingView.swift b/Examples/SwiftOpenAIExample/SwiftOpenAIExample/SharedUI/LoadingView.swift index bbceb00f..0463b9ed 100644 --- a/Examples/SwiftOpenAIExample/SwiftOpenAIExample/SharedUI/LoadingView.swift +++ b/Examples/SwiftOpenAIExample/SwiftOpenAIExample/SharedUI/LoadingView.swift @@ -8,8 +8,6 @@ import SwiftUI struct LoadingView: View { - @State private var dotsCount = 0 - let timer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect() var body: some View { @@ -28,4 +26,7 @@ struct LoadingView: View { func getDots() -> String { String(repeating: ".", count: dotsCount) } + + @State private var dotsCount = 0 + } diff --git a/Sources/OpenAI/Private/Audio/MicrophonePCMSampleVendorAT.swift b/Sources/OpenAI/Private/Audio/MicrophonePCMSampleVendorAT.swift index b9578511..c9e63ef3 100644 --- a/Sources/OpenAI/Private/Audio/MicrophonePCMSampleVendorAT.swift +++ b/Sources/OpenAI/Private/Audio/MicrophonePCMSampleVendorAT.swift @@ -278,11 +278,11 @@ class MicrophonePCMSampleVendorAT: MicrophonePCMSampleVendor { /// This @RealtimeActor annotation is a lie. @RealtimeActor private let audioRenderCallback: AURenderCallback = { inRefCon, - ioActionFlags, - inTimeStamp, - inBusNumber, - inNumberFrames, - _ in + ioActionFlags, + inTimeStamp, + inBusNumber, + inNumberFrames, + _ in let microphonePCMSampleVendor = Unmanaged .fromOpaque(inRefCon) .takeUnretainedValue() diff --git a/Sources/OpenAI/Private/Realtime/OpenAIRealtimeSession.swift b/Sources/OpenAI/Private/Realtime/OpenAIRealtimeSession.swift index cdd1ab08..0435f6ad 100644 --- a/Sources/OpenAI/Private/Realtime/OpenAIRealtimeSession.swift +++ b/Sources/OpenAI/Private/Realtime/OpenAIRealtimeSession.swift @@ -249,6 +249,15 @@ open class OpenAIRealtimeSession { continuation?.yield(.mcpListToolsFailed(fullError)) + case "response.mcp_call.completed": + let eventId = json["event_id"] as? String + let itemId = json["item_id"] as? String + let outputIndex = json["output_index"] as? Int + continuation?.yield(.responseMcpCallCompleted(eventId: eventId, itemId: itemId, outputIndex: outputIndex)) + + case "response.mcp_call.in_progress": + continuation?.yield(.responseMcpCallInProgress) + case "response.done": // Handle response completion (may contain errors like insufficient_quota) if diff --git a/Sources/OpenAI/Public/ResponseModels/Realtime/OpenAIRealtimeMessage.swift b/Sources/OpenAI/Public/ResponseModels/Realtime/OpenAIRealtimeMessage.swift index bb551a9c..e46f757e 100644 --- a/Sources/OpenAI/Public/ResponseModels/Realtime/OpenAIRealtimeMessage.swift +++ b/Sources/OpenAI/Public/ResponseModels/Realtime/OpenAIRealtimeMessage.swift @@ -26,7 +26,6 @@ public enum OpenAIRealtimeMessage: Sendable { case mcpListToolsInProgress // "mcp_list_tools.in_progress" case mcpListToolsCompleted([String: Any]) // "mcp_list_tools.completed" with tools data case mcpListToolsFailed(String?) // "mcp_list_tools.failed" with error details - /// Response completion with potential errors case responseDone(status: String, statusDetails: [String: Any]?) // "response.done" @@ -42,6 +41,10 @@ public enum OpenAIRealtimeMessage: Sendable { case responseContentPartAdded(type: String) // "response.content_part.added" case responseContentPartDone(type: String, text: String?) // "response.content_part.done" + // MCP response + case responseMcpCallCompleted(eventId: String?, itemId: String?, outputIndex: Int?) + case responseMcpCallInProgress + /// Conversation item case conversationItemCreated(itemId: String, type: String, role: String?) // "conversation.item.created" } diff --git a/Sources/OpenAI/Public/Service/OpenAIService.swift b/Sources/OpenAI/Public/Service/OpenAIService.swift index 525bdaf7..a0025ae3 100644 --- a/Sources/OpenAI/Public/Service/OpenAIService.swift +++ b/Sources/OpenAI/Public/Service/OpenAIService.swift @@ -1419,9 +1419,11 @@ extension OpenAIService { case .threadMessageDelta: let decoded = try self.decoder.decode(MessageDeltaObject.self, from: data) continuation.yield(.threadMessageDelta(decoded)) + case .threadRunStepDelta: let decoded = try self.decoder.decode(RunStepDeltaObject.self, from: data) continuation.yield(.threadRunStepDelta(decoded)) + case .threadRun: // We expect a object of type "thread.run.SOME_STATE" in the data object // However what we get is a `thread.run` object but we can check the status @@ -1454,6 +1456,7 @@ extension OpenAIService { } #endif } + default: #if DEBUG if debugEnabled {