syntax = "proto3";

option go_package = "github.com/modal-labs/modal/go/proto";

import "google/protobuf/empty.proto";
import "modal_proto/api.proto";

package modal.task_command_router;

enum TaskExecStderrConfig {
  // The output will be discarded.
  TASK_EXEC_STDERR_CONFIG_DEVNULL = 0;
  // The output will be streamed to the client.
  TASK_EXEC_STDERR_CONFIG_PIPE = 1;
  // A special value that can be used to indicate that the stderr stream should
  // be merged with the stdout stream.
  TASK_EXEC_STDERR_CONFIG_STDOUT = 2;
}

enum TaskExecStdioFileDescriptor {
  // Read from stdout.
  TASK_EXEC_STDIO_FILE_DESCRIPTOR_STDOUT = 0;
  // Read from stderr.
  TASK_EXEC_STDIO_FILE_DESCRIPTOR_STDERR = 1;
}

enum TaskExecStdoutConfig {
  // The output will be discarded.
  TASK_EXEC_STDOUT_CONFIG_DEVNULL = 0;
  // The output will be streamed to the client.
  TASK_EXEC_STDOUT_CONFIG_PIPE = 1;
}

message TaskExecPollRequest {
  // The ID of the task running the exec'd command.
  string task_id = 1;
  // The execution ID of the command to wait on.
  string exec_id = 2;
}

// The response to a TaskExecPollRequest. If the exec'd command has not
// completed, exit_status will be unset.
message TaskExecPollResponse {
  oneof exit_status {
    // The exit code of the command.
    int32 code = 1;
    // The signal that terminated the command.
    int32 signal = 2;
  }
  // TODO(saltzm): Give a way for the user to distinguish between normal exit
  // and termination by Modal (due to task timeout, exec exceeded deadline, etc.)
}

message TaskExecStartRequest {
  // The ID of the task to execute the command in.
  string task_id = 1;
  // Execution ID. This ID will be used to identify the execution for other
  // requests and ensure exec commands are idempotent.
  string exec_id = 2;
  // Command arguments to execute.
  repeated string command_args= 3;
  // Configures how the stdout of the command will be handled.
  TaskExecStdoutConfig stdout_config = 4;
  // Configures how the stderr of the command will be handled.
  TaskExecStderrConfig stderr_config = 5;
  // Timeout in seconds for the exec'd command to exit. If the command does not
  // exit within this duration, the command will be killed. This is NOT the
  // timeout for the ExecStartRequest RPC to complete.
  optional uint32 timeout_secs = 6;
  // Working directory for the command.
  optional string workdir = 7;
  // Secret IDs to mount into the task.
  repeated string secret_ids = 8;
  // PTY info for the command.
  optional modal.client.PTYInfo pty_info = 9;
  // Enable debugging capabilities on the container runtime. Used only for
  // internal debugging.
  bool runtime_debug = 10;
}

message TaskExecStartResponse { }

message TaskExecStdinWriteRequest {
  // The ID of the task running the exec'd command.
  string task_id = 1;
  // The execution ID of the command to write to.
  string exec_id = 2;
  // The offset to start writing to. This is used to resume writing from the
  // last write position if the connection is closed and reopened.
  uint64 offset = 3;
  bytes data = 4;
  // If true, close the stdin stream after writing any provided data.
  // This signals EOF to the exec'd process.
  bool eof = 5;
}

message TaskExecStdinWriteResponse { }

message TaskExecStdioReadRequest {
  // The ID of the task running the exec'd command.
  string task_id = 1;
  // The execution ID of the command to read from.
  string exec_id = 2;
  // The offset to start reading from. This is used to resume reading from the
  // last read position if the connection is closed and reopened.
  uint64 offset = 3;
  // Which file descriptor to read from.
  TaskExecStdioFileDescriptor file_descriptor = 4;
}

message TaskExecStdioReadResponse {
  // The data read from the file descriptor.
  bytes data = 1;
}

message TaskExecWaitRequest {
  // The ID of the task running the exec'd command.
  string task_id = 1;
  // The execution ID of the command to wait on. 
  string exec_id = 2;
}

message TaskExecWaitResponse {
  oneof exit_status {
    // The exit code of the command.
    int32 code = 1;
    // The signal that terminated the command.
    int32 signal = 2;
  }
  // TODO(saltzm): Give a way for the user to distinguish between normal exit
  // and termination by Modal (due to task timeout, exec exceeded deadline, etc.)
}

message TaskMountDirectoryRequest {
  string task_id = 1;
  bytes path = 2;
  string image_id = 3;
}

message TaskSnapshotDirectoryRequest {
  string task_id = 1;
  bytes path = 2;
}

message TaskSnapshotDirectoryResponse {
  string image_id = 1;
}

service TaskCommandRouter {
  // Poll for the exit status of an exec'd command.
  rpc TaskExecPoll(TaskExecPollRequest) returns (TaskExecPollResponse);
  // Execute a command in the task.
  rpc TaskExecStart(TaskExecStartRequest) returns (TaskExecStartResponse);
 // Write to the stdin stream of an exec'd command.
  rpc TaskExecStdinWrite(TaskExecStdinWriteRequest) returns (TaskExecStdinWriteResponse);
  // Get a stream of output from the stdout or stderr stream of an exec'd command.
  rpc TaskExecStdioRead(TaskExecStdioReadRequest) returns (stream TaskExecStdioReadResponse);
  // Wait for an exec'd command to exit and return the exit code.
  rpc TaskExecWait(TaskExecWaitRequest) returns (TaskExecWaitResponse);
  // Mount an image at a directory in the container.
  rpc TaskMountDirectory(TaskMountDirectoryRequest) returns (google.protobuf.Empty);
  // Snapshot a directory with a mounted image, including any local changes, into a new image.
  rpc TaskSnapshotDirectory(TaskSnapshotDirectoryRequest) returns (TaskSnapshotDirectoryResponse);
}
