This commit is contained in:
Eva Ho
2026-07-09 17:35:01 -07:00
parent 2363cf3ffa
commit 52b7205e5e
5 changed files with 52 additions and 30 deletions

View File

@@ -58,16 +58,21 @@ func (c *Claude) Run(model string, models []LaunchModel, args []string) error {
return err return err
} }
contextLength := 0
if len(models) > 0 {
contextLength = models[0].ContextLength
}
cmd := exec.Command(claudePath, c.args(model, args)...) cmd := exec.Command(claudePath, c.args(model, args)...)
cmd.Stdin = os.Stdin cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
cmd.Env = append(os.Environ(), c.envVars(model, models...)...) cmd.Env = append(os.Environ(), c.envVars(model, contextLength)...)
return cmd.Run() return cmd.Run()
} }
func (c *Claude) envVars(model string, models ...LaunchModel) []string { func (c *Claude) envVars(model string, contextLength int) []string {
env := []string{ env := []string{
"ANTHROPIC_BASE_URL=" + envconfig.Host().String(), "ANTHROPIC_BASE_URL=" + envconfig.Host().String(),
"ANTHROPIC_API_KEY=", "ANTHROPIC_API_KEY=",
@@ -80,7 +85,7 @@ func (c *Claude) envVars(model string, models ...LaunchModel) []string {
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1", "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1",
} }
env = append(env, c.modelEnvVars(model, models...)...) env = append(env, c.modelEnvVars(model, contextLength)...)
return env return env
} }
@@ -211,7 +216,7 @@ func launchModelsWithContextLength(primary string, models []LaunchModel, context
} }
// modelEnvVars returns Claude Code env vars that route all model tiers through Ollama. // modelEnvVars returns Claude Code env vars that route all model tiers through Ollama.
func (c *Claude) modelEnvVars(model string, models ...LaunchModel) []string { func (c *Claude) modelEnvVars(model string, contextLength int) []string {
env := []string{ env := []string{
"ANTHROPIC_DEFAULT_OPUS_MODEL=" + model, "ANTHROPIC_DEFAULT_OPUS_MODEL=" + model,
"ANTHROPIC_DEFAULT_SONNET_MODEL=" + model, "ANTHROPIC_DEFAULT_SONNET_MODEL=" + model,
@@ -223,8 +228,8 @@ func (c *Claude) modelEnvVars(model string, models ...LaunchModel) []string {
if l, ok := lookupCloudModelLimit(model); ok { if l, ok := lookupCloudModelLimit(model); ok {
env = append(env, "CLAUDE_CODE_AUTO_COMPACT_WINDOW="+strconv.Itoa(l.Context)) env = append(env, "CLAUDE_CODE_AUTO_COMPACT_WINDOW="+strconv.Itoa(l.Context))
} }
} else if launchModel, ok := findLaunchModel(models, model); ok && launchModel.ContextLength >= claudeCodeAutoCompactMinContext { } else if contextLength >= claudeCodeAutoCompactMinContext {
env = append(env, "CLAUDE_CODE_AUTO_COMPACT_WINDOW="+strconv.Itoa(launchModel.ContextLength)) env = append(env, "CLAUDE_CODE_AUTO_COMPACT_WINDOW="+strconv.Itoa(contextLength))
} }
return env return env

View File

@@ -348,7 +348,7 @@ func TestClaudeEnvVars(t *testing.T) {
return m return m
} }
got := envMap(c.envVars("llama3.2")) got := envMap(c.envVars("llama3.2", 0))
for key, want := range map[string]string{ for key, want := range map[string]string{
"ANTHROPIC_BASE_URL": envconfig.Host().String(), "ANTHROPIC_BASE_URL": envconfig.Host().String(),
"ANTHROPIC_API_KEY": "", "ANTHROPIC_API_KEY": "",
@@ -382,8 +382,8 @@ func TestClaudePrepareRunLaunchModelsWarnsForLowLocalContext(t *testing.T) {
if options.Default != ConfirmDefaultNo { if options.Default != ConfirmDefaultNo {
t.Fatal("expected warning prompt to default to no") t.Fatal("expected warning prompt to default to no")
} }
if options.YesLabel != "Continue" || options.NoLabel != "Cancel" { if options.YesLabel != "Launch anyway" || options.NoLabel != "Cancel" {
t.Fatalf("labels = %q/%q, want Continue/Cancel", options.YesLabel, options.NoLabel) t.Fatalf("labels = %q/%q, want Launch anyway/Cancel", options.YesLabel, options.NoLabel)
} }
return false, nil return false, nil
} }
@@ -393,9 +393,8 @@ func TestClaudePrepareRunLaunchModelsWarnsForLowLocalContext(t *testing.T) {
t.Fatalf("error = %v, want ErrCancelled", err) t.Fatalf("error = %v, want ErrCancelled", err)
} }
for _, want := range []string{ for _, want := range []string{
"Claude Code works best with at least 100k context.", "Claude Code works best with at least 100k context. Current local context: 32k.",
"Current local context: 32k.", "Launch Claude Code anyway?",
"Continue launching Claude Code?",
} { } {
if !strings.Contains(gotPrompt, want) { if !strings.Contains(gotPrompt, want) {
t.Fatalf("prompt missing %q:\n%s", want, gotPrompt) t.Fatalf("prompt missing %q:\n%s", want, gotPrompt)
@@ -505,7 +504,7 @@ func TestClaudeModelEnvVars(t *testing.T) {
} }
t.Run("maps all Claude model env vars to the provided model", func(t *testing.T) { t.Run("maps all Claude model env vars to the provided model", func(t *testing.T) {
got := envMap(c.modelEnvVars("llama3.2")) got := envMap(c.modelEnvVars("llama3.2", 0))
if got["ANTHROPIC_DEFAULT_OPUS_MODEL"] != "llama3.2" { if got["ANTHROPIC_DEFAULT_OPUS_MODEL"] != "llama3.2" {
t.Errorf("OPUS = %q, want llama3.2", got["ANTHROPIC_DEFAULT_OPUS_MODEL"]) t.Errorf("OPUS = %q, want llama3.2", got["ANTHROPIC_DEFAULT_OPUS_MODEL"])
} }
@@ -524,7 +523,7 @@ func TestClaudeModelEnvVars(t *testing.T) {
}) })
t.Run("supports empty model", func(t *testing.T) { t.Run("supports empty model", func(t *testing.T) {
got := envMap(c.modelEnvVars("")) got := envMap(c.modelEnvVars("", 0))
if got["ANTHROPIC_DEFAULT_OPUS_MODEL"] != "" { if got["ANTHROPIC_DEFAULT_OPUS_MODEL"] != "" {
t.Errorf("OPUS = %q, want empty", got["ANTHROPIC_DEFAULT_OPUS_MODEL"]) t.Errorf("OPUS = %q, want empty", got["ANTHROPIC_DEFAULT_OPUS_MODEL"])
} }
@@ -543,28 +542,28 @@ func TestClaudeModelEnvVars(t *testing.T) {
}) })
t.Run("sets auto compact window for known cloud models", func(t *testing.T) { t.Run("sets auto compact window for known cloud models", func(t *testing.T) {
got := envMap(c.modelEnvVars("glm-5:cloud")) got := envMap(c.modelEnvVars("glm-5:cloud", 0))
if got["CLAUDE_CODE_AUTO_COMPACT_WINDOW"] != "202752" { if got["CLAUDE_CODE_AUTO_COMPACT_WINDOW"] != "202752" {
t.Errorf("AUTO_COMPACT_WINDOW = %q, want 202752", got["CLAUDE_CODE_AUTO_COMPACT_WINDOW"]) t.Errorf("AUTO_COMPACT_WINDOW = %q, want 202752", got["CLAUDE_CODE_AUTO_COMPACT_WINDOW"])
} }
}) })
t.Run("sets auto compact window for local models at Claude minimum", func(t *testing.T) { t.Run("sets auto compact window for local models at Claude minimum", func(t *testing.T) {
got := envMap(c.modelEnvVars("llama3.2", LaunchModel{Name: "llama3.2", ContextLength: 131072})) got := envMap(c.modelEnvVars("llama3.2", 131072))
if got["CLAUDE_CODE_AUTO_COMPACT_WINDOW"] != "131072" { if got["CLAUDE_CODE_AUTO_COMPACT_WINDOW"] != "131072" {
t.Errorf("AUTO_COMPACT_WINDOW = %q, want 131072", got["CLAUDE_CODE_AUTO_COMPACT_WINDOW"]) t.Errorf("AUTO_COMPACT_WINDOW = %q, want 131072", got["CLAUDE_CODE_AUTO_COMPACT_WINDOW"])
} }
}) })
t.Run("does not set auto compact window for local models below Claude minimum", func(t *testing.T) { t.Run("does not set auto compact window for local models below Claude minimum", func(t *testing.T) {
got := envMap(c.modelEnvVars("llama3.2", LaunchModel{Name: "llama3.2", ContextLength: 32768})) got := envMap(c.modelEnvVars("llama3.2", 32768))
if got["CLAUDE_CODE_AUTO_COMPACT_WINDOW"] != "" { if got["CLAUDE_CODE_AUTO_COMPACT_WINDOW"] != "" {
t.Errorf("AUTO_COMPACT_WINDOW = %q, want empty", got["CLAUDE_CODE_AUTO_COMPACT_WINDOW"]) t.Errorf("AUTO_COMPACT_WINDOW = %q, want empty", got["CLAUDE_CODE_AUTO_COMPACT_WINDOW"])
} }
}) })
t.Run("does not set auto compact window for unknown cloud models", func(t *testing.T) { t.Run("does not set auto compact window for unknown cloud models", func(t *testing.T) {
got := envMap(c.modelEnvVars("unknown-model:cloud")) got := envMap(c.modelEnvVars("unknown-model:cloud", 0))
if got["CLAUDE_CODE_AUTO_COMPACT_WINDOW"] != "" { if got["CLAUDE_CODE_AUTO_COMPACT_WINDOW"] != "" {
t.Errorf("AUTO_COMPACT_WINDOW = %q, want empty", got["CLAUDE_CODE_AUTO_COMPACT_WINDOW"]) t.Errorf("AUTO_COMPACT_WINDOW = %q, want empty", got["CLAUDE_CODE_AUTO_COMPACT_WINDOW"])
} }

View File

@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"os" "os"
"strconv" "strconv"
"strings"
) )
func (c *launcherClient) localServerContextLength(ctx context.Context) (int, bool) { func (c *launcherClient) localServerContextLength(ctx context.Context) (int, bool) {
@@ -33,15 +34,8 @@ func confirmLocalContextWarning(integration string, current, recommended int) er
return fmt.Errorf("%s Re-run with --yes to continue", shortWarning) return fmt.Errorf("%s Re-run with --yes to continue", shortWarning)
} }
prompt := fmt.Sprintf( ok, err := ConfirmPromptWithOptions(localContextLengthPrompt(integration, current, recommended), ConfirmOptions{
"%s works best with at least %s context.\nCurrent local context: %s.\nAdjust Context length in Ollama Settings and restart to change this.\n\nContinue launching %s?", YesLabel: "Launch anyway",
integration,
formatContextLength(recommended),
formatContextLength(current),
integration,
)
ok, err := ConfirmPromptWithOptions(prompt, ConfirmOptions{
YesLabel: "Continue",
NoLabel: "Cancel", NoLabel: "Cancel",
Default: ConfirmDefaultNo, Default: ConfirmDefaultNo,
}) })
@@ -54,6 +48,16 @@ func confirmLocalContextWarning(integration string, current, recommended int) er
return nil return nil
} }
func localContextLengthPrompt(integration string, current, recommended int) string {
var b strings.Builder
fmt.Fprintf(&b, "%s works best with at least %s context. ", integration, formatContextLength(recommended))
fmt.Fprintf(&b, "Current local context: %s. ", formatContextLength(current))
b.WriteString("Adjust Context length in Ollama Settings and restart to change this:\n")
b.WriteString(" https://docs.ollama.com/context-length")
fmt.Fprintf(&b, "\n\nLaunch %s anyway?", integration)
return b.String()
}
func formatContextLength(tokens int) string { func formatContextLength(tokens int) string {
switch { switch {
case tokens <= 0: case tokens <= 0:

View File

@@ -78,3 +78,15 @@ func TestFormatContextLength(t *testing.T) {
} }
} }
} }
func TestLocalContextLengthPrompt(t *testing.T) {
got := localContextLengthPrompt("Claude Code", 32*1024, 100_000)
want := "Claude Code works best with at least 100k context. " +
"Current local context: 32k. " +
"Adjust Context length in Ollama Settings and restart to change this:\n" +
" https://docs.ollama.com/context-length\n\n" +
"Launch Claude Code anyway?"
if got != want {
t.Fatalf("prompt = %q, want %q", got, want)
}
}

View File

@@ -337,6 +337,9 @@ func TestOpenCodePrepareLaunchModelsWarnsForLowLocalContext(t *testing.T) {
if options.Default != ConfirmDefaultNo { if options.Default != ConfirmDefaultNo {
t.Fatal("expected warning prompt to default to no") t.Fatal("expected warning prompt to default to no")
} }
if options.YesLabel != "Launch anyway" || options.NoLabel != "Cancel" {
t.Fatalf("labels = %q/%q, want Launch anyway/Cancel", options.YesLabel, options.NoLabel)
}
return false, nil return false, nil
} }
@@ -345,9 +348,8 @@ func TestOpenCodePrepareLaunchModelsWarnsForLowLocalContext(t *testing.T) {
t.Fatalf("error = %v, want ErrCancelled", err) t.Fatalf("error = %v, want ErrCancelled", err)
} }
for _, want := range []string{ for _, want := range []string{
"OpenCode works best with at least 64k context.", "OpenCode works best with at least 64k context. Current local context: 32k.",
"Current local context: 32k.", "Launch OpenCode anyway?",
"Continue launching OpenCode?",
} { } {
if !strings.Contains(gotPrompt, want) { if !strings.Contains(gotPrompt, want) {
t.Fatalf("prompt missing %q:\n%s", want, gotPrompt) t.Fatalf("prompt missing %q:\n%s", want, gotPrompt)