mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-13 12:14:40 +00:00
fix(providers): send Kimi headers when listing models
This commit is contained in:
1 parent
cc9334fdd9
commit
540dc11301
3 files changed
+97
-8
No files matched your search
@@ -15,8 +15,8 @@ import (
|
||||
|
||||
// ModelInfo is a normalized model entry returned by the list-models endpoint.
|
||||
type ModelInfo struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name,omitempty"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Reasoning *providers.ReasoningCapability `json:"reasoning,omitempty"`
|
||||
}
|
||||
|
||||
@@ -109,11 +109,8 @@ func (h *ProvidersHandler) handleListProviderModels(w http.ResponseWriter, r *ht
|
||||
models = minimaxModels()
|
||||
default:
|
||||
// All other types use OpenAI-compatible /models endpoint
|
||||
apiBase := strings.TrimRight(h.resolveAPIBase(p), "/")
|
||||
if apiBase == "" {
|
||||
apiBase = "https://api.openai.com/v1"
|
||||
}
|
||||
models, err = fetchOpenAIModels(ctx, apiBase, p.APIKey)
|
||||
apiBase := openAIModelsAPIBase(p.ProviderType, h.resolveAPIBase(p))
|
||||
models, err = fetchOpenAIModels(ctx, apiBase, p.APIKey, openAIModelsExtraHeaders(p.ProviderType))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -126,6 +123,28 @@ func (h *ProvidersHandler) handleListProviderModels(w http.ResponseWriter, r *ht
|
||||
respond(withReasoningCapabilities(models))
|
||||
}
|
||||
|
||||
func openAIModelsAPIBase(providerType, apiBase string) string {
|
||||
base := strings.TrimRight(apiBase, "/")
|
||||
if base != "" {
|
||||
return base
|
||||
}
|
||||
switch providerType {
|
||||
case store.ProviderKimiCoding:
|
||||
return store.KimiCodingDefaultAPIBase
|
||||
default:
|
||||
return "https://api.openai.com/v1"
|
||||
}
|
||||
}
|
||||
|
||||
func openAIModelsExtraHeaders(providerType string) map[string]string {
|
||||
if providerType != store.ProviderKimiCoding {
|
||||
return nil
|
||||
}
|
||||
return map[string]string{
|
||||
"User-Agent": store.KimiCodingRequiredUserAgent,
|
||||
}
|
||||
}
|
||||
|
||||
func reasoningDefaultsForModels(
|
||||
settings []byte,
|
||||
models []ModelInfo,
|
||||
|
||||
@@ -91,12 +91,15 @@ func fetchGeminiModels(ctx context.Context, apiKey string) ([]ModelInfo, error)
|
||||
}
|
||||
|
||||
// fetchOpenAIModels calls an OpenAI-compatible /models endpoint.
|
||||
func fetchOpenAIModels(ctx context.Context, apiBase, apiKey string) ([]ModelInfo, error) {
|
||||
func fetchOpenAIModels(ctx context.Context, apiBase, apiKey string, extraHeaders map[string]string) ([]ModelInfo, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", apiBase+"/models", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+apiKey)
|
||||
for k, v := range extraHeaders {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
|
||||
@@ -179,6 +179,73 @@ func TestProvidersHandlerListProviderModelsOpenAICompatAnnotatesKnownModels(t *t
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvidersHandlerListProviderModelsKimiCodingSendsRequiredUserAgent(t *testing.T) {
|
||||
token := setupProvidersAdminToken(t)
|
||||
var capturedAuth, capturedUserAgent string
|
||||
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
capturedAuth = r.Header.Get("Authorization")
|
||||
capturedUserAgent = r.Header.Get("User-Agent")
|
||||
if r.URL.Path != "/models" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"data": []map[string]string{
|
||||
{"id": store.KimiCodingDefaultModel},
|
||||
},
|
||||
})
|
||||
}))
|
||||
t.Cleanup(upstream.Close)
|
||||
|
||||
providerStore := newMockProviderStore()
|
||||
provider := &store.LLMProviderData{
|
||||
BaseModel: store.BaseModel{ID: uuid.New()},
|
||||
Name: "kimi-coding",
|
||||
ProviderType: store.ProviderKimiCoding,
|
||||
APIBase: upstream.URL,
|
||||
APIKey: "kimi-key",
|
||||
Enabled: true,
|
||||
}
|
||||
if err := providerStore.CreateProvider(t.Context(), provider); err != nil {
|
||||
t.Fatalf("CreateProvider() error = %v", err)
|
||||
}
|
||||
|
||||
handler := NewProvidersHandler(providerStore, newMockSecretsStore(), nil, "")
|
||||
mux := http.NewServeMux()
|
||||
handler.RegisterRoutes(mux)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/v1/providers/"+provider.ID.String()+"/models", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
w := httptest.NewRecorder()
|
||||
mux.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status code = %d, want %d, body=%s", w.Code, http.StatusOK, w.Body.String())
|
||||
}
|
||||
if capturedAuth != "Bearer kimi-key" {
|
||||
t.Fatalf("Authorization = %q, want Bearer kimi-key", capturedAuth)
|
||||
}
|
||||
if capturedUserAgent != store.KimiCodingRequiredUserAgent {
|
||||
t.Fatalf("User-Agent = %q, want %q", capturedUserAgent, store.KimiCodingRequiredUserAgent)
|
||||
}
|
||||
var result ProviderModelsResponse
|
||||
if err := json.NewDecoder(w.Body).Decode(&result); err != nil {
|
||||
t.Fatalf("Decode() error = %v", err)
|
||||
}
|
||||
if len(result.Models) != 1 || result.Models[0].ID != store.KimiCodingDefaultModel {
|
||||
t.Fatalf("models = %#v, want %q", result.Models, store.KimiCodingDefaultModel)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenAIModelsAPIBaseDefaultsKimiCoding(t *testing.T) {
|
||||
if got := openAIModelsAPIBase(store.ProviderKimiCoding, ""); got != store.KimiCodingDefaultAPIBase {
|
||||
t.Fatalf("Kimi default api base = %q, want %q", got, store.KimiCodingDefaultAPIBase)
|
||||
}
|
||||
if got := openAIModelsAPIBase(store.ProviderOpenAICompat, ""); got != "https://api.openai.com/v1" {
|
||||
t.Fatalf("OpenAI compat default api base = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestProvidersHandlerListProviderModelsOllamaRichMetadata verifies that the
|
||||
// handler fetches /api/tags from Ollama and maps rich details (family,
|
||||
// parameter_size, quantization_level) into the display name.
|
||||
|
||||
Reference in new issue
Block a user