v0.1.54 7134fb7

discover.go RawHistoryFolder

1package provider
2
3import (
4	"context"
5	"encoding/json"
6	"fmt"
7	"io"
8	"net/http"
9	"strings"
10	"time"
11)
12
13// DiscoverAnthropic lists model ids visible to key on api.anthropic.com.
14// The API returns a paginated list; we page through until has_more is false.
15func DiscoverAnthropic(ctx context.Context, apiKey, baseURL string) ([]Model, error) {
16	if baseURL == "" {
17		baseURL = anthropicDefaultBaseURL
18	}
19	client := &http.Client{Timeout: 15 * time.Second}
20	var out []Model
21	after := ""
22	for {
23		url := strings.TrimRight(baseURL, "/") + "/v1/models?limit=1000"
24		if after != "" {
25			url += "&after_id=" + after
26		}
27		req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
28		if err != nil {
29			return nil, err
30		}
31		req.Header.Set("x-api-key", apiKey)
32		req.Header.Set("anthropic-version", anthropicAPIVersion)
33		resp, err := client.Do(req)
34		if err != nil {
35			return nil, err
36		}
37		body, _ := io.ReadAll(resp.Body)
38		resp.Body.Close()
39		if resp.StatusCode != 200 {
40			return nil, fmt.Errorf("anthropic discover http %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
41		}
42		var page struct {
43			Data []struct {
44				ID          string `json:"id"`
45				DisplayName string `json:"display_name"`
46			} `json:"data"`
47			HasMore bool   `json:"has_more"`
48			LastID  string `json:"last_id"`
49		}
50		if err := json.Unmarshal(body, &page); err != nil {
51			return nil, fmt.Errorf("anthropic discover parse: %w", err)
52		}
53		for _, d := range page.Data {
54			out = append(out, Model{
55				Provider:    "anthropic",
56				ID:          d.ID,
57				DisplayName: d.DisplayName,
58				Source:      "live",
59			})
60		}
61		if !page.HasMore || page.LastID == "" {
62			break
63		}
64		after = page.LastID
65	}
66	return out, nil
67}
68
69// DiscoverOpenAI lists model ids visible to key on api.openai.com.
70func DiscoverOpenAI(ctx context.Context, apiKey, baseURL string) ([]Model, error) {
71	if baseURL == "" {
72		baseURL = openaiDefaultBaseURL
73	}
74	client := &http.Client{Timeout: 15 * time.Second}
75	url := strings.TrimRight(baseURL, "/") + "/v1/models"
76	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
77	if err != nil {
78		return nil, err
79	}
80	req.Header.Set("authorization", "Bearer "+apiKey)
81	resp, err := client.Do(req)
82	if err != nil {
83		return nil, err
84	}
85	defer resp.Body.Close()
86	body, _ := io.ReadAll(resp.Body)
87	if resp.StatusCode != 200 {
88		return nil, fmt.Errorf("openai discover http %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
89	}
90	var page struct {
91		Data []struct {
92			ID string `json:"id"`
93		} `json:"data"`
94	}
95	if err := json.Unmarshal(body, &page); err != nil {
96		return nil, fmt.Errorf("openai discover parse: %w", err)
97	}
98	var out []Model
99	for _, d := range page.Data {
100		// Keep only chat-capable families. OpenAI's /v1/models returns
101		// everything including embeddings, TTS, DALL-E, etc.
102		if !looksLikeChatModel(d.ID) {
103			continue
104		}
105		out = append(out, Model{
106			Provider:    "openai",
107			ID:          d.ID,
108			DisplayName: d.ID,
109			Source:      "live",
110		})
111	}
112	return out, nil
113}
114
115// looksLikeChatModel returns true for OpenAI ids that can plausibly be
116// used with the chat/completions endpoint. Errs on the side of inclusion.
117func looksLikeChatModel(id string) bool {
118	switch {
119	case strings.HasPrefix(id, "gpt-"):
120		return true
121	case strings.HasPrefix(id, "o1"):
122		return true
123	case strings.HasPrefix(id, "o3"):
124		return true
125	case strings.HasPrefix(id, "o4"):
126		return true
127	case strings.HasPrefix(id, "o5"):
128		return true
129	case strings.HasPrefix(id, "chatgpt-"):
130		return true
131	}
132	return false
133}

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