v0.51.0 a9d674e

catalog_test.go RawHistoryFolder

1package sdk
2
3import (
4	"context"
5	"crypto/sha256"
6	"encoding/json"
7	"fmt"
8	"io"
9	"net/http"
10	"net/http/httptest"
11	"path/filepath"
12	"strings"
13	"testing"
14	"time"
15
16	"github.com/bnema/zut/packages/provider"
17)
18
19func testCredentialEndpointScope(credential, baseURL string) string {
20	endpoint := strings.TrimRight(strings.TrimSpace(baseURL), "/")
21	if endpoint == "" {
22		endpoint = provider.OpenCodeGoDefaultBaseURL
23	}
24	digest := sha256.Sum256([]byte(credential + "\x00" + endpoint))
25	return fmt.Sprintf("sha256:%x", digest)
26}
27
28func TestNewLoadsCachedOpenCodeGoMetadataBeforeResolving(t *testing.T) {
29	const (
30		apiKey  = "synthetic-opencode-key"
31		modelID = "cached-opencode-model"
32	)
33	home := t.TempDir()
34	t.Setenv("ZUT_HOME", home)
35	provider.SetLiveModels(nil)
36	provider.SetUserModels(nil)
37	t.Cleanup(func() {
38		provider.SetLiveModels(nil)
39		provider.SetUserModels(nil)
40	})
41
42	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
43		if r.URL.Path != "/v1/chat/completions" {
44			http.NotFound(w, r)
45			return
46		}
47		if got := r.Header.Get("authorization"); got != "Bearer "+apiKey {
48			t.Errorf("authorization = %q, want bearer API key", got)
49		}
50		body, err := io.ReadAll(r.Body)
51		if err != nil {
52			t.Errorf("read request: %v", err)
53		}
54		var request struct {
55			Model           string `json:"model"`
56			ReasoningEffort string `json:"reasoning_effort"`
57		}
58		if err := json.Unmarshal(body, &request); err != nil {
59			t.Errorf("decode request: %v", err)
60		}
61		if request.Model != modelID {
62			t.Errorf("request model = %q, want %q", request.Model, modelID)
63		}
64		if request.ReasoningEffort != "minimal" {
65			t.Errorf("request reasoning_effort = %q, want minimal", request.ReasoningEffort)
66		}
67		w.Header().Set("Content-Type", "text/event-stream")
68		_, _ = fmt.Fprintln(w, `data: {"choices":[{"delta":{"content":"ok"},"finish_reason":"stop"}]}`)
69		_, _ = fmt.Fprintln(w)
70		_, _ = fmt.Fprintln(w, "data: [DONE]")
71	}))
72	defer server.Close()
73
74	cached := provider.Model{
75		Provider:           provider.ProviderOpenCodeGo,
76		ID:                 modelID,
77		DisplayName:        "Cached OpenCode model",
78		Reasoning:          true,
79		ReasoningLevelMap:  map[string]string{"minimum": "minimum", "low": "", "medium": "", "high": "", "xhigh": "", "max": ""},
80		ReasoningEffortMap: map[string]string{"minimum": "minimal"},
81		ContextWindow:      128000,
82		MaxOutput:          8192,
83		BaseURL:            server.URL + "/v1",
84		Source:             "live",
85	}
86	if err := provider.SaveCache(filepath.Join(home, "models-cache.json"), provider.ModelCache{
87		Version:                provider.ModelCacheVersion,
88		FetchedAt:              time.Now().UTC(),
89		Models:                 []provider.Model{cached},
90		AuthoritativeProviders: []string{provider.ProviderOpenCodeGo},
91		ProviderScopes:         map[string]string{provider.ProviderOpenCodeGo: testCredentialEndpointScope(apiKey, server.URL+"/v1")},
92	}); err != nil {
93		t.Fatal(err)
94	}
95
96	runtime, err := New(Config{
97		Provider:  provider.ProviderOpenCodeGo,
98		Model:     "  " + modelID + "  ",
99		APIKey:    apiKey,
100		BaseURL:   server.URL + "/v1",
101		Reasoning: "minimum",
102		CWD:       t.TempDir(),
103		NoTools:   true,
104	})
105	if err != nil {
106		t.Fatal(err)
107	}
108	defer runtime.Close()
109
110	models := runtime.ListModels()
111	var found bool
112	for _, model := range models {
113		if model.ID == modelID {
114			found = model.ContextWindow == 128000 && model.MaxOutput == 8192 && model.Reasoning
115		}
116	}
117	if !found {
118		t.Fatalf("SDK did not load cached OpenCode Go metadata: %+v", models)
119	}
120	if err := runtime.SetModel("  " + modelID + "  "); err != nil {
121		t.Fatal(err)
122	}
123	if runtime.Model() != modelID {
124		t.Fatalf("SDK model = %q, want trimmed id", runtime.Model())
125	}
126
127	events, err := runtime.Prompt(context.Background(), "hello", nil)
128	if err != nil {
129		t.Fatal(err)
130	}
131	for event := range events {
132		if event.Type == "error" {
133			t.Fatalf("prompt error: %s", event.Error)
134		}
135	}
136}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close