diff --git a/commands/status.go b/commands/status.go index b121a97a..cb6fc39a 100644 --- a/commands/status.go +++ b/commands/status.go @@ -2,9 +2,9 @@ package commands import ( "fmt" - - "github.com/docker/pinata/common/cmd/docker-model/desktop" + "github.com/docker/model-cli/desktop" "github.com/spf13/cobra" + "os" ) func newStatusCmd() *cobra.Command { @@ -16,11 +16,17 @@ func newStatusCmd() *cobra.Command { if err != nil { return fmt.Errorf("Failed to create Docker client: %v\n", err) } - status, err := client.Status() - if err != nil { + status := client.Status() + if status.Error != nil { return fmt.Errorf("Failed to get Docker Model Runner status: %v\n", err) } - cmd.Println(status) + if status.Running { + cmd.Println("Docker Model Runner is running") + } else { + cmd.Println("Docker Model Runner is not running") + os.Exit(1) + } + return nil }, } diff --git a/desktop/desktop.go b/desktop/desktop.go index 69e1bf2e..d0344fdd 100644 --- a/desktop/desktop.go +++ b/desktop/desktop.go @@ -48,17 +48,29 @@ func New() (*Client, error) { return &Client{dockerClient}, nil } -func (c *Client) Status() (string, error) { +type Status struct { + Running bool `json:"running"` + Error error `json:"error"` +} + +func (c *Client) Status() Status { // TODO: Query "/". resp, err := c.dockerClient.HTTPClient().Get(url(inference.ModelsPrefix)) if err != nil { - return "", err + return Status{ + Running: false, + Error: err, + } } defer resp.Body.Close() if resp.StatusCode == http.StatusOK { - return "Docker Model Runner is running", nil + return Status{ + Running: true, + } + } + return Status{ + Running: false, } - return "Docker Model Runner is not running", nil } func (c *Client) Pull(model string) (string, error) { diff --git a/go.mod b/go.mod index bff0fcdd..ebca2715 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.23.7 require ( github.com/docker/cli v28.0.1+incompatible github.com/docker/docker v28.0.1+incompatible + github.com/docker/go-units v0.5.0 github.com/docker/pinata v0.0.1-0.20250317110157-7302b389632a github.com/olekukonko/tablewriter v0.0.5 github.com/pkg/errors v0.9.1 @@ -26,7 +27,6 @@ require ( github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect - github.com/docker/go-units v0.5.0 // indirect github.com/docker/model-distribution v0.0.0-20250306122437-2530363c51c5 // indirect github.com/fatih/color v1.17.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect diff --git a/vendor/github.com/docker/pinata/common/cmd/docker-model/desktop/api.go b/vendor/github.com/docker/pinata/common/cmd/docker-model/desktop/api.go deleted file mode 100644 index c8f33558..00000000 --- a/vendor/github.com/docker/pinata/common/cmd/docker-model/desktop/api.go +++ /dev/null @@ -1,27 +0,0 @@ -package desktop - -type OpenAIChatMessage struct { - Role string `json:"role"` - Content string `json:"content"` -} - -type OpenAIChatRequest struct { - Model string `json:"model"` - Messages []OpenAIChatMessage `json:"messages"` - Stream bool `json:"stream"` -} - -type OpenAIChatResponse struct { - ID string `json:"id"` - Object string `json:"object"` - Created int64 `json:"created"` - Model string `json:"model"` - Choices []struct { - Delta struct { - Content string `json:"content"` - Role string `json:"role,omitempty"` - } `json:"delta"` - Index int `json:"index"` - FinishReason string `json:"finish_reason"` - } `json:"choices"` -} diff --git a/vendor/github.com/docker/pinata/common/cmd/docker-model/desktop/desktop.go b/vendor/github.com/docker/pinata/common/cmd/docker-model/desktop/desktop.go deleted file mode 100644 index b39e538f..00000000 --- a/vendor/github.com/docker/pinata/common/cmd/docker-model/desktop/desktop.go +++ /dev/null @@ -1,196 +0,0 @@ -package desktop - -import ( - "bufio" - "bytes" - "encoding/json" - "fmt" - "io" - "net/http" - "strings" - - "github.com/docker/docker/client" - "github.com/docker/pinata/common/pkg/engine" - "github.com/docker/pinata/common/pkg/inference" - "github.com/docker/pinata/common/pkg/inference/models" - "github.com/docker/pinata/common/pkg/paths" -) - -func init() { - paths.Init(paths.OnHost) -} - -type Client struct { - dockerClient *client.Client -} - -func New() (*Client, error) { - dockerClient, err := client.NewClientWithOpts( - // TODO: Make sure it works while running in Windows containers mode. - client.WithHost(paths.HostServiceSockets().DockerHost(engine.Linux)), - ) - if err != nil { - return nil, err - } - return &Client{dockerClient}, nil -} - -func (c *Client) Status() (string, error) { - // TODO: Query "/". - resp, err := c.dockerClient.HTTPClient().Get(url(inference.ModelsPrefix)) - if err != nil { - return "", err - } - defer resp.Body.Close() - if resp.StatusCode == http.StatusOK { - return "Docker Model Runner is running", nil - } - return "Docker Model Runner is not running", nil -} - -func (c *Client) Pull(model string) (string, error) { - jsonData, err := json.Marshal(models.ModelCreateRequest{From: model}) - if err != nil { - return "", fmt.Errorf("error marshaling request: %w", err) - } - - resp, err := c.dockerClient.HTTPClient().Post( - url(inference.ModelsPrefix+"/create"), - "application/json", - bytes.NewReader(jsonData), - ) - if err != nil { - return "", fmt.Errorf("error querying %s: %w", inference.ModelsPrefix+"/create", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - body, _ := io.ReadAll(resp.Body) - return "", fmt.Errorf("pulling %s failed with status %s: %s", model, resp.Status, string(body)) - } - - scanner := bufio.NewScanner(resp.Body) - for scanner.Scan() { - progressLine := scanner.Text() - if progressLine != "" { - fmt.Print("\r\033[K", progressLine) - } - } - - fmt.Println() - - return fmt.Sprintf("Model %s pulled successfully", model), nil -} - -func (c *Client) List() (string, error) { - resp, err := c.dockerClient.HTTPClient().Get(url(inference.InferencePrefix + "/v1/models")) - if err != nil { - return "", err - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("failed to list models: %s", resp.Status) - } - body, err := io.ReadAll(resp.Body) - if err != nil { - return "", fmt.Errorf("failed to read response body: %w", err) - } - return string(body), nil -} - -func (c *Client) Chat(model, prompt string) error { - reqBody := OpenAIChatRequest{ - Model: model, - Messages: []OpenAIChatMessage{ - { - Role: "user", - Content: prompt, - }, - }, - Stream: true, - } - - jsonData, err := json.Marshal(reqBody) - if err != nil { - return fmt.Errorf("error marshaling request: %w", err) - } - - resp, err := c.dockerClient.HTTPClient().Post( - url(inference.InferencePrefix+"/v1/chat/completions"), - "application/json", - bytes.NewReader(jsonData), - ) - if err != nil { - return fmt.Errorf("error querying %s: %w", inference.InferencePrefix+"/v1/chat/completions", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - body, _ := io.ReadAll(resp.Body) - return fmt.Errorf("error response: status=%d body=%s", resp.StatusCode, body) - } - - scanner := bufio.NewScanner(resp.Body) - for scanner.Scan() { - line := scanner.Text() - if line == "" { - continue - } - - if !strings.HasPrefix(line, "data: ") { - continue - } - - data := strings.TrimPrefix(line, "data: ") - - if data == "[DONE]" { - break - } - - var streamResp OpenAIChatResponse - if err := json.Unmarshal([]byte(data), &streamResp); err != nil { - return fmt.Errorf("error parsing stream response: %w", err) - } - - if len(streamResp.Choices) > 0 && streamResp.Choices[0].Delta.Content != "" { - chunk := streamResp.Choices[0].Delta.Content - fmt.Print(chunk) - } - } - - if err := scanner.Err(); err != nil { - return fmt.Errorf("error reading response stream: %w", err) - } - - return nil -} - -func (c *Client) Remove(model string) (string, error) { - req, err := http.NewRequest(http.MethodDelete, url(inference.ModelsPrefix+"/"+model), nil) - if err != nil { - return "", fmt.Errorf("error creating request: %w", err) - } - - resp, err := c.dockerClient.HTTPClient().Do(req) - if err != nil { - return "", fmt.Errorf("error querying %s: %w", inference.ModelsPrefix+"/"+model, err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { // from common/pkg/inference/models/manager.go - var bodyStr string - body, err := io.ReadAll(resp.Body) - if err != nil { - bodyStr = fmt.Sprintf("(failed to read response body: %v)", err) - } else { - bodyStr = string(body) - } - return "", fmt.Errorf("removing %s failed with status %s: %s", model, resp.Status, bodyStr) - } - - return fmt.Sprintf("Model %s removed successfully", model), nil -} - -func url(path string) string { - return fmt.Sprintf("http://localhost" + inference.ExperimentalEndpointsPrefix + path) -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 341d9160..e49b38ee 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -130,7 +130,6 @@ github.com/docker/model-distribution/pkg/store github.com/docker/model-distribution/pkg/types # github.com/docker/pinata v0.0.1-0.20250317110157-7302b389632a ## explicit; go 1.23.7 -github.com/docker/pinata/common/cmd/docker-model/desktop github.com/docker/pinata/common/pkg/engine github.com/docker/pinata/common/pkg/inference github.com/docker/pinata/common/pkg/inference/models