Changeset 0.28.8 (#194)

This commit is contained in:
2026-03-15 23:43:36 +00:00
parent 3237d55e0c
commit 128cbb8174
25 changed files with 1157 additions and 863 deletions

View File

@@ -2,6 +2,7 @@ package handlers
import (
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
@@ -665,6 +666,10 @@ func (h *AdminHandler) FetchModels(c *gin.Context) {
added, updated, fetched, err := h.fetchModelsForProvider(c, cfg)
if err != nil {
if errors.Is(err, ErrUpstreamTimeout) {
c.JSON(http.StatusGatewayTimeout, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusBadGateway, gin.H{"error": "failed to fetch models: " + err.Error()})
return
}

View File

@@ -1,6 +1,7 @@
package handlers
import (
"errors"
"log"
"net/http"
@@ -279,6 +280,10 @@ func (h *ProviderConfigHandler) FetchModels(c *gin.Context) {
result, err := syncAndEnableProviderModels(c.Request.Context(), h.stores, cfg, apiKey)
if err != nil {
if errors.Is(err, ErrUpstreamTimeout) {
c.JSON(http.StatusGatewayTimeout, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusBadGateway, gin.H{"error": "failed to fetch models: " + err.Error()})
return
}

View File

@@ -3,14 +3,35 @@ package handlers
import (
"context"
"encoding/json"
"errors"
"fmt"
"log"
"os"
"strconv"
"time"
"git.gobha.me/xcaliber/chat-switchboard/models"
"git.gobha.me/xcaliber/chat-switchboard/providers"
"git.gobha.me/xcaliber/chat-switchboard/store"
)
// providerSyncTimeout is the maximum time allowed for an outbound provider
// HTTP call during model sync. Configurable via PROVIDER_SYNC_TIMEOUT env
// var (seconds). Default 30s.
var providerSyncTimeout = func() time.Duration {
if v := os.Getenv("PROVIDER_SYNC_TIMEOUT"); v != "" {
if n, err := strconv.Atoi(v); err == nil && n > 0 {
return time.Duration(n) * time.Second
}
}
return 30 * time.Second
}()
// ErrUpstreamTimeout is returned when a provider API call exceeds the
// configured timeout. Handlers use this to distinguish upstream timeouts
// from other errors and return 504 instead of 502.
var ErrUpstreamTimeout = errors.New("upstream timeout")
// syncResult holds the outcome of a model sync operation.
type syncResult struct {
Added int `json:"added"`
@@ -62,8 +83,17 @@ func syncProviderModels(ctx context.Context, stores store.Stores, cfg *models.Pr
}
}
provModels, err := prov.ListModels(ctx, provCfg)
// Wrap context with timeout for the outbound provider call.
// All provider ListModels implementations use http.NewRequestWithContext,
// so the deadline propagates to the HTTP transport automatically.
syncCtx, cancel := context.WithTimeout(ctx, providerSyncTimeout)
defer cancel()
provModels, err := prov.ListModels(syncCtx, provCfg)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return syncResult{}, fmt.Errorf("%w: %s", ErrUpstreamTimeout, cfg.Provider)
}
return syncResult{}, err
}