launch: add DeepSeek Harness integration (#17733)

This commit is contained in:
Parth Sareen 2026-08-13 15:19:02 -07:00 • committed by GitHub
parent 7ce88bd686
commit 39df91c982
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 1139 additions and 3 deletions

View file

@ -0,0 +1,520 @@
package launch
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"slices"
"strings"
"github.com/ollama/ollama/cmd/config"
"github.com/ollama/ollama/cmd/internal/fileutil"
"github.com/ollama/ollama/envconfig"
"github.com/ollama/ollama/types/model"
"gopkg.in/yaml.v3"
)
const (
deepSeekHarnessIntegrationName = "dsh"
deepSeekHarnessNpmPackage = "@deepseek-ai/dsh@latest"
deepSeekHarnessProvider = "ollama"
deepSeekHarnessAPIKeyEnv = "OLLAMA_LAUNCH_DSH_API_KEY"
deepSeekHarnessWebSettings = "web-search-deepseek"
)
var (
deepSeekHarnessLookPath = exec.LookPath
deepSeekHarnessCommand = exec.Command
deepSeekHarnessGOOS = runtime.GOOS
)
// DeepSeekHarness is the Ollama-managed DeepSeek Harness integration.
// It redirects only the settings provider for this invocation to an
// Ollama-owned document. The user's normal DSH_HOME, profiles, sessions,
// credentials, and patch layers remain available and untouched.
type DeepSeekHarness struct{}
func (d *DeepSeekHarness) String() string { return "DeepSeek Harness" }
func (d *DeepSeekHarness) Run(_ string, _ []LaunchModel, args []string) error {
if err := validateDeepSeekHarnessArgs(args); err != nil {
return err
}
bin, err := deepSeekHarnessLookPath("dsh")
if err != nil {
return fmt.Errorf("dsh is not installed: %w", err)
}
patchPath, err := deepSeekHarnessPatchPath()
if err != nil {
return err
}
cmd, err := deepSeekHarnessExecutableCommand(bin, deepSeekHarnessLaunchArgs(patchPath, args))
if err != nil {
return err
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = deepSeekHarnessLaunchEnv(os.Environ())
return cmd.Run()
}
func deepSeekHarnessLaunchArgs(patchPath string, args []string) []string {
launchArgs := []string{"web", "--patch", patchPath}
return append(launchArgs, args...)
}
func validateDeepSeekHarnessArgs(args []string) error {
for _, arg := range args {
if arg == "--patch" || strings.HasPrefix(arg, "--patch=") {
return fmt.Errorf("conflicting extra argument %q: ollama launch dsh manages --patch", arg)
}
}
return nil
}
func deepSeekHarnessLaunchEnv(env []string) []string {
return deepSeekHarnessUpsertEnv(env, deepSeekHarnessAPIKeyEnv, "ollama")
}
func deepSeekHarnessUpsertEnv(env []string, key, value string) []string {
prefix := key + "="
out := make([]string, 0, len(env)+1)
for _, entry := range env {
if strings.HasPrefix(entry, prefix) {
continue
}
out = append(out, entry)
}
return append(out, prefix+value)
}
func ensureDeepSeekHarnessInstalled() (string, error) {
if path, err := deepSeekHarnessLookPath("dsh"); err == nil {
return path, nil
}
npm, err := deepSeekHarnessLookPath("npm")
if err != nil {
return "", fmt.Errorf("dsh is not installed and npm (Node.js) is required\n\nInstall Node.js first:\n https://nodejs.org/\n\nThen re-run:\n ollama launch dsh")
}
ok, err := ConfirmPrompt("DeepSeek Harness is not installed. Install with npm?")
if err != nil {
return "", err
}
if !ok {
return "", fmt.Errorf("deepseek harness installation cancelled")
}
fmt.Fprintln(os.Stderr, "\nInstalling DeepSeek Harness...")
cmd, err := deepSeekHarnessNpmCommand(npm, []string{"install", "-g", deepSeekHarnessNpmPackage})
if err != nil {
return "", err
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return "", fmt.Errorf("failed to install deepseek harness: %w", err)
}
path, err := deepSeekHarnessLookPath("dsh")
if err != nil {
return "", fmt.Errorf("deepseek harness was installed but dsh was not found on PATH\n\nYou may need to restart your shell")
}
fmt.Fprintf(os.Stderr, "%sDeepSeek Harness installed successfully%s\n\n", ansiGreen, ansiReset)
return path, nil
}
func deepSeekHarnessExecutableCommand(bin string, args []string) (*exec.Cmd, error) {
return deepSeekHarnessNodeShimCommand(bin, []string{"node_modules", "@deepseek-ai", "dsh", "lib", "bin.js"}, args)
}
func deepSeekHarnessNpmCommand(bin string, args []string) (*exec.Cmd, error) {
return deepSeekHarnessNodeShimCommand(bin, []string{"node_modules", "npm", "bin", "npm-cli.js"}, args)
}
// Windows npm binaries are .cmd shims, which cannot be passed safely to
// CreateProcess with an argv. Invoke their JavaScript entrypoints with Node so
// passthrough arguments remain data rather than cmd.exe syntax.
func deepSeekHarnessNodeShimCommand(shim string, entrypointParts, args []string) (*exec.Cmd, error) {
if deepSeekHarnessGOOS != "windows" || !deepSeekHarnessIsCommandShim(shim) {
return deepSeekHarnessCommand(shim, args...), nil
}
node, err := deepSeekHarnessLookPath("node")
if err != nil {
return nil, fmt.Errorf("node is required to run %s on Windows: %w", filepath.Base(shim), err)
}
entrypoint := filepath.Join(append([]string{filepath.Dir(shim)}, entrypointParts...)...)
if _, err := os.Stat(entrypoint); err != nil {
return nil, fmt.Errorf("resolve Windows entrypoint for %s: %w", filepath.Base(shim), err)
}
return deepSeekHarnessCommand(node, append([]string{entrypoint}, args...)...), nil
}
func deepSeekHarnessIsCommandShim(path string) bool {
ext := strings.ToLower(filepath.Ext(path))
return ext == ".cmd" || ext == ".bat"
}
func (d *DeepSeekHarness) Paths() []string {
settingsPath, settingsErr := deepSeekHarnessSettingsPath()
patchPath, patchErr := deepSeekHarnessPatchPath()
if settingsErr != nil || patchErr != nil {
return nil
}
return []string{settingsPath, patchPath}
}
func (d *DeepSeekHarness) Configure(modelName string) error {
return d.ConfigureWithModels(modelName, []LaunchModel{fallbackLaunchModel(modelName)})
}
func (d *DeepSeekHarness) ConfigureWithModels(primary string, models []LaunchModel) error {
if strings.TrimSpace(primary) == "" {
return nil
}
if len(models) == 0 {
models = []LaunchModel{fallbackLaunchModel(primary)}
}
if selected, ok := findLaunchModel(models, primary); ok {
primary = selected.Name
}
settingsPath, err := deepSeekHarnessSettingsPath()
if err != nil {
return err
}
settings, err := readDeepSeekHarnessYAMLDocument(settingsPath)
if err != nil {
return fmt.Errorf("parse deepseek harness launch settings: %w", err)
}
if err := applyDeepSeekHarnessSettings(settings, primary, models, shouldManageOllamaWebSearch()); err != nil {
return err
}
settingsData, err := yaml.Marshal(settings)
if err != nil {
return err
}
if err := writeDeepSeekHarnessFile(settingsPath, settingsData); err != nil {
return err
}
patchPath, err := deepSeekHarnessPatchPath()
if err != nil {
return err
}
patchData, err := yaml.Marshal([]map[string]any{
{
"id": "settings",
"config": map[string]any{
"path": settingsPath,
},
},
})
if err != nil {
return err
}
return writeDeepSeekHarnessFile(patchPath, patchData)
}
func readDeepSeekHarnessYAML(path string) (map[string]any, error) {
data, err := os.ReadFile(path)
if err != nil {
if os.IsNotExist(err) {
return make(map[string]any), nil
}
return nil, err
}
settings := make(map[string]any)
if err := yaml.Unmarshal(data, &settings); err != nil {
return nil, err
}
if settings == nil {
settings = make(map[string]any)
}
return settings, nil
}
func readDeepSeekHarnessYAMLDocument(path string) (*yaml.Node, error) {
document := &yaml.Node{Kind: yaml.DocumentNode}
data, err := os.ReadFile(path)
if err != nil {
if !os.IsNotExist(err) {
return nil, err
}
document.Content = []*yaml.Node{{Kind: yaml.MappingNode, Tag: "!!map"}}
return document, nil
}
if err := yaml.Unmarshal(data, document); err != nil {
return nil, err
}
if len(document.Content) == 0 || document.Content[0].Kind == yaml.ScalarNode && document.Content[0].Tag == "!!null" {
document.Content = []*yaml.Node{{Kind: yaml.MappingNode, Tag: "!!map"}}
}
if document.Content[0].Kind != yaml.MappingNode {
return nil, fmt.Errorf("settings root must be a mapping")
}
return document, nil
}
func applyDeepSeekHarnessSettings(document *yaml.Node, primary string, models []LaunchModel, manageWebSearch bool) error {
settings := document.Content[0]
selected := deepSeekHarnessEnsureYAMLMapping(settings, "agent-default-model")
for key, value := range map[string]string{
"provider": deepSeekHarnessProvider,
"model": primary,
} {
if err := deepSeekHarnessSetYAMLValue(selected, key, value); err != nil {
return err
}
}
llm := deepSeekHarnessEnsureYAMLMapping(settings, "llm-pi-ai")
providers := deepSeekHarnessEnsureYAMLMapping(llm, "providers")
provider := deepSeekHarnessEnsureYAMLMapping(providers, deepSeekHarnessProvider)
for key, value := range map[string]any{
"displayName": "Ollama",
"apiKeyEnv": deepSeekHarnessAPIKeyEnv,
"api": "openai-completions",
"baseURL": deepSeekHarnessBaseURL(),
"models": deepSeekHarnessModelConfigs(primary, models),
} {
if err := deepSeekHarnessSetYAMLValue(provider, key, value); err != nil {
return err
}
}
if !manageWebSearch {
return nil
}
// Harness's bundled search provider appends /messages to this /v1 base and
// sends the Anthropic web_search server tool. This is separate from the main
// model provider above; Harness does not expose a configured way to send the
// native OpenAI Responses web_search tool.
web := deepSeekHarnessEnsureYAMLMapping(settings, deepSeekHarnessWebSettings)
for key, value := range map[string]string{
"apiKeyEnv": deepSeekHarnessAPIKeyEnv,
"baseURL": deepSeekHarnessBaseURL(),
"model": primary,
} {
if err := deepSeekHarnessSetYAMLValue(web, key, value); err != nil {
return err
}
}
return nil
}
func deepSeekHarnessEnsureYAMLMapping(mapping *yaml.Node, key string) *yaml.Node {
if value := deepSeekHarnessYAMLValue(mapping, key); value != nil && value.Kind == yaml.MappingNode {
return value
}
value := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}
deepSeekHarnessSetYAMLNode(mapping, key, value)
return value
}
func deepSeekHarnessSetYAMLValue(mapping *yaml.Node, key string, value any) error {
node := &yaml.Node{}
if err := node.Encode(value); err != nil {
return err
}
deepSeekHarnessSetYAMLNode(mapping, key, node)
return nil
}
func deepSeekHarnessSetYAMLNode(mapping *yaml.Node, key string, value *yaml.Node) {
for i := 0; i+1 < len(mapping.Content); i += 2 {
if mapping.Content[i].Value == key {
mapping.Content[i+1] = value
return
}
}
mapping.Content = append(mapping.Content,
&yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: key},
value,
)
}
func deepSeekHarnessYAMLValue(mapping *yaml.Node, key string) *yaml.Node {
for i := 0; i+1 < len(mapping.Content); i += 2 {
if mapping.Content[i].Value == key {
return mapping.Content[i+1]
}
}
return nil
}
func deepSeekHarnessModelConfigs(primary string, models []LaunchModel) []any {
ordered := append([]LaunchModel(nil), models...)
if selected, ok := findLaunchModel(ordered, primary); ok {
ordered = append([]LaunchModel{selected}, removeLaunchModel(ordered, primary)...)
} else {
ordered = append([]LaunchModel{fallbackLaunchModel(primary)}, ordered...)
}
configs := make([]any, 0, len(ordered))
seen := make(map[string]bool, len(ordered))
for _, item := range ordered {
if item.Name == "" || seen[item.Name] {
continue
}
seen[item.Name] = true
entry := map[string]any{
"id": item.Name,
"name": item.Name,
"input": []string{"text"},
}
if slices.Contains(item.Capabilities, model.CapabilityVision) {
entry["input"] = []string{"text", "image"}
}
if item.ContextLength > 0 {
entry["contextWindow"] = item.ContextLength
}
if item.MaxOutputTokens > 0 {
entry["maxTokens"] = item.MaxOutputTokens
}
configs = append(configs, entry)
}
return configs
}
func (d *DeepSeekHarness) CurrentModel() string {
settingsPath, err := deepSeekHarnessSettingsPath()
if err != nil {
return ""
}
if !deepSeekHarnessPatchHealthy(settingsPath) {
return ""
}
settings, err := readDeepSeekHarnessYAML(settingsPath)
if err != nil {
return ""
}
selected, _ := settings["agent-default-model"].(map[string]any)
if selected == nil || selected["provider"] != deepSeekHarnessProvider {
return ""
}
modelName, _ := selected["model"].(string)
if modelName == "" {
return ""
}
llm, _ := settings["llm-pi-ai"].(map[string]any)
providers, _ := llm["providers"].(map[string]any)
provider, _ := providers[deepSeekHarnessProvider].(map[string]any)
if !deepSeekHarnessProviderHealthy(provider, modelName) {
return ""
}
if !shouldManageOllamaWebSearch() {
return modelName
}
web, _ := settings[deepSeekHarnessWebSettings].(map[string]any)
if !deepSeekHarnessWebProviderHealthy(web, modelName) {
return ""
}
return modelName
}
func deepSeekHarnessPatchHealthy(settingsPath string) bool {
patchPath, err := deepSeekHarnessPatchPath()
if err != nil {
return false
}
data, err := os.ReadFile(patchPath)
if err != nil {
return false
}
var patches []struct {
ID string `yaml:"id"`
Config struct {
Path string `yaml:"path"`
} `yaml:"config"`
}
if err := yaml.Unmarshal(data, &patches); err != nil || len(patches) != 1 {
return false
}
return patches[0].ID == "settings" && patches[0].Config.Path == settingsPath
}
func deepSeekHarnessProviderHealthy(provider map[string]any, modelName string) bool {
if provider == nil || provider["api"] != "openai-completions" || provider["apiKeyEnv"] != deepSeekHarnessAPIKeyEnv {
return false
}
baseURL, _ := provider["baseURL"].(string)
if strings.TrimRight(baseURL, "/") != strings.TrimRight(deepSeekHarnessBaseURL(), "/") {
return false
}
models, _ := provider["models"].([]any)
for _, raw := range models {
entry, _ := raw.(map[string]any)
if entry["id"] == modelName {
return true
}
}
return false
}
func deepSeekHarnessWebProviderHealthy(web map[string]any, modelName string) bool {
if web == nil || web["apiKeyEnv"] != deepSeekHarnessAPIKeyEnv || web["model"] != modelName {
return false
}
baseURL, _ := web["baseURL"].(string)
return strings.TrimRight(baseURL, "/") == strings.TrimRight(deepSeekHarnessBaseURL(), "/")
}
func (d *DeepSeekHarness) Onboard() error {
return config.MarkIntegrationOnboarded(deepSeekHarnessIntegrationName)
}
func (d *DeepSeekHarness) RequiresInteractiveOnboarding() bool { return false }
func deepSeekHarnessBaseURL() string {
return strings.TrimRight(envconfig.ConnectableHost().String(), "/") + "/v1"
}
func deepSeekHarnessConfigDir() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(home, ".ollama", "launch", "dsh"), nil
}
func deepSeekHarnessSettingsPath() (string, error) {
dir, err := deepSeekHarnessConfigDir()
if err != nil {
return "", err
}
return filepath.Join(dir, "settings.yaml"), nil
}
func deepSeekHarnessPatchPath() (string, error) {
dir, err := deepSeekHarnessConfigDir()
if err != nil {
return "", err
}
return filepath.Join(dir, "ollama.cordis.yml"), nil
}
func writeDeepSeekHarnessFile(path string, data []byte) error {
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0o700); err != nil {
return err
}
if err := os.Chmod(dir, 0o700); err != nil {
return err
}
if err := fileutil.WriteWithBackup(path, data, deepSeekHarnessIntegrationName); err != nil {
return err
}
return os.Chmod(path, 0o600)
}

