@@ -167,7 +167,8 @@ public class MutagenDaemon: FileSyncDaemon {
167
167
)
168
168
client = DaemonClient (
169
169
mgmt: Daemon_DaemonAsyncClient ( channel: channel!) ,
170
- sync: Synchronization_SynchronizationAsyncClient ( channel: channel!)
170
+ sync: Synchronization_SynchronizationAsyncClient ( channel: channel!) ,
171
+ prompt: Prompting_PromptingAsyncClient ( channel: channel!)
171
172
)
172
173
logger. info (
173
174
" Successfully connected to mutagen daemon, socket: \( self . mutagenDaemonSocket. path, privacy: . public) "
@@ -292,9 +293,63 @@ public class MutagenDaemon: FileSyncDaemon {
292
293
}
293
294
}
294
295
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
+
295
349
struct DaemonClient {
296
350
let mgmt : Daemon_DaemonAsyncClient
297
351
let sync : Synchronization_SynchronizationAsyncClient
352
+ let prompt : Prompting_PromptingAsyncClient
298
353
}
299
354
300
355
public enum DaemonState {
@@ -336,6 +391,8 @@ public enum DaemonError: Error {
336
391
case connectionFailure( Error )
337
392
case terminatedUnexpectedly
338
393
case grpcFailure( Error )
394
+ case invalidGrpcResponse( String )
395
+ case unexpectedStreamClosure
339
396
340
397
var description : String {
341
398
switch self {
@@ -349,6 +406,10 @@ public enum DaemonError: Error {
349
406
" The daemon must be started first "
350
407
case let . grpcFailure( error) :
351
408
" Failed to communicate with daemon: \( error) "
409
+ case let . invalidGrpcResponse( response) :
410
+ " Invalid gRPC response: \( response) "
411
+ case . unexpectedStreamClosure:
412
+ " Unexpected stream closure "
352
413
}
353
414
}
354
415
0 commit comments