Repository: ollama/ollama
Pull request: cmd: clawdbot fixes


Changed files:
filename: cmd/config/clawdbot.go
patch: @@ -1,17 +1,22 @@
 package config
 
 import (
+	"bytes"
 	"encoding/json"
 	"fmt"
+	"io"
 	"os"
 	"os/exec"
 	"path/filepath"
+	"strings"
 )
 
 type Clawdbot struct{}
 
 func (c *Clawdbot) String() string { return "Clawdbot" }
 
+const ansiGreen = "\033[32m"
+
 func (c *Clawdbot) Run(model string) error {
 	if _, err := exec.LookPath("clawdbot"); err != nil {
 		return fmt.Errorf("clawdbot is not installed, install from https://docs.clawd.bot")
@@ -27,9 +32,18 @@ func (c *Clawdbot) Run(model string) error {
 
 	cmd := exec.Command("clawdbot", "gateway")
 	cmd.Stdin = os.Stdin
-	cmd.Stdout = os.Stdout
-	cmd.Stderr = os.Stderr
-	return cmd.Run()
+
+	// Capture output to detect "already running" message
+	var outputBuf bytes.Buffer
+	cmd.Stdout = io.MultiWriter(os.Stdout, &outputBuf)
+	cmd.Stderr = io.MultiWriter(os.Stderr, &outputBuf)
+
+	err := cmd.Run()
+	if err != nil && strings.Contains(outputBuf.String(), "Gateway already running") {
+		fmt.Fprintf(os.Stderr, "%sClawdbot has been configured with Ollama. Gateway is already running.%s\n", ansiGreen, ansiReset)
+		return nil
+	}
+	return err
 }
 
 func (c *Clawdbot) Paths() []string {
@@ -77,6 +91,8 @@ func (c *Clawdbot) Edit(models []string) error {
 	}
 
 	ollama["baseUrl"] = "http://127.0.0.1:11434/v1"
+	// needed to register provider
+	ollama["apiKey"] = "ollama-local"
 	// TODO(parthsareen): potentially move to responses
 	ollama["api"] = "openai-completions"
 
@@ -94,9 +110,19 @@ func (c *Clawdbot) Edit(models []string) error {
 	var newModels []any
 	for _, model := range models {
 		entry := map[string]any{
-			"id":   model,
-			"name": model,
-			"cost": map[string]any{"input": 0, "output": 0},
+			"id":        model,
+			"name":      model,
+			"reasoning": false,
+			"input":     []any{"text"},
+			"cost": map[string]any{
+				"input":      0,
+				"output":     0,
+				"cacheRead":  0,
+				"cacheWrite": 0,
+			},
+			// TODO(parthsareen): get these values from API
+			"contextWindow": 131072,
+			"maxTokens":     16384,
 		}
 		// Merge existing fields (user customizations)
 		if existing, ok := existingByID[model]; ok {

filename: cmd/config/clawdbot_test.go
patch: @@ -340,6 +340,47 @@ func TestClawdbotModelsEdgeCases(t *testing.T) {
 	})
 }
 
+func TestClawdbotEditSchemaFields(t *testing.T) {
+	c := &Clawdbot{}
+	tmpDir := t.TempDir()
+	setTestHome(t, tmpDir)
+	configPath := filepath.Join(tmpDir, ".clawdbot", "clawdbot.json")
+
+	if err := c.Edit([]string{"llama3.2"}); err != nil {
+		t.Fatal(err)
+	}
+
+	data, _ := os.ReadFile(configPath)
+	var cfg map[string]any
+	json.Unmarshal(data, &cfg)
+	models := cfg["models"].(map[string]any)
+	providers := models["providers"].(map[string]any)
+	ollama := providers["ollama"].(map[string]any)
+	modelList := ollama["models"].([]any)
+	entry := modelList[0].(map[string]any)
+
+	// Verify required schema fields
+	if entry["reasoning"] != false {
+		t.Error("reasoning should be false")
+	}
+	if entry["input"] == nil {
+		t.Error("input should be set")
+	}
+	if entry["contextWindow"] == nil {
+		t.Error("contextWindow should be set")
+	}
+	if entry["maxTokens"] == nil {
+		t.Error("maxTokens should be set")
+	}
+	cost := entry["cost"].(map[string]any)
+	if cost["cacheRead"] == nil {
+		t.Error("cost.cacheRead should be set")
+	}
+	if cost["cacheWrite"] == nil {
+		t.Error("cost.cacheWrite should be set")
+	}
+}
+
 func TestClawdbotEditModelNames(t *testing.T) {
 	c := &Clawdbot{}
 	tmpDir := t.TempDir()