chore: initial commit
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
discoveryTimeout = 30 * time.Second
|
||||
discoveryMaxBody = 1 << 20
|
||||
discoveryMaxPages = 20
|
||||
)
|
||||
|
||||
func (s *Server) discoverModels(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
errJSON(w, 405, "method not allowed")
|
||||
return
|
||||
}
|
||||
var c channel
|
||||
if decode(w, r, &c) != nil {
|
||||
errJSON(w, 400, "invalid channel")
|
||||
return
|
||||
}
|
||||
u, e := url.Parse(c.BaseURL)
|
||||
if e != nil || (u.Scheme != "http" && u.Scheme != "https") || u.Hostname() == "" || u.User != nil || u.RawQuery != "" || u.ForceQuery || strings.Contains(c.BaseURL, "#") || (c.Protocol != "openai-chat" && c.Protocol != "openai-responses" && c.Protocol != "anthropic") {
|
||||
errJSON(w, 400, "invalid channel")
|
||||
return
|
||||
}
|
||||
if c.ID != "" {
|
||||
var old string
|
||||
e = s.db.QueryRowContext(r.Context(), "SELECT api_key FROM channels WHERE id=?", c.ID).Scan(&old)
|
||||
if e == sql.ErrNoRows {
|
||||
errJSON(w, 404, "not found")
|
||||
return
|
||||
}
|
||||
if e != nil {
|
||||
errJSON(w, 500, "database error")
|
||||
return
|
||||
}
|
||||
if c.APIKey == "" {
|
||||
c.APIKey = old
|
||||
}
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(r.Context(), discoveryTimeout)
|
||||
defer cancel()
|
||||
models, e := s.fetchModels(ctx, c)
|
||||
if e != nil {
|
||||
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
|
||||
errJSON(w, 504, "model discovery timed out")
|
||||
} else {
|
||||
errJSON(w, 502, "model discovery failed")
|
||||
}
|
||||
return
|
||||
}
|
||||
writeJSON(w, map[string][]string{"models": models})
|
||||
}
|
||||
|
||||
func (s *Server) fetchModels(ctx context.Context, c channel) ([]string, error) {
|
||||
// Share the bounded transport, but never forward credentials across redirects.
|
||||
client := &http.Client{Transport: s.client.Transport, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
|
||||
endpoint := strings.TrimRight(c.BaseURL, "/") + "/models"
|
||||
if c.Protocol == "anthropic" {
|
||||
endpoint = strings.TrimRight(c.BaseURL, "/") + "/v1/models"
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
cursors := map[string]bool{}
|
||||
after := ""
|
||||
for page := 0; page < discoveryMaxPages; page++ {
|
||||
u := endpoint
|
||||
if after != "" {
|
||||
u += "?" + url.Values{"after_id": {after}}.Encode()
|
||||
}
|
||||
req, e := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
req.Header.Set("Accept", "application/json")
|
||||
if c.Protocol == "anthropic" {
|
||||
req.Header.Set("x-api-key", c.APIKey)
|
||||
req.Header.Set("anthropic-version", "2023-06-01")
|
||||
} else {
|
||||
req.Header.Set("Authorization", "Bearer "+c.APIKey)
|
||||
}
|
||||
res, e := client.Do(req)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
res.Body.Close()
|
||||
return nil, errors.New("upstream status")
|
||||
}
|
||||
body, e := io.ReadAll(io.LimitReader(res.Body, discoveryMaxBody+1))
|
||||
res.Body.Close()
|
||||
if e != nil || len(body) > discoveryMaxBody {
|
||||
return nil, errors.New("upstream body")
|
||||
}
|
||||
var result struct {
|
||||
Data []struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"data"`
|
||||
HasMore bool `json:"has_more"`
|
||||
LastID string `json:"last_id"`
|
||||
}
|
||||
if json.Unmarshal(body, &result) != nil || result.Data == nil {
|
||||
return nil, errors.New("upstream models")
|
||||
}
|
||||
for _, model := range result.Data {
|
||||
if strings.TrimSpace(model.ID) == "" {
|
||||
return nil, errors.New("upstream model id")
|
||||
}
|
||||
seen[model.ID] = true
|
||||
}
|
||||
if c.Protocol != "anthropic" || !result.HasMore {
|
||||
models := make([]string, 0, len(seen))
|
||||
for id := range seen {
|
||||
models = append(models, id)
|
||||
}
|
||||
sort.Strings(models)
|
||||
return models, nil
|
||||
}
|
||||
if result.LastID == "" || cursors[result.LastID] {
|
||||
return nil, errors.New("upstream pagination")
|
||||
}
|
||||
cursors[result.LastID] = true
|
||||
after = result.LastID
|
||||
}
|
||||
return nil, errors.New("upstream page limit")
|
||||
}
|
||||
Reference in New Issue
Block a user