mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-10-24 16:45:45 +08:00
fix(xai): add proxy support and semver validation to Grok CLI updater
- Pass configured proxy URL to the npm registry updater client - Validate fetched versions against strict semver and minimum server version floor (`1.0.13`) - Update default fallback client version to `1.0.46` - Manage background updater lifecycle and context via service lifecycle initialization
This commit is contained in:
@@ -32,7 +32,6 @@ import (
|
||||
"github.com/router-for-me/CLIProxyAPI/v8/internal/pluginhost"
|
||||
"github.com/router-for-me/CLIProxyAPI/v8/internal/redisqueue"
|
||||
"github.com/router-for-me/CLIProxyAPI/v8/internal/registry"
|
||||
"github.com/router-for-me/CLIProxyAPI/v8/internal/runtime/executor"
|
||||
"github.com/router-for-me/CLIProxyAPI/v8/internal/safemode"
|
||||
"github.com/router-for-me/CLIProxyAPI/v8/internal/store"
|
||||
_ "github.com/router-for-me/CLIProxyAPI/v8/internal/translator"
|
||||
@@ -752,7 +751,6 @@ func main() {
|
||||
// Standalone mode: start an embedded local server and connect TUI client to it.
|
||||
managementasset.StartAutoUpdater(context.Background(), configFilePath)
|
||||
misc.StartAntigravityVersionUpdater(context.Background())
|
||||
executor.StartXAIVersionUpdater(context.Background())
|
||||
registry.SetLocalModelCatalogs(localModel)
|
||||
hook := tui.NewLogHook(2000)
|
||||
hook.SetFormatter(&logging.LogFormatter{})
|
||||
@@ -828,7 +826,6 @@ func main() {
|
||||
// Start the main proxy service
|
||||
managementasset.StartAutoUpdater(context.Background(), configFilePath)
|
||||
misc.StartAntigravityVersionUpdater(context.Background())
|
||||
executor.StartXAIVersionUpdater(context.Background())
|
||||
registry.SetLocalModelCatalogs(localModel)
|
||||
cmd.StartServiceWithPluginHost(cfg, configFilePath, password, pluginHost, serverOptions...)
|
||||
}
|
||||
|
||||
@@ -6,18 +6,23 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v8/internal/config"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultXAIFallbackClientVersion is the fallback Grok CLI version when npm registry resolution fails.
|
||||
DefaultXAIFallbackClientVersion = "1.0.50"
|
||||
// DefaultXAIFallbackClientVersion is the stable Grok CLI version used when npm
|
||||
// registry resolution fails. dist-tag latest is 1.0.46; 1.0.50 is the alpha tag.
|
||||
DefaultXAIFallbackClientVersion = "1.0.46"
|
||||
// XAIClientVersionServerFloor is the minimum Grok CLI version cli-chat-proxy accepts.
|
||||
// Older clients are rejected with HTTP 426 (#6249).
|
||||
XAIClientVersionServerFloor = "1.0.13"
|
||||
// XAIVersionRefreshInterval is the periodic interval to check for Grok CLI updates from npm.
|
||||
XAIVersionRefreshInterval = 3 * time.Hour
|
||||
// XAIVersionFetchTimeout is the maximum duration for a single npm registry lookup.
|
||||
@@ -30,12 +35,14 @@ var (
|
||||
|
||||
var (
|
||||
cachedXAIClientVersion = DefaultXAIFallbackClientVersion
|
||||
xaiVersionProxyURL string
|
||||
xaiClientVersionMu sync.RWMutex
|
||||
xaiVersionUpdaterOnce sync.Once
|
||||
xaiUpdaterCancel context.CancelFunc
|
||||
xaiVersionRefreshed chan struct{}
|
||||
)
|
||||
|
||||
// GetXAIClientVersion returns the current Grok CLI client version.
|
||||
// If the background updater has fetched a newer version from npm, it returns that version;
|
||||
// If the background updater has fetched a newer acceptable version from npm, it returns that version;
|
||||
// otherwise it returns DefaultXAIFallbackClientVersion.
|
||||
func GetXAIClientVersion() string {
|
||||
xaiClientVersionMu.RLock()
|
||||
@@ -44,19 +51,26 @@ func GetXAIClientVersion() string {
|
||||
}
|
||||
|
||||
// StartXAIVersionUpdater starts a background goroutine that periodically refreshes the Grok CLI version from npm.
|
||||
// A later call cancels the previous goroutine and binds the new service context and proxy URL.
|
||||
// It executes a single fetch on startup and then polls every 3 hours without multiple retries on failure.
|
||||
func StartXAIVersionUpdater(ctx context.Context) {
|
||||
xaiVersionUpdaterOnce.Do(func() {
|
||||
go runXAIVersionUpdater(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
func runXAIVersionUpdater(ctx context.Context) {
|
||||
func StartXAIVersionUpdater(ctx context.Context, proxyURL string) {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
runCtx, cancel := context.WithCancel(ctx)
|
||||
|
||||
// Fetch once on startup
|
||||
xaiClientVersionMu.Lock()
|
||||
if xaiUpdaterCancel != nil {
|
||||
xaiUpdaterCancel()
|
||||
}
|
||||
xaiUpdaterCancel = cancel
|
||||
xaiVersionProxyURL = strings.TrimSpace(proxyURL)
|
||||
xaiClientVersionMu.Unlock()
|
||||
|
||||
go runXAIVersionUpdater(runCtx)
|
||||
}
|
||||
|
||||
func runXAIVersionUpdater(ctx context.Context) {
|
||||
refreshXAIClientVersion(ctx)
|
||||
|
||||
ticker := time.NewTicker(XAIVersionRefreshInterval)
|
||||
@@ -75,25 +89,28 @@ func runXAIVersionUpdater(ctx context.Context) {
|
||||
}
|
||||
|
||||
func refreshXAIClientVersion(ctx context.Context) {
|
||||
defer notifyXAIVersionRefreshed()
|
||||
|
||||
version, errFetch := FetchXAINPMLatestVersion(ctx, nil)
|
||||
if errFetch != nil {
|
||||
log.WithError(errFetch).Warn("failed to fetch latest Grok CLI version from npm, keeping fallback/cached version")
|
||||
return
|
||||
}
|
||||
|
||||
if version == "" {
|
||||
log.Warn("fetched empty Grok CLI version from npm, keeping fallback/cached version")
|
||||
return
|
||||
}
|
||||
|
||||
xaiClientVersionMu.Lock()
|
||||
cachedXAIClientVersion = version
|
||||
changed := cachedXAIClientVersion != version
|
||||
if changed {
|
||||
cachedXAIClientVersion = version
|
||||
}
|
||||
xaiClientVersionMu.Unlock()
|
||||
|
||||
log.WithField("version", version).Info("updated Grok CLI client version from npm")
|
||||
if changed {
|
||||
log.WithField("version", version).Info("updated Grok CLI client version from npm")
|
||||
}
|
||||
}
|
||||
|
||||
// FetchXAINPMLatestVersion performs a single request to the npm registry to query the latest version of @xai-official/grok.
|
||||
// The returned version is a strict numeric semver at or above XAIClientVersionServerFloor.
|
||||
func FetchXAINPMLatestVersion(ctx context.Context, client *http.Client) (string, error) {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
@@ -114,7 +131,7 @@ func FetchXAINPMLatestVersion(ctx context.Context, client *http.Client) (string,
|
||||
req.Header.Set("User-Agent", "CLIProxyAPI")
|
||||
|
||||
if client == nil {
|
||||
client = &http.Client{Timeout: XAIVersionFetchTimeout}
|
||||
client = xaiVersionHTTPClient()
|
||||
}
|
||||
|
||||
resp, errDo := client.Do(req)
|
||||
@@ -140,12 +157,88 @@ func FetchXAINPMLatestVersion(ctx context.Context, client *http.Client) (string,
|
||||
if version == "" {
|
||||
return "", errors.New("version not found in npm response")
|
||||
}
|
||||
if !acceptableXAIClientVersion(version) {
|
||||
return "", fmt.Errorf("npm registry returned unacceptable Grok CLI version %s", strconv.Quote(version))
|
||||
}
|
||||
|
||||
return version, nil
|
||||
}
|
||||
|
||||
func xaiVersionHTTPClient() *http.Client {
|
||||
xaiClientVersionMu.RLock()
|
||||
proxyURL := xaiVersionProxyURL
|
||||
xaiClientVersionMu.RUnlock()
|
||||
if strings.TrimSpace(proxyURL) == "" {
|
||||
return &http.Client{Timeout: XAIVersionFetchTimeout}
|
||||
}
|
||||
return NewProxyAwareHTTPClient(context.Background(), &config.Config{SDKConfig: config.SDKConfig{ProxyURL: proxyURL}}, nil, XAIVersionFetchTimeout)
|
||||
}
|
||||
|
||||
func acceptableXAIClientVersion(version string) bool {
|
||||
if !isStrictXAISemver(version) {
|
||||
return false
|
||||
}
|
||||
return xaiVersionAtLeast(version, XAIClientVersionServerFloor)
|
||||
}
|
||||
|
||||
func isStrictXAISemver(version string) bool {
|
||||
parts := strings.Split(version, ".")
|
||||
if len(parts) != 3 {
|
||||
return false
|
||||
}
|
||||
for _, part := range parts {
|
||||
if part == "" {
|
||||
return false
|
||||
}
|
||||
for _, ch := range part {
|
||||
if ch < '0' || ch > '9' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func xaiVersionAtLeast(got, floor string) bool {
|
||||
gotParts := strings.Split(got, ".")
|
||||
floorParts := strings.Split(floor, ".")
|
||||
for i := 0; i < len(gotParts) || i < len(floorParts); i++ {
|
||||
var g, f int
|
||||
var errG, errF error
|
||||
if i < len(gotParts) {
|
||||
g, errG = strconv.Atoi(gotParts[i])
|
||||
if errG != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if i < len(floorParts) {
|
||||
f, errF = strconv.Atoi(floorParts[i])
|
||||
if errF != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if g != f {
|
||||
return g > f
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func notifyXAIVersionRefreshed() {
|
||||
xaiClientVersionMu.RLock()
|
||||
ch := xaiVersionRefreshed
|
||||
xaiClientVersionMu.RUnlock()
|
||||
if ch == nil {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case ch <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// OverrideXAINPMRegistryURLForTest overrides the npm registry URL for testing purposes.
|
||||
func OverrideXAINPMRegistryURLForTest(t *testing.T, url string) func() {
|
||||
func OverrideXAINPMRegistryURLForTest(url string) func() {
|
||||
xaiClientVersionMu.Lock()
|
||||
oldURL := xaiNPMRegistryURL
|
||||
xaiNPMRegistryURL = url
|
||||
@@ -158,7 +251,7 @@ func OverrideXAINPMRegistryURLForTest(t *testing.T, url string) func() {
|
||||
}
|
||||
|
||||
// SetXAIClientVersionForTest sets the cached Grok CLI version directly for testing purposes.
|
||||
func SetXAIClientVersionForTest(t *testing.T, version string) func() {
|
||||
func SetXAIClientVersionForTest(version string) func() {
|
||||
xaiClientVersionMu.Lock()
|
||||
old := cachedXAIClientVersion
|
||||
cachedXAIClientVersion = version
|
||||
@@ -170,9 +263,15 @@ func SetXAIClientVersionForTest(t *testing.T, version string) func() {
|
||||
}
|
||||
}
|
||||
|
||||
// ResetXAIVersionUpdaterOnceForTest resets the sync.Once for testing updater initialization.
|
||||
func ResetXAIVersionUpdaterOnceForTest(t *testing.T) {
|
||||
// SetXAIVersionRefreshedHookForTest receives one signal after each refresh attempt.
|
||||
func SetXAIVersionRefreshedHookForTest(ch chan struct{}) func() {
|
||||
xaiClientVersionMu.Lock()
|
||||
xaiVersionUpdaterOnce = sync.Once{}
|
||||
old := xaiVersionRefreshed
|
||||
xaiVersionRefreshed = ch
|
||||
xaiClientVersionMu.Unlock()
|
||||
return func() {
|
||||
xaiClientVersionMu.Lock()
|
||||
xaiVersionRefreshed = old
|
||||
xaiClientVersionMu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,16 @@ package helps
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestGetXAIClientVersionDefault(t *testing.T) {
|
||||
restore := SetXAIClientVersionForTest(t, DefaultXAIFallbackClientVersion)
|
||||
restore := SetXAIClientVersionForTest(DefaultXAIFallbackClientVersion)
|
||||
defer restore()
|
||||
|
||||
if got := GetXAIClientVersion(); got != DefaultXAIFallbackClientVersion {
|
||||
@@ -27,7 +29,7 @@ func TestFetchXAINPMLatestVersionSuccess(t *testing.T) {
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
restore := OverrideXAINPMRegistryURLForTest(t, server.URL)
|
||||
restore := OverrideXAINPMRegistryURLForTest(server.URL)
|
||||
defer restore()
|
||||
|
||||
version, err := FetchXAINPMLatestVersion(context.Background(), server.Client())
|
||||
@@ -45,7 +47,7 @@ func TestFetchXAINPMLatestVersionHttpError(t *testing.T) {
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
restore := OverrideXAINPMRegistryURLForTest(t, server.URL)
|
||||
restore := OverrideXAINPMRegistryURLForTest(server.URL)
|
||||
defer restore()
|
||||
|
||||
_, err := FetchXAINPMLatestVersion(context.Background(), server.Client())
|
||||
@@ -61,7 +63,7 @@ func TestFetchXAINPMLatestVersionMissingVersion(t *testing.T) {
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
restore := OverrideXAINPMRegistryURLForTest(t, server.URL)
|
||||
restore := OverrideXAINPMRegistryURLForTest(server.URL)
|
||||
defer restore()
|
||||
|
||||
_, err := FetchXAINPMLatestVersion(context.Background(), server.Client())
|
||||
@@ -71,7 +73,7 @@ func TestFetchXAINPMLatestVersionMissingVersion(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRefreshXAIClientVersionKeepsFallbackOnError(t *testing.T) {
|
||||
restoreVersion := SetXAIClientVersionForTest(t, "1.0.50")
|
||||
restoreVersion := SetXAIClientVersionForTest("1.0.50")
|
||||
defer restoreVersion()
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -79,7 +81,7 @@ func TestRefreshXAIClientVersionKeepsFallbackOnError(t *testing.T) {
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
restoreURL := OverrideXAINPMRegistryURLForTest(t, server.URL)
|
||||
restoreURL := OverrideXAINPMRegistryURLForTest(server.URL)
|
||||
defer restoreURL()
|
||||
|
||||
refreshXAIClientVersion(context.Background())
|
||||
@@ -90,7 +92,7 @@ func TestRefreshXAIClientVersionKeepsFallbackOnError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRefreshXAIClientVersionUpdatesOnSuccess(t *testing.T) {
|
||||
restoreVersion := SetXAIClientVersionForTest(t, "1.0.50")
|
||||
restoreVersion := SetXAIClientVersionForTest("1.0.50")
|
||||
defer restoreVersion()
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -99,7 +101,7 @@ func TestRefreshXAIClientVersionUpdatesOnSuccess(t *testing.T) {
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
restoreURL := OverrideXAINPMRegistryURLForTest(t, server.URL)
|
||||
restoreURL := OverrideXAINPMRegistryURLForTest(server.URL)
|
||||
defer restoreURL()
|
||||
|
||||
refreshXAIClientVersion(context.Background())
|
||||
@@ -109,9 +111,123 @@ func TestRefreshXAIClientVersionUpdatesOnSuccess(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefreshXAIClientVersionAcceptsStableLatest(t *testing.T) {
|
||||
restoreVersion := SetXAIClientVersionForTest("1.0.44")
|
||||
defer restoreVersion()
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"version":"1.0.46"}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
restoreURL := OverrideXAINPMRegistryURLForTest(server.URL)
|
||||
defer restoreURL()
|
||||
|
||||
version, errFetch := FetchXAINPMLatestVersion(context.Background(), server.Client())
|
||||
if errFetch != nil {
|
||||
t.Fatalf("FetchXAINPMLatestVersion() error = %v", errFetch)
|
||||
}
|
||||
if version != "1.0.46" {
|
||||
t.Fatalf("FetchXAINPMLatestVersion() = %q, want 1.0.46", version)
|
||||
}
|
||||
refreshXAIClientVersion(context.Background())
|
||||
if got := GetXAIClientVersion(); got != "1.0.46" {
|
||||
t.Fatalf("GetXAIClientVersion() = %q, want stable latest 1.0.46", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefreshXAIClientVersionRejectsUnsafeVersions(t *testing.T) {
|
||||
restoreVersion := SetXAIClientVersionForTest(DefaultXAIFallbackClientVersion)
|
||||
defer restoreVersion()
|
||||
|
||||
unsafeVersions := []string{
|
||||
"0.2.93",
|
||||
"1.0.12",
|
||||
"1.0.55-beta.1",
|
||||
"1.0.55\r\nX-Injected: yes",
|
||||
}
|
||||
for _, unsafeVersion := range unsafeVersions {
|
||||
t.Run(unsafeVersion, func(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"version":` + strconv.Quote(unsafeVersion) + `}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
restoreURL := OverrideXAINPMRegistryURLForTest(server.URL)
|
||||
defer restoreURL()
|
||||
|
||||
if _, errFetch := FetchXAINPMLatestVersion(context.Background(), server.Client()); errFetch == nil {
|
||||
t.Fatal("FetchXAINPMLatestVersion() expected error for unsafe version, got nil")
|
||||
}
|
||||
refreshXAIClientVersion(context.Background())
|
||||
if got := GetXAIClientVersion(); got != DefaultXAIFallbackClientVersion {
|
||||
t.Fatalf("GetXAIClientVersion() = %q, want fallback %s preserved", got, DefaultXAIFallbackClientVersion)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefreshXAIClientVersionUsesConfiguredProxy(t *testing.T) {
|
||||
restoreVersion := SetXAIClientVersionForTest(DefaultXAIFallbackClientVersion)
|
||||
defer restoreVersion()
|
||||
|
||||
registry := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"version":"1.0.61"}`))
|
||||
}))
|
||||
defer registry.Close()
|
||||
|
||||
var proxied atomic.Bool
|
||||
proxyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
proxied.Store(true)
|
||||
if r.URL.Scheme == "" || r.URL.Host == "" {
|
||||
http.Error(w, "expected absolute proxy URI", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
outbound, errReq := http.NewRequestWithContext(r.Context(), r.Method, r.URL.String(), nil)
|
||||
if errReq != nil {
|
||||
http.Error(w, errReq.Error(), http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
outbound.Header = r.Header.Clone()
|
||||
direct := &http.Client{Transport: &http.Transport{Proxy: nil}}
|
||||
resp, errDo := direct.Do(outbound)
|
||||
if errDo != nil {
|
||||
http.Error(w, errDo.Error(), http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
for key, values := range resp.Header {
|
||||
for _, value := range values {
|
||||
w.Header().Add(key, value)
|
||||
}
|
||||
}
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
_, _ = io.Copy(w, resp.Body)
|
||||
}))
|
||||
defer proxyServer.Close()
|
||||
|
||||
restoreURL := OverrideXAINPMRegistryURLForTest(registry.URL)
|
||||
defer restoreURL()
|
||||
restoreProxy := setXAIVersionProxyURLForTest(proxyServer.URL)
|
||||
defer restoreProxy()
|
||||
|
||||
refreshXAIClientVersion(context.Background())
|
||||
|
||||
if !proxied.Load() {
|
||||
t.Fatal("expected npm lookup to use the configured proxy")
|
||||
}
|
||||
if got := GetXAIClientVersion(); got != "1.0.61" {
|
||||
t.Fatalf("GetXAIClientVersion() = %q, want 1.0.61 via proxy", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartXAIVersionUpdaterLifecycle(t *testing.T) {
|
||||
ResetXAIVersionUpdaterOnceForTest(t)
|
||||
restoreVersion := SetXAIClientVersionForTest(t, "1.0.50")
|
||||
restoreVersion := SetXAIClientVersionForTest(DefaultXAIFallbackClientVersion)
|
||||
defer restoreVersion()
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -120,24 +236,78 @@ func TestStartXAIVersionUpdaterLifecycle(t *testing.T) {
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
restoreURL := OverrideXAINPMRegistryURLForTest(t, server.URL)
|
||||
restoreURL := OverrideXAINPMRegistryURLForTest(server.URL)
|
||||
defer restoreURL()
|
||||
notified := make(chan struct{}, 1)
|
||||
restoreHook := SetXAIVersionRefreshedHookForTest(notified)
|
||||
defer restoreHook()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
StartXAIVersionUpdater(ctx)
|
||||
|
||||
// Wait briefly for the startup fetch to settle
|
||||
deadline := time.Now().Add(2 * time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
if GetXAIClientVersion() == "1.0.60" {
|
||||
break
|
||||
}
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
}
|
||||
StartXAIVersionUpdater(ctx, "")
|
||||
waitXAIVersionRefresh(t, notified)
|
||||
|
||||
if got := GetXAIClientVersion(); got != "1.0.60" {
|
||||
t.Fatalf("GetXAIClientVersion() = %q, want 1.0.60 after StartXAIVersionUpdater", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartXAIVersionUpdaterRestartsWithNewContext(t *testing.T) {
|
||||
restoreVersion := SetXAIClientVersionForTest(DefaultXAIFallbackClientVersion)
|
||||
defer restoreVersion()
|
||||
|
||||
var published atomic.Value
|
||||
published.Store("1.0.70")
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
version, _ := published.Load().(string)
|
||||
_, _ = w.Write([]byte(`{"version":` + strconv.Quote(version) + `}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
restoreURL := OverrideXAINPMRegistryURLForTest(server.URL)
|
||||
defer restoreURL()
|
||||
notified := make(chan struct{}, 2)
|
||||
restoreHook := SetXAIVersionRefreshedHookForTest(notified)
|
||||
defer restoreHook()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
StartXAIVersionUpdater(ctx, "")
|
||||
waitXAIVersionRefresh(t, notified)
|
||||
if got := GetXAIClientVersion(); got != "1.0.70" {
|
||||
cancel()
|
||||
t.Fatalf("GetXAIClientVersion() = %q, want 1.0.70", got)
|
||||
}
|
||||
cancel()
|
||||
|
||||
published.Store("1.0.71")
|
||||
restartCtx, restartCancel := context.WithCancel(context.Background())
|
||||
defer restartCancel()
|
||||
StartXAIVersionUpdater(restartCtx, "")
|
||||
waitXAIVersionRefresh(t, notified)
|
||||
if got := GetXAIClientVersion(); got != "1.0.71" {
|
||||
t.Fatalf("GetXAIClientVersion() = %q, want 1.0.71 after restart", got)
|
||||
}
|
||||
}
|
||||
|
||||
func waitXAIVersionRefresh(t *testing.T, notified <-chan struct{}) {
|
||||
t.Helper()
|
||||
select {
|
||||
case <-notified:
|
||||
case <-t.Context().Done():
|
||||
t.Fatal("timed out waiting for Grok CLI version refresh")
|
||||
}
|
||||
}
|
||||
|
||||
func setXAIVersionProxyURLForTest(proxyURL string) func() {
|
||||
xaiClientVersionMu.Lock()
|
||||
old := xaiVersionProxyURL
|
||||
xaiVersionProxyURL = proxyURL
|
||||
xaiClientVersionMu.Unlock()
|
||||
return func() {
|
||||
xaiClientVersionMu.Lock()
|
||||
xaiVersionProxyURL = old
|
||||
xaiClientVersionMu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ func TestXAIChatProxyCustomHeadersOverridePinnedVersion(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestXAIChatProxyIdentityHeadersFollowDynamicVersion(t *testing.T) {
|
||||
restore := helps.SetXAIClientVersionForTest(t, "1.2.34")
|
||||
restore := helps.SetXAIClientVersionForTest("1.2.34")
|
||||
defer restore()
|
||||
|
||||
auth := &cliproxyauth.Auth{
|
||||
|
||||
@@ -85,8 +85,9 @@ func xaiClientVersion() string {
|
||||
}
|
||||
|
||||
// StartXAIVersionUpdater starts the periodic Grok CLI version updater from npm.
|
||||
func StartXAIVersionUpdater(ctx context.Context) {
|
||||
helps.StartXAIVersionUpdater(ctx)
|
||||
// proxyURL is the global outbound proxy; an empty value inherits the process environment.
|
||||
func StartXAIVersionUpdater(ctx context.Context, proxyURL string) {
|
||||
helps.StartXAIVersionUpdater(ctx, proxyURL)
|
||||
}
|
||||
|
||||
// Identifier returns the provider identifier.
|
||||
|
||||
@@ -395,8 +395,10 @@ func (s *Service) startModelCatalogUpdaters(ctx context.Context) {
|
||||
s.cfgMu.RLock()
|
||||
cfg := s.cfg
|
||||
s.cfgMu.RUnlock()
|
||||
proxyURL := ""
|
||||
if cfg != nil {
|
||||
registry.StartModelCatalogUpdaters(ctx, cfg.Models, cfg.Home.Enabled)
|
||||
proxyURL = cfg.ProxyURL
|
||||
}
|
||||
executor.StartXAIVersionUpdater(ctx)
|
||||
executor.StartXAIVersionUpdater(ctx, proxyURL)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user