github.com/mutagen-io/mutagen@v0.18.0-rc1/pkg/service/prompting/server.go (about) 1 package prompting 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 8 "github.com/mutagen-io/mutagen/pkg/identifier" 9 "github.com/mutagen-io/mutagen/pkg/prompting" 10 ) 11 12 // Server provides an implementation of the Prompting service. 13 type Server struct { 14 // UnimplementedPromptingServer is the required base implementation. 15 UnimplementedPromptingServer 16 } 17 18 // NewServer creates a new prompt server. 19 func NewServer() *Server { 20 return &Server{} 21 } 22 23 // Host performs prompt hosting. 24 func (s *Server) Host(stream Prompting_HostServer) error { 25 // Receive and validate the initial request. 26 request, err := stream.Recv() 27 if err != nil { 28 return fmt.Errorf("unable to receive initial request: %w", err) 29 } else if err = request.ensureValid(hostRequestModeInitial); err != nil { 30 return fmt.Errorf("received invalid initial request: %w", err) 31 } 32 33 // Create a unique identifier for the prompter. 34 identifier, err := identifier.New(identifier.PrefixPrompter) 35 if err != nil { 36 return fmt.Errorf("unable to generate prompter identifier: %w", err) 37 } 38 39 // Send the initial response. 40 if err := stream.Send(&HostResponse{Identifier: identifier}); err != nil { 41 return fmt.Errorf("unable to send initial response: %w", err) 42 } 43 44 // Extract the request context. 45 ctx := stream.Context() 46 47 // Wrap the stream to create a prompter. 48 prompter := &streamPrompter{ 49 allowPrompts: request.AllowPrompts, 50 stream: stream, 51 } 52 53 // Register the prompter. 54 if err := prompting.RegisterPrompterWithIdentifier(identifier, prompter); err != nil { 55 return fmt.Errorf("unable to register prompter: %w", err) 56 } 57 58 // Wait for the request or connection to be terminated. 59 <-ctx.Done() 60 61 // Unregister the promper. 62 prompting.UnregisterPrompter(identifier) 63 64 // Success. 65 return nil 66 } 67 68 // asyncPromptResponse provides a structure for returning prompt results 69 // asynchronously, allowing prompting to be cancelled. 70 type asyncPromptResponse struct { 71 // response is the response returned by the prompter. 72 response string 73 // error is the error returned by the prompter. 74 error error 75 } 76 77 // Prompt performs prompting against registered prompters. 78 func (s *Server) Prompt(ctx context.Context, request *PromptRequest) (*PromptResponse, error) { 79 // Validate the request. 80 if err := request.ensureValid(); err != nil { 81 return nil, fmt.Errorf("invalid prompt request: %w", err) 82 } 83 84 // Perform prompting from the global registry asynchronously. 85 // TODO: Should we build cancellation into the Prompter interface itself? 86 asyncResponse := make(chan asyncPromptResponse, 1) 87 go func() { 88 response, err := prompting.Prompt(request.Prompter, request.Prompt) 89 asyncResponse <- asyncPromptResponse{response, err} 90 }() 91 92 // Wait for a response or cancellation. 93 select { 94 case <-ctx.Done(): 95 return nil, errors.New("prompting cancelled while waiting for response") 96 case r := <-asyncResponse: 97 if r.error != nil { 98 return nil, fmt.Errorf("unable to prompt: %w", r.error) 99 } else { 100 return &PromptResponse{Response: r.response}, nil 101 } 102 } 103 }