Skip to content

Commit 37b5708

Browse files
committed
chore: add mutagen prompting gRPC
1 parent f0a7bbd commit 37b5708

6 files changed

+882
-17
lines changed

Coder-Desktop/VPNLib/FileSync/FileSyncDaemon.swift

+62-1
Original file line numberDiff line numberDiff line change
@@ -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+
295349
struct DaemonClient {
296350
let mgmt: Daemon_DaemonAsyncClient
297351
let sync: Synchronization_SynchronizationAsyncClient
352+
let prompt: Prompting_PromptingAsyncClient
298353
}
299354

300355
public 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

Coder-Desktop/VPNLib/FileSync/MutagenConvert.swift

+24
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,27 @@ func accumulateErrors(from state: Synchronization_State) -> [FileSyncError] {
5757
func humanReadableBytes(_ bytes: UInt64) -> String {
5858
ByteCountFormatter().string(fromByteCount: Int64(bytes))
5959
}
60+
61+
62+
extension Prompting_HostResponse {
63+
func ensureValid(first: Bool, allowPrompts: Bool) throws(DaemonError) {
64+
if first {
65+
if identifier.isEmpty {
66+
throw .invalidGrpcResponse("empty prompter identifier")
67+
}
68+
if isPrompt {
69+
throw .invalidGrpcResponse("unexpected message type specification")
70+
}
71+
if !message.isEmpty {
72+
throw .invalidGrpcResponse("unexpected message")
73+
}
74+
} else {
75+
if !identifier.isEmpty {
76+
throw .invalidGrpcResponse("unexpected prompter identifier")
77+
}
78+
if isPrompt, !allowPrompts {
79+
throw .invalidGrpcResponse("disallowed prompt message type")
80+
}
81+
}
82+
}
83+
}

0 commit comments

Comments
 (0)