feat(opencode): переход на v2 HTTP API opencode (хардпин модели, поллинг вердикта)
- client.go: эндпоинты /api/* (create+model, prompt-admit, message, active, interrupt) - runner.go: неблокирующий prompt + поллинг новых assistant-сообщений; завершение = сессия ушла из активных дренажей + стабильное финальное сообщение - config.go: чтение top-level model из opencode.jsonc (JSONC-стрип) + хардпин в сессию - server.go: healthcheck /api/health, MinVersion=1.18.18, понятная ошибка для старого бинаря - класс O5 WARN: устойчивость к v1-конфигу провайдера (npm/options игнорируются v2) - README: раздел интеграции, минимальная версия opencode, предупреждения - .serena: актуализация памяти (core, tech_stack)
This commit is contained in:
85
internal/opencode/config_test.go
Normal file
85
internal/opencode/config_test.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package opencode
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReadModelRef_String(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "opencode.jsonc")
|
||||
// конфиг с комментариями и trailing-запятыми (JSONC).
|
||||
src := `{
|
||||
// комментарий
|
||||
"model": "tokentool/deepseek/deepseek-v4-flash-0731", /* и блочный */
|
||||
"provider": {
|
||||
"tokentool": {"api": {"type": "aisdk", "package": "@ai-sdk/openai-compatible", "url": "https://x"}},
|
||||
},
|
||||
}`
|
||||
if err := os.WriteFile(path, []byte(src), 0o644); err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
m, err := ReadModelRef(path, "")
|
||||
if err != nil {
|
||||
t.Fatalf("ReadModelRef err: %v", err)
|
||||
}
|
||||
if m == nil || m.ProviderID != "tokentool" || m.ID != "deepseek/deepseek-v4-flash-0731" {
|
||||
t.Errorf("model = %+v, want tokentool/deepseek-v4-flash-0731", m)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadModelRef_Object(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "opencode.json")
|
||||
src := `{"model": {"providerID": "tokentool", "id": "deepseek/deepseek-v4-flash-0731"}}`
|
||||
if err := os.WriteFile(path, []byte(src), 0o644); err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
m, err := ReadModelRef(path, "")
|
||||
if err != nil {
|
||||
t.Fatalf("ReadModelRef err: %v", err)
|
||||
}
|
||||
if m == nil || m.ID != "deepseek/deepseek-v4-flash-0731" {
|
||||
t.Errorf("model = %+v, want object-форма", m)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadModelRef_Missing(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "opencode.json")
|
||||
src := `{"provider": {}}`
|
||||
if err := os.WriteFile(path, []byte(src), 0o644); err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
m, err := ReadModelRef(path, "")
|
||||
if err != nil {
|
||||
t.Fatalf("ReadModelRef err: %v", err)
|
||||
}
|
||||
if m != nil {
|
||||
t.Errorf("model = %+v, want nil (model не задан)", m)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadModelRef_NoFile(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
m, err := ReadModelRef(filepath.Join(dir, "nope.json"), dir)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadModelRef err: %v", err)
|
||||
}
|
||||
if m != nil {
|
||||
t.Errorf("model = %+v, want nil", m)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadModelRef_Bad(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "opencode.json")
|
||||
src := `{"model": 12345}`
|
||||
if err := os.WriteFile(path, []byte(src), 0o644); err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
if _, err := ReadModelRef(path, ""); err == nil {
|
||||
t.Error("ReadModelRef должен упасть на некорректной model")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user