CI / test (push) Failing after 2m41s
A --context-length 32768 --ctx-pool 32768 model got llama.cpp's default four slots of 8192, and refused what the gateway admitted; --parallel is now what the pool holds of the context. max_output was 1024 for every model, which cut an editor's agent off mid-file; it is now a quarter of the context, from 1024 to 8192. --tools writes capabilities.tools, which the Continue configuration now reads, as it reads max_output. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
146 lines
5.6 KiB
Go
146 lines
5.6 KiB
Go
package web
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestEachToolGetsItsConfigurationWithTheKey(t *testing.T) {
|
|
c := connectFor("continue", "https://gw.example", "qwen2.5-coder-7b", "sk-llmgw-abc", ModelFacts{})
|
|
if !strings.Contains(c.Files[0].Body, "apiBase: https://gw.example/v1") || !strings.Contains(c.Files[0].Body, "apiKey: sk-llmgw-abc") ||
|
|
c.Files[0].Path != "~/.continue/assistants/llmgw.yaml" {
|
|
t.Errorf("continue = %+v", c.Files)
|
|
}
|
|
// The key goes in the environment, not the URL, where it would be kept in
|
|
// shell history and proxy logs.
|
|
if strings.Contains(strings.Split(c.Command, "|")[0], "sk-llmgw-abc") {
|
|
t.Errorf("the key is in the URL: %s", c.Command)
|
|
}
|
|
x := connectFor("codex", "https://gw.example", "qwen2.5-coder-7b", "sk-llmgw-abc", ModelFacts{})
|
|
if strings.Contains(x.Files[0].Body, "sk-llmgw-abc") || !strings.Contains(x.Files[0].Body, `env_key = "LLMGW_API_KEY"`) {
|
|
t.Errorf("codex writes the key into its file: %s", x.Files[0].Body)
|
|
}
|
|
cl := connectFor("cline", "https://gw.example", "m", "sk-llmgw-abc", ModelFacts{})
|
|
if len(cl.Fields) != 4 || cl.Fields[1].Value != "https://gw.example/v1" || cl.Command != "" {
|
|
t.Errorf("cline = %+v", cl)
|
|
}
|
|
}
|
|
|
|
// runScript fetches a setup script and runs it with HOME in a temporary
|
|
// directory, as a person's shell would.
|
|
func runScript(t *testing.T, tool string, env ...string) string {
|
|
t.Helper()
|
|
p := &AccountPages{}
|
|
w := httptest.NewRecorder()
|
|
r := httptest.NewRequest("GET", "http://gw.example/setup/"+tool+"?model=qwen2.5-coder-7b", nil)
|
|
r.SetPathValue("tool", tool)
|
|
p.setupScript(w, r)
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("%d %s", w.Code, w.Body.String())
|
|
}
|
|
home := t.TempDir()
|
|
cmd := exec.Command("sh")
|
|
cmd.Stdin = strings.NewReader(w.Body.String())
|
|
cmd.Env = append([]string{"HOME=" + home, "PATH=" + os.Getenv("PATH")}, env...)
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
t.Fatalf("%v: %s", err, out)
|
|
}
|
|
return home
|
|
}
|
|
|
|
func TestTheContinueScriptWritesItsOwnFileWithTheKey(t *testing.T) {
|
|
home := runScript(t, "continue", "LLMGW_KEY=sk-llmgw-xyz")
|
|
b, err := os.ReadFile(filepath.Join(home, ".continue", "assistants", "llmgw.yaml"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(string(b), "apiKey: sk-llmgw-xyz") || strings.Contains(string(b), "__LLMGW_KEY__") {
|
|
t.Errorf("wrote %s", b)
|
|
}
|
|
if fi, _ := os.Stat(filepath.Join(home, ".continue", "assistants", "llmgw.yaml")); fi.Mode().Perm()&0o077 != 0 {
|
|
t.Errorf("a file holding a key is readable by others: %v", fi.Mode())
|
|
}
|
|
}
|
|
|
|
func TestTheCodexScriptKeepsTheRestOfTheConfig(t *testing.T) {
|
|
// Run twice over a config with the person's own settings: theirs stay,
|
|
// and ours appear once.
|
|
p := &AccountPages{}
|
|
w := httptest.NewRecorder()
|
|
r := httptest.NewRequest("GET", "http://gw.example/setup/codex?model=qwen2.5-coder-7b", nil)
|
|
r.SetPathValue("tool", "codex")
|
|
p.setupScript(w, r)
|
|
home := t.TempDir()
|
|
if err := os.MkdirAll(filepath.Join(home, ".codex"), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
mine := "model = \"o3\"\n\n[mcp_servers.docs]\ncommand = \"docs-mcp\"\n"
|
|
cfg := filepath.Join(home, ".codex", "config.toml")
|
|
if err := os.WriteFile(cfg, []byte(mine), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for range 2 {
|
|
cmd := exec.Command("sh")
|
|
cmd.Stdin = strings.NewReader(w.Body.String())
|
|
cmd.Env = []string{"HOME=" + home, "PATH=" + os.Getenv("PATH")}
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
t.Fatalf("%v: %s", err, out)
|
|
}
|
|
}
|
|
b, _ := os.ReadFile(cfg)
|
|
got := string(b)
|
|
if !strings.Contains(got, `model = "o3"`) || !strings.Contains(got, "[mcp_servers.docs]") {
|
|
t.Errorf("the person's settings were lost:\n%s", got)
|
|
}
|
|
if strings.Count(got, "[model_providers.llmgw]") != 1 || strings.Count(got, "[profiles.llmgw]") != 1 {
|
|
t.Errorf("ours should be there once:\n%s", got)
|
|
}
|
|
if backups, _ := filepath.Glob(cfg + ".bak.*"); len(backups) == 0 {
|
|
t.Error("no copy was kept of the file as it was")
|
|
}
|
|
}
|
|
|
|
func TestASetupScriptRefusesANameAShellWouldRead(t *testing.T) {
|
|
p := &AccountPages{}
|
|
w := httptest.NewRecorder()
|
|
r := httptest.NewRequest("GET", "http://gw.example/setup/codex?model=$(rm%20-rf%20~)", nil)
|
|
r.SetPathValue("tool", "codex")
|
|
p.setupScript(w, r)
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("%d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestContinueIsToldTheModelsContextAndWhetherItCanUseTools(t *testing.T) {
|
|
// Told a context far larger than the gateway allows, Continue sends
|
|
// prompts that are refused; told a model uses tools when its calls come
|
|
// back as text, its agent stalls.
|
|
agent := continueYAML("https://gw.example", "coder-30b", "k", ModelFacts{ContextLength: 32768, Tools: true})
|
|
for _, want := range []string{"tool_use", "contextLength: 32768", "maxTokens: 4096"} {
|
|
if !strings.Contains(agent, want) {
|
|
t.Errorf("%q missing:\n%s", want, agent)
|
|
}
|
|
}
|
|
chat := continueYAML("https://gw.example", "coder", "k", ModelFacts{ContextLength: 4096})
|
|
if strings.Contains(chat, "tool_use") || !strings.Contains(chat, "contextLength: 4096") ||
|
|
!strings.Contains(chat, "maxTokens: 1024") || !strings.Contains(chat, "its agent cannot act") {
|
|
t.Errorf("a model without tools:\n%s", chat)
|
|
}
|
|
if unknown := continueYAML("https://gw.example", "m", "k", ModelFacts{}); strings.Contains(unknown, "contextLength") {
|
|
t.Errorf("a context nobody knows was written down:\n%s", unknown)
|
|
}
|
|
}
|
|
|
|
func TestContinueNeverAsksForMoreThanTheGatewayAllows(t *testing.T) {
|
|
// Asked for more than max_output, the gateway refuses the request.
|
|
y := continueYAML("https://gw.example", "m", "k", ModelFacts{ContextLength: 32768, MaxOutput: 2048, Tools: true})
|
|
if !strings.Contains(y, "maxTokens: 2048") {
|
|
t.Errorf("not capped at max_output:\n%s", y)
|
|
}
|
|
}
|