View file

@ -0,0 +1,461 @@
package launch
import (
"fmt"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"runtime"
"slices"
"strings"
"testing"
"github.com/ollama/ollama/types/model"
"gopkg.in/yaml.v3"
)
func TestDeepSeekHarnessRegistry(t *testing.T) {
spec, err := LookupIntegrationSpec("deepseek-harness")
if err != nil {
t.Fatal(err)
}
if spec.Name != deepSeekHarnessIntegrationName {
t.Fatalf("canonical name = %q, want %q", spec.Name, deepSeekHarnessIntegrationName)
}
if spec.Runner.String() != "DeepSeek Harness" {
t.Fatalf("display name = %q", spec.Runner.String())
}
if got := strings.Join(spec.Install.Command, " "); got != "npm install -g @deepseek-ai/dsh@latest" {
t.Fatalf("install command = %q", got)
}
}
func TestDeepSeekHarnessConfigurePreservesSettingsAndIsIdempotent(t *testing.T) {
home := t.TempDir()
setTestHome(t, home)
t.Setenv("OLLAMA_HOST", "http://127.0.0.1:12345")
settingsPath, err := deepSeekHarnessSettingsPath()
if err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(filepath.Dir(settingsPath), 0o700); err != nil {
t.Fatal(err)
}
existing := []byte("# keep-comment\ndefaults: &defaults\n mode: dark\ntheme: *defaults\nagent-default-model:\n # keep-reasoning-comment\n reasoningEffort: high\nllm-pi-ai:\n providers:\n custom:\n api: openai-completions\n baseURL: https://example.invalid/v1\n models:\n - id: custom-model\n ollama:\n # keep-retry-comment\n retryPolicy:\n maxAttempts: 2\nweb-search-deepseek:\n maxUses: 3\n")
if err := os.WriteFile(settingsPath, existing, 0o600); err != nil {
t.Fatal(err)
}
models := []LaunchModel{
{Name: "qwen3.5:latest", ContextLength: 262144, MaxOutputTokens: 32768, Capabilities: []model.Capability{model.CapabilityVision}},
{Name: "kimi-k2.6:cloud", ContextLength: 262144, MaxOutputTokens: 262144},
}
dsh := &DeepSeekHarness{}
if err := dsh.ConfigureWithModels("qwen3.5", models); err != nil {
t.Fatal(err)
}
firstSettings, err := os.ReadFile(settingsPath)
if err != nil {
t.Fatal(err)
}
patchPath, err := deepSeekHarnessPatchPath()
if err != nil {
t.Fatal(err)
}
firstPatch, err := os.ReadFile(patchPath)
if err != nil {
t.Fatal(err)
}
if err := dsh.ConfigureWithModels("qwen3.5", models); err != nil {
t.Fatal(err)
}
secondSettings, _ := os.ReadFile(settingsPath)
secondPatch, _ := os.ReadFile(patchPath)
if string(firstSettings) != string(secondSettings) || string(firstPatch) != string(secondPatch) {
t.Fatal("repeated configuration changed Ollama-managed files")
}
for _, preserved := range []string{"# keep-comment", "&defaults", "*defaults", "# keep-reasoning-comment", "# keep-retry-comment"} {
if !strings.Contains(string(firstSettings), preserved) {
t.Fatalf("settings did not preserve %q:\n%s", preserved, firstSettings)
}
}
var settings map[string]any
if err := yaml.Unmarshal(firstSettings, &settings); err != nil {
t.Fatal(err)
}
if theme, _ := settings["theme"].(map[string]any); theme["mode"] != "dark" {
t.Fatalf("unrelated settings were not preserved: %#v", settings["theme"])
}
selected, _ := settings["agent-default-model"].(map[string]any)
if selected["provider"] != deepSeekHarnessProvider || selected["model"] != "qwen3.5:latest" {
t.Fatalf("default model = %#v", selected)
}
if selected["reasoningEffort"] != "high" {
t.Fatalf("default model settings were not preserved: %#v", selected)
}
llm, _ := settings["llm-pi-ai"].(map[string]any)
providers, _ := llm["providers"].(map[string]any)
if providers["custom"] == nil {
t.Fatal("custom provider was removed")
}
provider, _ := providers[deepSeekHarnessProvider].(map[string]any)
if provider["baseURL"] != "http://127.0.0.1:12345/v1" || provider["apiKeyEnv"] != deepSeekHarnessAPIKeyEnv {
t.Fatalf("Ollama provider = %#v", provider)
}
retryPolicy, _ := provider["retryPolicy"].(map[string]any)
if retryPolicy["maxAttempts"] != 2 {
t.Fatalf("Ollama provider settings were not preserved: %#v", provider)
}
configuredModels, _ := provider["models"].([]any)
if len(configuredModels) != 2 {
t.Fatalf("configured models = %#v", configuredModels)
}
local, _ := configuredModels[0].(map[string]any)
if local["id"] != "qwen3.5:latest" || local["contextWindow"] != 262144 || local["maxTokens"] != 32768 {
t.Fatalf("local model = %#v", local)
}
if got, _ := local["input"].([]any); !slices.Equal(got, []any{"text", "image"}) {
t.Fatalf("local model input = %#v", local["input"])
}
cloud, _ := configuredModels[1].(map[string]any)
if cloud["id"] != "kimi-k2.6:cloud" || cloud["contextWindow"] != 262144 || cloud["maxTokens"] != 262144 {
t.Fatalf("cloud model = %#v", cloud)
}
web, _ := settings[deepSeekHarnessWebSettings].(map[string]any)
if web["baseURL"] != "http://127.0.0.1:12345/v1" || web["apiKeyEnv"] != deepSeekHarnessAPIKeyEnv || web["model"] != "qwen3.5:latest" {
t.Fatalf("Ollama web search provider = %#v", web)
}
if web["maxUses"] != 3 {
t.Fatalf("existing web search settings were not preserved: %#v", web)
}
var patches []map[string]any
if err := yaml.Unmarshal(firstPatch, &patches); err != nil {
t.Fatal(err)
}
if len(patches) != 1 || patches[0]["id"] != "settings" {
t.Fatalf("patches = %#v", patches)
}
patchConfig, _ := patches[0]["config"].(map[string]any)
if patchConfig["path"] != settingsPath {
t.Fatalf("settings patch path = %#v", patchConfig["path"])
}
if got := dsh.CurrentModel(); got != "qwen3.5:latest" {
t.Fatalf("CurrentModel() = %q", got)
}
}
func TestDeepSeekHarnessConfigureSkipsWebSearchWhenCloudDisabled(t *testing.T) {
home := t.TempDir()
setTestHome(t, home)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/api/status" {
fmt.Fprint(w, `{"cloud":{"disabled":true,"source":"config"}}`)
return
}
http.NotFound(w, r)
}))
t.Cleanup(srv.Close)
t.Setenv("OLLAMA_HOST", srv.URL)
settingsPath, err := deepSeekHarnessSettingsPath()
if err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(filepath.Dir(settingsPath), 0o700); err != nil {
t.Fatal(err)
}
existing := []byte("web-search-deepseek:\n maxUses: 3\n")
if err := os.WriteFile(settingsPath, existing, 0o600); err != nil {
t.Fatal(err)
}
dsh := &DeepSeekHarness{}
if err := dsh.ConfigureWithModels("qwen3.5", []LaunchModel{{Name: "qwen3.5:latest"}}); err != nil {
t.Fatal(err)
}
settings, err := readDeepSeekHarnessYAML(settingsPath)
if err != nil {
t.Fatal(err)
}
web, _ := settings[deepSeekHarnessWebSettings].(map[string]any)
if len(web) != 1 || web["maxUses"] != 3 {
t.Fatalf("web search settings = %#v", web)
}
if got := dsh.CurrentModel(); got != "qwen3.5:latest" {
t.Fatalf("CurrentModel() = %q", got)
}
}
func TestDeepSeekHarnessCurrentModelRejectsDrift(t *testing.T) {
setTestHome(t, t.TempDir())
t.Setenv("OLLAMA_HOST", "http://127.0.0.1:11434")
dsh := &DeepSeekHarness{}
if err := dsh.Configure("qwen3.5"); err != nil {
t.Fatal(err)
}
t.Setenv("OLLAMA_HOST", "http://127.0.0.1:9999")
if got := dsh.CurrentModel(); got != "" {
t.Fatalf("CurrentModel() = %q for stale endpoint", got)
}
}
func TestDeepSeekHarnessCurrentModelRejectsPatchDrift(t *testing.T) {
setTestHome(t, t.TempDir())
t.Setenv("OLLAMA_HOST", "http://127.0.0.1:11434")
dsh := &DeepSeekHarness{}
if err := dsh.Configure("qwen3.5"); err != nil {
t.Fatal(err)
}
patchPath, err := deepSeekHarnessPatchPath()
if err != nil {
t.Fatal(err)
}
for name, data := range map[string][]byte{
"missing": nil,
"malformed": []byte("["),
"wrong path": []byte(`- id: settings
config:
path: /tmp/not-managed.yaml
`),
} {
t.Run(name, func(t *testing.T) {
if err := dsh.Configure("qwen3.5"); err != nil {
t.Fatal(err)
}
if data == nil {
if err := os.Remove(patchPath); err != nil {
t.Fatal(err)
}
} else if err := os.WriteFile(patchPath, data, 0o600); err != nil {
t.Fatal(err)
}
if got := dsh.CurrentModel(); got != "" {
t.Fatalf("CurrentModel() = %q", got)
}
})
}
}
func TestDeepSeekHarnessConfigureRejectsMalformedSettingsWithoutOverwrite(t *testing.T) {
setTestHome(t, t.TempDir())
settingsPath, err := deepSeekHarnessSettingsPath()
if err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(filepath.Dir(settingsPath), 0o700); err != nil {
t.Fatal(err)
}
malformed := []byte("llm-pi-ai: [")
if err := os.WriteFile(settingsPath, malformed, 0o600); err != nil {
t.Fatal(err)
}
err = (&DeepSeekHarness{}).Configure("qwen3.5")
if err == nil || !strings.Contains(err.Error(), "parse deepseek harness launch settings") {
t.Fatalf("Configure() error = %v", err)
}
got, err := os.ReadFile(settingsPath)
if err != nil {
t.Fatal(err)
}
if !slices.Equal(got, malformed) {
t.Fatalf("malformed settings were overwritten: %q", got)
}
}
func TestDeepSeekHarnessConfigureAcceptsNullSettings(t *testing.T) {
setTestHome(t, t.TempDir())
settingsPath, err := deepSeekHarnessSettingsPath()
if err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(filepath.Dir(settingsPath), 0o700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(settingsPath, []byte("null\n"), 0o600); err != nil {
t.Fatal(err)
}
if err := (&DeepSeekHarness{}).Configure("qwen3.5"); err != nil {
t.Fatal(err)
}
}
func TestDeepSeekHarnessRunUsesManagedPatchAndCredential(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("uses a POSIX shell test binary")
}
home := t.TempDir()
setTestHome(t, home)
binDir := t.TempDir()
logPath := filepath.Join(home, "dsh-invocation")
script := "#!/bin/sh\nprintf '%s\\n' \"$@\" > \"$DSH_TEST_LOG\"\nprintf '%s\\n' \"$OLLAMA_LAUNCH_DSH_API_KEY\" >> \"$DSH_TEST_LOG\"\n"
bin := filepath.Join(binDir, "dsh")
if err := os.WriteFile(bin, []byte(script), 0o755); err != nil {
t.Fatal(err)
}
t.Setenv("PATH", strings.Join([]string{binDir, "/bin", "/usr/bin"}, string(os.PathListSeparator)))
t.Setenv("DSH_TEST_LOG", logPath)
t.Setenv(deepSeekHarnessAPIKeyEnv, "do-not-keep")
dsh := &DeepSeekHarness{}
if err := dsh.Configure("qwen3.5"); err != nil {
t.Fatal(err)
}
if err := dsh.Run("qwen3.5", nil, []string{"--port", "0"}); err != nil {
t.Fatal(err)
}
data, err := os.ReadFile(logPath)
if err != nil {
t.Fatal(err)
}
patchPath, _ := deepSeekHarnessPatchPath()
want := "web\n--patch\n" + patchPath + "\n--port\n0\nollama\n"
if string(data) != want {
t.Fatalf("invocation = %q, want %q", data, want)
}
}
func TestDeepSeekHarnessRejectsManagedPatchArgument(t *testing.T) {
for _, args := range [][]string{{"--patch", "other.yml"}, {"--patch=other.yml"}} {
if err := (&DeepSeekHarness{}).Run("qwen3.5", nil, args); err == nil || !strings.Contains(err.Error(), "manages --patch") {
t.Fatalf("Run(%v) error = %v", args, err)
}
}
}
func TestEnsureDeepSeekHarnessInstalledUsesPublicNpmPackage(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("uses a POSIX shell test binary")
}
home := t.TempDir()
binDir := t.TempDir()
logPath := filepath.Join(home, "npm-invocation")
npm := filepath.Join(binDir, "npm")
script := "#!/bin/sh\nprintf '%s\\n' \"$@\" > \"$DSH_NPM_LOG\"\nprintf '#!/bin/sh\\nexit 0\\n' > \"$DSH_INSTALLED_BIN\"\nchmod +x \"$DSH_INSTALLED_BIN\"\n"
if err := os.WriteFile(npm, []byte(script), 0o755); err != nil {
t.Fatal(err)
}
dshBin := filepath.Join(binDir, "dsh")
t.Setenv("PATH", strings.Join([]string{binDir, "/bin", "/usr/bin"}, string(os.PathListSeparator)))
t.Setenv("DSH_NPM_LOG", logPath)
t.Setenv("DSH_INSTALLED_BIN", dshBin)
restore := withLaunchConfirmPolicy(launchConfirmPolicy{yes: true})
defer restore()
path, err := ensureDeepSeekHarnessInstalled()
if err != nil {
t.Fatal(err)
}
if path != dshBin {
t.Fatalf("installed path = %q, want %q", path, dshBin)
}
data, err := os.ReadFile(logPath)
if err != nil {
t.Fatal(err)
}
if string(data) != "install\n-g\n@deepseek-ai/dsh@latest\n" {
t.Fatalf("npm invocation = %q", data)
}
}
func TestDeepSeekHarnessLaunchArgs(t *testing.T) {
got := deepSeekHarnessLaunchArgs("/tmp/ollama.cordis.yml", []string{"--port", "0"})
want := []string{"web", "--patch", "/tmp/ollama.cordis.yml", "--port", "0"}
if !slices.Equal(got, want) {
t.Fatalf("launch args = %v, want %v", got, want)
}
}
func TestDeepSeekHarnessWindowsNodeShims(t *testing.T) {
root := t.TempDir()
node := filepath.Join(root, "node.exe")
dsh := filepath.Join(root, "npm", "dsh.cmd")
npm := filepath.Join(root, "node", "npm.cmd")
dshEntrypoint := filepath.Join(filepath.Dir(dsh), "node_modules", "@deepseek-ai", "dsh", "lib", "bin.js")
npmEntrypoint := filepath.Join(filepath.Dir(npm), "node_modules", "npm", "bin", "npm-cli.js")
for _, path := range []string{node, dsh, npm, dshEntrypoint, npmEntrypoint} {
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, nil, 0o755); err != nil {
t.Fatal(err)
}
}
originalGOOS := deepSeekHarnessGOOS
originalLookPath := deepSeekHarnessLookPath
deepSeekHarnessGOOS = "windows"
deepSeekHarnessLookPath = func(file string) (string, error) {
if file == "node" {
return node, nil
}
return "", exec.ErrNotFound
}
t.Cleanup(func() {
deepSeekHarnessGOOS = originalGOOS
deepSeekHarnessLookPath = originalLookPath
})
t.Run("dsh", func(t *testing.T) {
cmd, err := deepSeekHarnessExecutableCommand(dsh, []string{"web", "--port", "0", "a&b"})
if err != nil {
t.Fatal(err)
}
want := []string{node, dshEntrypoint, "web", "--port", "0", "a&b"}
if !slices.Equal(cmd.Args, want) {
t.Fatalf("command args = %v, want %v", cmd.Args, want)
}
})
t.Run("npm", func(t *testing.T) {
cmd, err := deepSeekHarnessNpmCommand(npm, []string{"install", "-g", deepSeekHarnessNpmPackage})
if err != nil {
t.Fatal(err)
}
want := []string{node, npmEntrypoint, "install", "-g", deepSeekHarnessNpmPackage}
if !slices.Equal(cmd.Args, want) {
t.Fatalf("command args = %v, want %v", cmd.Args, want)
}
})
}
func TestDeepSeekHarnessWindowsNodeShimRequiresEntrypoint(t *testing.T) {
originalGOOS := deepSeekHarnessGOOS
originalLookPath := deepSeekHarnessLookPath
deepSeekHarnessGOOS = "windows"
deepSeekHarnessLookPath = func(file string) (string, error) {
if file == "node" {
return filepath.Join(t.TempDir(), "node.exe"), nil
}
return "", exec.ErrNotFound
}
t.Cleanup(func() {
deepSeekHarnessGOOS = originalGOOS
deepSeekHarnessLookPath = originalLookPath
})
_, err := deepSeekHarnessExecutableCommand(filepath.Join(t.TempDir(), "dsh.cmd"), nil)
if err == nil || !strings.Contains(err.Error(), "resolve Windows entrypoint") {
t.Fatalf("error = %v", err)
}
}
func TestDeepSeekHarnessInstallDependencyError(t *testing.T) {
originalLookPath := deepSeekHarnessLookPath
deepSeekHarnessLookPath = func(file string) (string, error) { return "", exec.ErrNotFound }
t.Cleanup(func() { deepSeekHarnessLookPath = originalLookPath })
_, err := ensureDeepSeekHarnessInstalled()
if err == nil || !strings.Contains(err.Error(), "npm (Node.js) is required") {
t.Fatalf("error = %v", err)
}
}

