Skip to content

Commit 37b5708

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

File tree

6 files changed

+882
-17
lines changed

6 files changed

+882
-17
lines changed

Coder-Desktop/VPNLib/FileSync/FileSyncDaemon.swift

Lines changed: 62 additions & 1 deletion
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

Lines changed: 24 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy