mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-15 10:14:51 +00:00
fix(http): provider verify ping mode on empty body (#1034)
This commit is contained in:
1 parent
ed7c371cb2
commit
dfcb897954
2 files changed
+33
-8
No files matched your search
@@ -543,9 +543,9 @@
|
||||
"post": {
|
||||
"tags": ["Providers"],
|
||||
"summary": "Verify provider connection",
|
||||
"description": "Test provider connectivity with a minimal LLM call.",
|
||||
"description": "Empty body or omitted model = ping mode (provider registered/reachable check). With model = full chat-verify against the upstream provider.",
|
||||
"parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
|
||||
"requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "model": { "type": "string" } } } } } },
|
||||
"requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object", "properties": { "model": { "type": "string", "description": "Optional. Omit (or send empty body) for ping mode." } } } } } },
|
||||
"responses": { "200": { "description": "Verification result", "content": { "application/json": { "schema": { "type": "object", "properties": { "valid": { "type": "boolean" }, "error": { "type": "string" } } } } } } }
|
||||
}
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -17,6 +18,14 @@ import (
|
||||
"github.com/nextlevelbuilder/goclaw/internal/store"
|
||||
)
|
||||
|
||||
// HandleVerifyProviderForTest invokes the verify handler directly without auth
|
||||
// middleware. Integration tests must inject the desired tenant_id into the
|
||||
// request context before calling. Production code MUST go through RegisterRoutes
|
||||
// so the auth/locale/tenant pipeline runs first.
|
||||
func (h *ProvidersHandler) HandleVerifyProviderForTest(w http.ResponseWriter, r *http.Request) {
|
||||
h.handleVerifyProvider(w, r)
|
||||
}
|
||||
|
||||
// handleVerifyProvider tests a provider+model combination with a minimal LLM call.
|
||||
//
|
||||
// POST /v1/providers/{id}/verify
|
||||
@@ -33,14 +42,17 @@ func (h *ProvidersHandler) handleVerifyProvider(w http.ResponseWriter, r *http.R
|
||||
var req struct {
|
||||
Model string `json:"model"`
|
||||
}
|
||||
// Empty body == ping mode (connectivity check only). Truncated/malformed
|
||||
// JSON still returns 400. io.EOF on Decode unambiguously means no body;
|
||||
// io.ErrUnexpectedEOF is what truncated JSON returns.
|
||||
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<16)).Decode(&req); err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidJSON)})
|
||||
return
|
||||
}
|
||||
if req.Model == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgRequired, "model")})
|
||||
return
|
||||
if !errors.Is(err, io.EOF) {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidJSON)})
|
||||
return
|
||||
}
|
||||
// empty body — req.Model stays "" → pingMode below
|
||||
}
|
||||
pingMode := req.Model == ""
|
||||
|
||||
// Look up provider record from DB to get the provider name
|
||||
p, err := h.store.GetProvider(r.Context(), id)
|
||||
@@ -51,6 +63,10 @@ func (h *ProvidersHandler) handleVerifyProvider(w http.ResponseWriter, r *http.R
|
||||
|
||||
// ACP: verify binary exists on the server (no LLM call needed)
|
||||
if p.ProviderType == store.ProviderACP {
|
||||
if pingMode {
|
||||
writeJSON(w, http.StatusOK, map[string]any{"valid": true})
|
||||
return
|
||||
}
|
||||
binary := p.APIBase
|
||||
if binary == "" {
|
||||
binary = "claude"
|
||||
@@ -70,6 +86,10 @@ func (h *ProvidersHandler) handleVerifyProvider(w http.ResponseWriter, r *http.R
|
||||
|
||||
// Claude CLI: validate model alias locally (no LLM call needed)
|
||||
if p.ProviderType == "claude_cli" {
|
||||
if pingMode {
|
||||
writeJSON(w, http.StatusOK, map[string]any{"valid": true})
|
||||
return
|
||||
}
|
||||
validModels := map[string]bool{"sonnet": true, "opus": true, "haiku": true}
|
||||
if validModels[req.Model] {
|
||||
writeJSON(w, http.StatusOK, map[string]any{"valid": true})
|
||||
@@ -92,6 +112,11 @@ func (h *ProvidersHandler) handleVerifyProvider(w http.ResponseWriter, r *http.R
|
||||
return
|
||||
}
|
||||
|
||||
if pingMode {
|
||||
writeJSON(w, http.StatusOK, map[string]any{"valid": true})
|
||||
return
|
||||
}
|
||||
|
||||
// Non-chat models (image/video generation) can't be verified via Chat API.
|
||||
// Accept them if the provider is reachable (already validated above).
|
||||
if isNonChatModel(req.Model) {
|
||||
|
||||
Reference in new issue
Block a user