View file

@ -67,6 +67,8 @@ func TestIntegrationLookup(t *testing.T) {
{"muse", "muse", true, "Muse Code"},
{"muse alias", "muse-code", true, "Muse Code"},
{"droid", "droid", true, "Droid"},
{"dsh", "dsh", true, "DeepSeek Harness"},
{"deepseek harness alias", "deepseek-harness", true, "DeepSeek Harness"},
{"opencode", "opencode", true, "OpenCode"},
{"omp", "omp", true, "OMP"},
{"pool", "pool", true, "Pool"},
@ -88,7 +90,7 @@ func TestIntegrationLookup(t *testing.T) {
}
func TestIntegrationRegistry(t *testing.T) {
expectedIntegrations := []string{"claude", "claude-desktop", "cline", "codex", "chatgpt", "kimi", "muse", "droid", "opencode", "omp", "hermes", "hermes-desktop", "pool", "qwen"}
expectedIntegrations := []string{"claude", "claude-desktop", "cline", "codex", "chatgpt", "kimi", "muse", "droid", "dsh", "opencode", "omp", "hermes", "hermes-desktop", "pool", "qwen"}
for _, name := range expectedIntegrations {
t.Run(name, func(t *testing.T) {
r, ok := integrations[name]

View file

@ -296,6 +296,7 @@ Supported integrations:
copilot Copilot CLI (aliases: copilot-cli)
omp OMP
droid Droid
dsh DeepSeek Harness (alias: deepseek-harness)
kimi Kimi Code CLI
muse Muse Code (aliases: muse-code)
pi Pi
@ -312,6 +313,7 @@ Examples:
ollama launch chatgpt --restore
ollama launch hermes
ollama launch hermes-desktop
ollama launch dsh
ollama launch droid --config (does not auto-launch)
ollama launch codex --restore
ollama launch codex -- --sandbox workspace-write`,

View file

@ -151,6 +151,21 @@ func (r *launcherManagedListRunner) ConfigureWithModels(primary string, models [
return r.Configure(primary)
}
type launcherCanonicalManagedListRunner struct {
launcherManagedListRunner
}
func (r *launcherCanonicalManagedListRunner) ConfigureWithModels(primary string, models []LaunchModel) error {
r.configuredModelLists = append(r.configuredModelLists, launchModelNames(models))
r.configured = append(r.configured, primary)
if selected, ok := findLaunchModel(models, primary); ok {
r.currentModel = selected.Name
} else {
r.currentModel = primary
}
return nil
}
type launcherManagedAutodiscoveryRunner struct {
launcherManagedRunner
autodiscoveryConfigures int
@ -1089,6 +1104,51 @@ func TestLaunchIntegration_ManagedSingleIntegrationCanConfigureWithModelList(t *
}
}
func TestLaunchIntegration_ManagedSingleIntegrationSavesCanonicalModel(t *testing.T) {
tmpDir := t.TempDir()
setLaunchTestHome(t, tmpDir)
withInteractiveSession(t, true)
withLauncherHooks(t)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/experimental/model-recommendations":
fmt.Fprint(w, `{"recommendations":[]}`)
case "/api/tags":
fmt.Fprint(w, `{"models":[{"name":"qwen3.5:latest"}]}`)
case "/api/show":
fmt.Fprint(w, `{"model_info":{"general.context_length":131072}}`)
default:
http.NotFound(w, r)
}
}))
defer srv.Close()
t.Setenv("OLLAMA_HOST", srv.URL)
runner := &launcherCanonicalManagedListRunner{}
withIntegrationOverride(t, "stubmanaged", runner)
request := IntegrationLaunchRequest{Name: "stubmanaged", ModelOverride: "qwen3.5"}
if err := LaunchIntegration(context.Background(), request); err != nil {
t.Fatalf("first LaunchIntegration returned error: %v", err)
}
saved, err := config.LoadIntegration("stubmanaged")
if err != nil {
t.Fatalf("failed to reload managed integration config: %v", err)
}
if diff := compareStrings(saved.Models, []string{"qwen3.5:latest"}); diff != "" {
t.Fatalf("saved models mismatch: %s", diff)
}
if err := LaunchIntegration(context.Background(), IntegrationLaunchRequest{Name: "stubmanaged"}); err != nil {
t.Fatalf("second LaunchIntegration returned error: %v", err)
}
if diff := compareStrings(runner.configured, []string{"qwen3.5"}); diff != "" {
t.Fatalf("expected second launch to skip configuration: %s", diff)
}
}
func TestLaunchIntegration_ManagedAutodiscoverySkipsModelPicker(t *testing.T) {
tmpDir := t.TempDir()
setLaunchTestHome(t, tmpDir)

View file

@ -323,6 +323,9 @@ func prepareManagedSingleIntegration(name string, managed ManagedSingleModel, mo
if err != nil {
return fmt.Errorf("setup failed: %w", err)
}
if current := managed.CurrentModel(); current != "" {
model = current
}
if err := config.SaveIntegration(name, []string{model}); err != nil {
return fmt.Errorf("failed to save: %w", err)
}

View file

@ -33,7 +33,7 @@ type IntegrationInfo struct {
Description string
}
var launcherIntegrationOrder = []string{"claude", "chatgpt", "hermes", "openclaw", "opencode", "hermes-desktop", "codex", "copilot", "omp", "cline", "droid", "pi", "pool", "qwen"}
var launcherIntegrationOrder = []string{"claude", "chatgpt", "hermes", "openclaw", "opencode", "hermes-desktop", "codex", "copilot", "omp", "cline", "droid", "dsh", "pi", "pool", "qwen"}
var integrationSpecs = []*IntegrationSpec{
{
@ -166,6 +166,24 @@ var integrationSpecs = []*IntegrationSpec{
URL: "https://docs.factory.ai/cli/getting-started/quickstart",
},
},
{
Name: deepSeekHarnessIntegrationName,
Runner: &DeepSeekHarness{},
Aliases: []string{"deepseek-harness"},
Description: "DeepSeek's open-source agent harness",
Install: IntegrationInstallSpec{
CheckInstalled: func() bool {
_, err := deepSeekHarnessLookPath("dsh")
return err == nil
},
EnsureInstalled: func() error {
_, err := ensureDeepSeekHarnessInstalled()
return err
},
URL: "https://github.com/deepseek-ai/deepseek-harness",
Command: []string{"npm", "install", "-g", deepSeekHarnessNpmPackage},
},
},
{
Name: "opencode",
Runner: &OpenCode{},