fix(http): provider verify ping mode on empty body (#1034)

This commit is contained in:
viettranx committed 2026-04-27 12:00:09 +07:00
1 parent ed7c371cb2
commit dfcb897954
2 files changed
+33 -8

No files matched your search

+2 -2
View File
@@ -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" } } } } } } }
}
},
+31 -6
View File
@@ -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) {