diff --git a/server/pkg/instance.go b/server/pkg/instance.go index 1f26ef4..23179e1 100644 --- a/server/pkg/instance.go +++ b/server/pkg/instance.go @@ -1,12 +1,52 @@ package llamactl import ( + "context" + "io" + "os/exec" + "sync" + "github.com/google/uuid" ) type Instance struct { ID uuid.UUID - Port int - Status string + Args []string Options *InstanceOptions + + // Status + Running bool + + // Output channels + StdOut chan string // Channel for sending output messages + StdErr chan string // Channel for sending error messages + + // internal + cmd *exec.Cmd // Command to run the instance + ctx context.Context // Context for managing the instance lifecycle + cancel context.CancelFunc // Function to cancel the context + stdout io.ReadCloser // Standard output stream + stderr io.ReadCloser // Standard error stream + mu sync.Mutex // Mutex for synchronizing access to the instance +} + +func NewInstance(id uuid.UUID, options *InstanceOptions) *Instance { + return &Instance{ + ID: id, + Args: options.BuildCommandArgs(), + Options: options, + + Running: false, + + StdOut: make(chan string, 100), + StdErr: make(chan string, 100), + } +} + +func (i *Instance) Start() *exec.Cmd { + args := i.Options.BuildCommandArgs() + cmd := exec.Command("llama-server", args...) + + cmd.Start() + return cmd } diff --git a/server/pkg/manager.go b/server/pkg/manager.go index bf5fca1..22451de 100644 --- a/server/pkg/manager.go +++ b/server/pkg/manager.go @@ -46,15 +46,30 @@ func (im *instanceManager) ListInstances() ([]*Instance, error) { // CreateInstance creates a new instance with the given options and returns it. // The instance is initially in a "stopped" state. func (im *instanceManager) CreateInstance(options *InstanceOptions) (*Instance, error) { - id := uuid.New() - instance := &Instance{ - ID: id, - Port: 8080, // Default port, can be changed later - Status: "stopped", // Initial status - Options: options, + if options == nil { + return nil, fmt.Errorf("instance options cannot be nil") } - im.instances[id] = instance + + // Generate a unique ID for the new instance + id := uuid.New() + for im.instances[id] != nil { + id = uuid.New() // Ensure unique ID + } + + // Assign a port if not specified + if options.Port == 0 { + port, err := im.getNextAvailablePort() + if err != nil { + return nil, fmt.Errorf("failed to get next available port: %w", err) + } + options.Port = port + } + + instance := NewInstance(id, options) + im.instances[instance.ID] = instance + return instance, nil + } // GetInstance retrieves an instance by its ID. @@ -83,8 +98,8 @@ func (im *instanceManager) DeleteInstance(id uuid.UUID) error { return fmt.Errorf("instance with ID %s not found", id) } - if im.instances[id].Status != "stopped" { - return fmt.Errorf("cannot delete a running instance, stop it first") + if im.instances[id].Running { + return fmt.Errorf("instance with ID %s is still running, stop it before deleting", id) } delete(im.instances, id) @@ -98,13 +113,11 @@ func (im *instanceManager) StartInstance(id uuid.UUID) (*Instance, error) { if !exists { return nil, fmt.Errorf("instance with ID %s not found", id) } - if instance.Status == "running" { + if instance.Running { return instance, fmt.Errorf("instance with ID %s is already running", id) } //TODO: - - instance.Status = "running" return instance, nil } @@ -114,12 +127,11 @@ func (im *instanceManager) StopInstance(id uuid.UUID) (*Instance, error) { if !exists { return nil, fmt.Errorf("instance with ID %s not found", id) } - if instance.Status == "stopped" { + if !instance.Running { return instance, fmt.Errorf("instance with ID %s is already stopped", id) } // TODO: - instance.Status = "stopped" return instance, nil } diff --git a/server/pkg/options.go b/server/pkg/options.go index b7284ec..c0bd965 100644 --- a/server/pkg/options.go +++ b/server/pkg/options.go @@ -8,10 +8,35 @@ import ( ) type InstanceOptions struct { - Name string `json:"name,omitempty"` + Name string `json:"name,omitempty"` // Display name + + // Auto restart + AutoRestart bool `json:"auto_restart,omitempty"` + MaxRestarts int `json:"max_restarts,omitempty"` + RestartDelay int `json:"restart_delay,omitempty"` // in seconds + *LlamaServerOptions } +// UnmarshalJSON implements custom JSON unmarshaling with default values +func (o *InstanceOptions) UnmarshalJSON(data []byte) error { + // Set defaults first + o.AutoRestart = true + o.MaxRestarts = 3 + o.RestartDelay = 5 + + // Create a temporary struct to avoid recursion + type tempInstanceOptions InstanceOptions + temp := (*tempInstanceOptions)(o) + + // Unmarshal into the temporary struct + if err := json.Unmarshal(data, temp); err != nil { + return err + } + + return nil +} + type LlamaServerOptions struct { // Common params VerbosePrompt bool `json:"verbose_prompt,omitempty"`