mirror of
https://github.com/ollama/ollama.git
synced 2026-03-09 07:16:38 -05:00
* Revert "Revert "Reapply "don't require pulling stubs for cloud models"" (#14606)"
This reverts commit 39982a954e.
* fix test + do cloud lookup only when seeing cloud models
---------
Co-authored-by: ParthSareen <parth.sareen@ollama.com>
188 lines
4.8 KiB
Go
188 lines
4.8 KiB
Go
package config
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
|
|
"github.com/ollama/ollama/api"
|
|
"github.com/ollama/ollama/envconfig"
|
|
)
|
|
|
|
// Claude implements Runner and AliasConfigurer for Claude Code integration
|
|
type Claude struct{}
|
|
|
|
// Compile-time check that Claude implements AliasConfigurer
|
|
var _ AliasConfigurer = (*Claude)(nil)
|
|
|
|
func (c *Claude) String() string { return "Claude Code" }
|
|
|
|
func (c *Claude) args(model string, extra []string) []string {
|
|
var args []string
|
|
if model != "" {
|
|
args = append(args, "--model", model)
|
|
}
|
|
args = append(args, extra...)
|
|
return args
|
|
}
|
|
|
|
func (c *Claude) findPath() (string, error) {
|
|
if p, err := exec.LookPath("claude"); err == nil {
|
|
return p, nil
|
|
}
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
name := "claude"
|
|
if runtime.GOOS == "windows" {
|
|
name = "claude.exe"
|
|
}
|
|
fallback := filepath.Join(home, ".claude", "local", name)
|
|
if _, err := os.Stat(fallback); err != nil {
|
|
return "", err
|
|
}
|
|
return fallback, nil
|
|
}
|
|
|
|
func (c *Claude) Run(model string, args []string) error {
|
|
claudePath, err := c.findPath()
|
|
if err != nil {
|
|
return fmt.Errorf("claude is not installed, install from https://code.claude.com/docs/en/quickstart")
|
|
}
|
|
|
|
cmd := exec.Command(claudePath, c.args(model, args)...)
|
|
cmd.Stdin = os.Stdin
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
env := append(os.Environ(),
|
|
"ANTHROPIC_BASE_URL="+envconfig.Host().String(),
|
|
"ANTHROPIC_API_KEY=",
|
|
"ANTHROPIC_AUTH_TOKEN=ollama",
|
|
)
|
|
|
|
env = append(env, c.modelEnvVars(model)...)
|
|
|
|
cmd.Env = env
|
|
return cmd.Run()
|
|
}
|
|
|
|
// modelEnvVars returns Claude Code env vars that route all model tiers through Ollama.
|
|
func (c *Claude) modelEnvVars(model string) []string {
|
|
primary := model
|
|
fast := model
|
|
if cfg, err := loadIntegration("claude"); err == nil && cfg.Aliases != nil {
|
|
if p := cfg.Aliases["primary"]; p != "" {
|
|
primary = p
|
|
}
|
|
if f := cfg.Aliases["fast"]; f != "" {
|
|
fast = f
|
|
}
|
|
}
|
|
return []string{
|
|
"ANTHROPIC_DEFAULT_OPUS_MODEL=" + primary,
|
|
"ANTHROPIC_DEFAULT_SONNET_MODEL=" + primary,
|
|
"ANTHROPIC_DEFAULT_HAIKU_MODEL=" + fast,
|
|
"CLAUDE_CODE_SUBAGENT_MODEL=" + primary,
|
|
}
|
|
}
|
|
|
|
// ConfigureAliases sets up model aliases for Claude Code.
|
|
// model: the model to use (if empty, user will be prompted to select)
|
|
// aliases: existing alias configuration to preserve/update
|
|
// Cloud-only: subagent routing (fast model) is gated to cloud models only until
|
|
// there is a better strategy for prompt caching on local models.
|
|
func (c *Claude) ConfigureAliases(ctx context.Context, model string, existingAliases map[string]string, force bool) (map[string]string, bool, error) {
|
|
aliases := make(map[string]string)
|
|
for k, v := range existingAliases {
|
|
aliases[k] = v
|
|
}
|
|
|
|
if model != "" {
|
|
aliases["primary"] = model
|
|
}
|
|
|
|
if !force && aliases["primary"] != "" {
|
|
if isCloudModelName(aliases["primary"]) {
|
|
aliases["fast"] = aliases["primary"]
|
|
return aliases, false, nil
|
|
}
|
|
delete(aliases, "fast")
|
|
return aliases, false, nil
|
|
}
|
|
|
|
items, existingModels, cloudModels, client, err := listModels(ctx)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
fmt.Fprintf(os.Stderr, "\n%sModel Configuration%s\n\n", ansiBold, ansiReset)
|
|
|
|
if aliases["primary"] == "" || force {
|
|
primary, err := DefaultSingleSelector("Select model:", items, aliases["primary"])
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
if err := pullIfNeeded(ctx, client, existingModels, primary); err != nil {
|
|
return nil, false, err
|
|
}
|
|
if err := ensureAuth(ctx, client, cloudModels, []string{primary}); err != nil {
|
|
return nil, false, err
|
|
}
|
|
aliases["primary"] = primary
|
|
}
|
|
|
|
if isCloudModelName(aliases["primary"]) {
|
|
aliases["fast"] = aliases["primary"]
|
|
} else {
|
|
delete(aliases, "fast")
|
|
}
|
|
|
|
return aliases, true, nil
|
|
}
|
|
|
|
// SetAliases syncs the configured aliases to the Ollama server using prefix matching.
|
|
// Cloud-only: for local models (fast is empty), we delete any existing aliases to
|
|
// prevent stale routing to a previous cloud model.
|
|
func (c *Claude) SetAliases(ctx context.Context, aliases map[string]string) error {
|
|
client, err := api.ClientFromEnvironment()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
prefixes := []string{"claude-sonnet-", "claude-haiku-"}
|
|
|
|
if aliases["fast"] == "" {
|
|
for _, prefix := range prefixes {
|
|
_ = client.DeleteAliasExperimental(ctx, &api.AliasDeleteRequest{Alias: prefix})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
prefixAliases := map[string]string{
|
|
"claude-sonnet-": aliases["primary"],
|
|
"claude-haiku-": aliases["fast"],
|
|
}
|
|
|
|
var errs []string
|
|
for prefix, target := range prefixAliases {
|
|
req := &api.AliasRequest{
|
|
Alias: prefix,
|
|
Target: target,
|
|
PrefixMatching: true,
|
|
}
|
|
if err := client.SetAliasExperimental(ctx, req); err != nil {
|
|
errs = append(errs, prefix)
|
|
}
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
return fmt.Errorf("failed to set aliases: %v", errs)
|
|
}
|
|
return nil
|
|
}
|