fix(opencode): serve передаёт OPENCODE_CONFIG — детерминированный выбор модели
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -31,6 +32,12 @@ type Server struct {
|
|||||||
Password string // basic auth (если непустой — сервер защищён)
|
Password string // basic auth (если непустой — сервер защищён)
|
||||||
Dir string // каталог, в котором запускается serve (project сервера)
|
Dir string // каталог, в котором запускается serve (project сервера)
|
||||||
|
|
||||||
|
// ConfigPath — путь к глобальному конфигу opencode (opencode.jsonc).
|
||||||
|
// Передаётся serve через env OPENCODE_CONFIG, чтобы гарантированно
|
||||||
|
// подхватить модель/провайдеров независимо от резолва глобального пути.
|
||||||
|
// Пусто — serve резолвит конфиг сам (см. defaults).
|
||||||
|
ConfigPath string
|
||||||
|
|
||||||
// URL задаёт внешний сервер. Пусто — супервайзер владеет процессом.
|
// URL задаёт внешний сервер. Пусто — супервайзер владеет процессом.
|
||||||
URL string
|
URL string
|
||||||
PollInterval time.Duration // как часто проверять /global/health
|
PollInterval time.Duration // как часто проверять /global/health
|
||||||
@@ -60,6 +67,20 @@ func (s *Server) defaults() {
|
|||||||
if s.Stdout == nil {
|
if s.Stdout == nil {
|
||||||
s.Stdout = os.Stderr
|
s.Stdout = os.Stderr
|
||||||
}
|
}
|
||||||
|
if s.ConfigPath == "" {
|
||||||
|
s.ConfigPath = defaultOpenCodeConfigPath()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultOpenCodeConfigPath возвращает путь к глобальному конфигу opencode,
|
||||||
|
// совпадающий с тем, что opencode загружает по умолчанию: ~/.config/opencode/
|
||||||
|
// (в т.ч. на Windows — см. Global.Path.config в исходниках opencode).
|
||||||
|
func defaultOpenCodeConfigPath() string {
|
||||||
|
home, _ := os.UserHomeDir()
|
||||||
|
if home == "" {
|
||||||
|
home = "."
|
||||||
|
}
|
||||||
|
return filepath.Join(home, ".config", "opencode", "opencode.jsonc")
|
||||||
}
|
}
|
||||||
|
|
||||||
// baseURL собирает полный адрес сервера (http://host:port).
|
// baseURL собирает полный адрес сервера (http://host:port).
|
||||||
@@ -125,6 +146,12 @@ func (s *Server) serveCmd(ctx context.Context) *exec.Cmd {
|
|||||||
env := append(os.Environ(),
|
env := append(os.Environ(),
|
||||||
"OPENCODE_DISABLE_AUTOUPDATE=1",
|
"OPENCODE_DISABLE_AUTOUPDATE=1",
|
||||||
"OPENCODE_DISABLE_MODELS_FETCH=1")
|
"OPENCODE_DISABLE_MODELS_FETCH=1")
|
||||||
|
// Гарантированно указываем глобальный конфиг opencode: serve обязан
|
||||||
|
// подхватить модель/провайдеров из него (иначе возможен фоллбэк на
|
||||||
|
// случайную модель из каталога).
|
||||||
|
if s.ConfigPath != "" {
|
||||||
|
env = append(env, "OPENCODE_CONFIG="+s.ConfigPath)
|
||||||
|
}
|
||||||
// Агенты (analyst/dev/...) opencode находит сам через project-каталог
|
// Агенты (analyst/dev/...) opencode находит сам через project-каталог
|
||||||
// .opencode (см. internal/agents).
|
// .opencode (см. internal/agents).
|
||||||
if s.DBPath != "" {
|
if s.DBPath != "" {
|
||||||
|
|||||||
@@ -198,4 +198,38 @@ func atoiOrZero(s string) int {
|
|||||||
n = n*10 + int(c-'0')
|
n = n*10 + int(c-'0')
|
||||||
}
|
}
|
||||||
return n
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServeCmd_SetsOpenCodeConfig(t *testing.T) {
|
||||||
|
// serveCmd должен всегда передавать OPENCODE_CONFIG: без него serve может
|
||||||
|
// не подхватить модель/провайдеров из глобального конфига.
|
||||||
|
s := &Server{}
|
||||||
|
s.defaults()
|
||||||
|
cmd := s.serveCmd(context.Background())
|
||||||
|
if cmd.Env == nil {
|
||||||
|
t.Fatal("serveCmd: Env не задан")
|
||||||
|
}
|
||||||
|
prefix := "OPENCODE_CONFIG="
|
||||||
|
path := ""
|
||||||
|
for _, kv := range cmd.Env {
|
||||||
|
if strings.HasPrefix(kv, prefix) {
|
||||||
|
path = strings.TrimPrefix(kv, prefix)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if path == "" {
|
||||||
|
t.Fatal("serveCmd: OPENCODE_CONFIG не выставлен")
|
||||||
|
}
|
||||||
|
if want := s.ConfigPath; path != want {
|
||||||
|
t.Errorf("OPENCODE_CONFIG = %q, want %q", path, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerCmd_CustomOpenCodeConfig(t *testing.T) {
|
||||||
|
// явно заданный путь переопределяет дефолтный
|
||||||
|
s := &Server{ConfigPath: `C:\custom\opencode.jsonc`}
|
||||||
|
s.defaults()
|
||||||
|
if s.ConfigPath != `C:\custom\opencode.jsonc` {
|
||||||
|
t.Errorf("ConfigPath = %q, want explicit", s.ConfigPath)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user