@@ -167,7 +167,8 @@ public class MutagenDaemon: FileSyncDaemon {
167167 )
168168 client = DaemonClient (
169169 mgmt: Daemon_DaemonAsyncClient ( channel: channel!) ,
170- sync: Synchronization_SynchronizationAsyncClient ( channel: channel!)
170+ sync: Synchronization_SynchronizationAsyncClient ( channel: channel!) ,
171+ prompt: Prompting_PromptingAsyncClient ( channel: channel!)
171172 )
172173 logger. info (
173174 " Successfully connected to mutagen daemon, socket: \( self . mutagenDaemonSocket. path, privacy: . public) "
@@ -292,9 +293,63 @@ public class MutagenDaemon: FileSyncDaemon {
292293 }
293294}
294295
296+
297+ extension MutagenDaemon {
298+ typealias PromptStream = GRPCAsyncBidirectionalStreamingCall < Prompting_HostRequest , Prompting_HostResponse >
299+
300+ func Host( allowPrompts: Bool = true ) async throws ( DaemonError) -> ( PromptStream , identifier: String ) {
301+ let stream = client!. prompt. makeHostCall ( )
302+
303+ do {
304+ try await stream. requestStream. send ( . with { req in req. allowPrompts = allowPrompts } )
305+ } catch {
306+ throw . grpcFailure( error)
307+ }
308+
309+ // We can't make call `makeAsyncIterator` more than once
310+ // (as a for-loop would do implicitly)
311+ var iter = stream. responseStream. makeAsyncIterator ( )
312+
313+ // "Receive the initialization response, validate it, and extract the prompt identifier"
314+ let initResp : Prompting_HostResponse ?
315+ do {
316+ initResp = try await iter. next ( )
317+ } catch {
318+ throw . grpcFailure( error)
319+ }
320+ guard let initResp = initResp else {
321+ throw . unexpectedStreamClosure
322+ }
323+ // TODO: we'll always accept prompts for now
324+ try initResp. ensureValid ( first: true , allowPrompts: allowPrompts)
325+
326+ Task . detached ( priority: . background) {
327+ do {
328+ while let resp = try await iter. next ( ) {
329+ debugPrint ( resp)
330+ try resp. ensureValid ( first: false , allowPrompts: allowPrompts)
331+ switch resp. isPrompt {
332+ case true :
333+ // TODO: Handle prompt
334+ break
335+ case false :
336+ // TODO: Handle message
337+ break
338+ }
339+ }
340+ } catch {
341+ // TODO: Log prompter stream error
342+ }
343+ }
344+ return ( stream, identifier: initResp. identifier)
345+ }
346+ }
347+
348+
295349struct DaemonClient {
296350 let mgmt : Daemon_DaemonAsyncClient
297351 let sync : Synchronization_SynchronizationAsyncClient
352+ let prompt : Prompting_PromptingAsyncClient
298353}
299354
300355public enum DaemonState {
@@ -336,6 +391,8 @@ public enum DaemonError: Error {
336391 case connectionFailure( Error )
337392 case terminatedUnexpectedly
338393 case grpcFailure( Error )
394+ case invalidGrpcResponse( String )
395+ case unexpectedStreamClosure
339396
340397 var description : String {
341398 switch self {
@@ -349,6 +406,10 @@ public enum DaemonError: Error {
349406 " The daemon must be started first "
350407 case let . grpcFailure( error) :
351408 " Failed to communicate with daemon: \( error) "
409+ case let . invalidGrpcResponse( response) :
410+ " Invalid gRPC response: \( response) "
411+ case . unexpectedStreamClosure:
412+ " Unexpected stream closure "
352413 }
353414 }
354415
0 commit comments