From c7cc3a1a4dadd08526247766edbee34c9e02a69e Mon Sep 17 00:00:00 2001 From: sususu98 Date: Tue, 9 Jun 2026 11:01:42 +0800 Subject: [PATCH] fix: update antigravity version lookup --- internal/logging/global_logger.go | 2 +- internal/logging/global_logger_test.go | 27 ++++ internal/misc/antigravity_version.go | 165 +++++++++++++++++++++- internal/misc/antigravity_version_test.go | 148 +++++++++++++++++++ 4 files changed, 339 insertions(+), 3 deletions(-) create mode 100644 internal/logging/global_logger_test.go create mode 100644 internal/misc/antigravity_version_test.go diff --git a/internal/logging/global_logger.go b/internal/logging/global_logger.go index 4b4ef62c..0fe621a3 100644 --- a/internal/logging/global_logger.go +++ b/internal/logging/global_logger.go @@ -30,7 +30,7 @@ var ( type LogFormatter struct{} // logFieldOrder defines the display order for common log fields. -var logFieldOrder = []string{"provider", "model", "mode", "budget", "level", "original_mode", "original_value", "min", "max", "clamped_to", "error"} +var logFieldOrder = []string{"provider", "model", "version", "mode", "budget", "level", "original_mode", "original_value", "min", "max", "clamped_to", "error"} // Format renders a single log entry with custom formatting. func (m *LogFormatter) Format(entry *log.Entry) ([]byte, error) { diff --git a/internal/logging/global_logger_test.go b/internal/logging/global_logger_test.go new file mode 100644 index 00000000..a90bf404 --- /dev/null +++ b/internal/logging/global_logger_test.go @@ -0,0 +1,27 @@ +package logging + +import ( + "strings" + "testing" + "time" + + log "github.com/sirupsen/logrus" +) + +func TestLogFormatterPrintsVersionField(t *testing.T) { + entry := log.NewEntry(log.New()) + entry.Time = time.Date(2026, 6, 9, 11, 10, 2, 0, time.Local) + entry.Level = log.InfoLevel + entry.Message = "fetched latest antigravity version" + entry.Data["version"] = "2.1.0" + + formatted, errFormat := (&LogFormatter{}).Format(entry) + if errFormat != nil { + t.Fatalf("Format() error = %v", errFormat) + } + + line := string(formatted) + if !strings.Contains(line, "version=2.1.0") { + t.Fatalf("formatted line %q missing version field", line) + } +} diff --git a/internal/misc/antigravity_version.go b/internal/misc/antigravity_version.go index 0d187c25..45eef31a 100644 --- a/internal/misc/antigravity_version.go +++ b/internal/misc/antigravity_version.go @@ -4,9 +4,11 @@ package misc import ( "context" "encoding/json" + "encoding/xml" "errors" "fmt" "net/http" + "strconv" "strings" "sync" "time" @@ -15,19 +17,36 @@ import ( ) const ( - antigravityReleasesURL = "https://antigravity-auto-updater-974169037036.us-central1.run.app/releases" - antigravityFallbackVersion = "1.21.9" + antigravityFallbackVersion = "2.1.0" antigravityVersionCacheTTL = 6 * time.Hour antigravityFetchTimeout = 10 * time.Second AntigravityNodeAPIClientUA = "google-api-nodejs-client/10.3.0" AntigravityGoogAPIClientUA = "gl-node/22.21.1" ) +var ( + antigravityHubGCSListURL = "https://storage.googleapis.com/antigravity-public/?prefix=antigravity-hub/&delimiter=/" + antigravityReleasesURL = "https://antigravity-auto-updater-974169037036.us-central1.run.app/releases" +) + type antigravityRelease struct { Version string `json:"version"` ExecutionID string `json:"execution_id"` } +type antigravityHubGCSList struct { + CommonPrefixes []antigravityHubGCSPrefix `xml:"CommonPrefixes"` +} + +type antigravityHubGCSPrefix struct { + Prefix string `xml:"Prefix"` +} + +type antigravitySemVersion struct { + raw string + parts [3]int +} + var ( cachedAntigravityVersion = antigravityFallbackVersion antigravityVersionMu sync.RWMutex @@ -176,6 +195,55 @@ func fetchAntigravityLatestVersion(ctx context.Context) (string, error) { client := &http.Client{Timeout: antigravityFetchTimeout} + version, errHub := fetchAntigravityHubGCSLatestVersion(ctx, client) + if errHub == nil { + return version, nil + } + + log.WithError(errHub).Debug("failed to fetch antigravity hub GCS version, trying legacy releases API") + + version, errLegacy := fetchAntigravityLegacyLatestVersion(ctx, client) + if errLegacy == nil { + return version, nil + } + + return "", fmt.Errorf("fetch antigravity hub GCS version: %v; fetch legacy releases: %w", errHub, errLegacy) +} + +func fetchAntigravityHubGCSLatestVersion(ctx context.Context, client *http.Client) (string, error) { + httpReq, errReq := http.NewRequestWithContext(ctx, http.MethodGet, antigravityHubGCSListURL, nil) + if errReq != nil { + return "", fmt.Errorf("build antigravity hub GCS request: %w", errReq) + } + + resp, errDo := client.Do(httpReq) + if errDo != nil { + return "", fmt.Errorf("fetch antigravity hub GCS list: %w", errDo) + } + defer func() { + if errClose := resp.Body.Close(); errClose != nil { + log.WithError(errClose).Warn("antigravity hub GCS response body close error") + } + }() + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("antigravity hub GCS list returned status %d", resp.StatusCode) + } + + var list antigravityHubGCSList + if errDecode := xml.NewDecoder(resp.Body).Decode(&list); errDecode != nil { + return "", fmt.Errorf("decode antigravity hub GCS list: %w", errDecode) + } + + prefixes := make([]string, 0, len(list.CommonPrefixes)) + for _, commonPrefix := range list.CommonPrefixes { + prefixes = append(prefixes, commonPrefix.Prefix) + } + + return latestAntigravityHubVersionFromPrefixes(prefixes) +} + +func fetchAntigravityLegacyLatestVersion(ctx context.Context, client *http.Client) (string, error) { httpReq, errReq := http.NewRequestWithContext(ctx, http.MethodGet, antigravityReleasesURL, nil) if errReq != nil { return "", fmt.Errorf("build antigravity releases request: %w", errReq) @@ -211,3 +279,96 @@ func fetchAntigravityLatestVersion(ctx context.Context) (string, error) { return version, nil } + +func latestAntigravityHubVersionFromPrefixes(prefixes []string) (string, error) { + var best antigravitySemVersion + found := false + + for _, prefix := range prefixes { + version, ok := antigravityHubVersionFromPrefix(prefix) + if !ok { + continue + } + semVersion, ok := parseAntigravitySemVersion(version) + if !ok { + continue + } + if !found || compareAntigravitySemVersion(semVersion, best) > 0 { + best = semVersion + found = true + } + } + + if !found { + return "", errors.New("antigravity hub GCS list contained no version prefixes") + } + + return best.raw, nil +} + +func antigravityHubVersionFromPrefix(prefix string) (string, bool) { + const hubPrefix = "antigravity-hub/" + + prefix = strings.TrimSpace(prefix) + prefix = strings.TrimSuffix(prefix, "/") + if !strings.HasPrefix(prefix, hubPrefix) { + return "", false + } + + name := strings.TrimPrefix(prefix, hubPrefix) + separator := strings.LastIndex(name, "-") + if separator <= 0 || separator == len(name)-1 { + return "", false + } + + version := strings.TrimSpace(name[:separator]) + executionID := name[separator+1:] + if version == "" || executionID == "" { + return "", false + } + for _, ch := range executionID { + if ch < '0' || ch > '9' { + return "", false + } + } + + return version, true +} + +func parseAntigravitySemVersion(version string) (antigravitySemVersion, bool) { + parts := strings.Split(version, ".") + if len(parts) != 3 { + return antigravitySemVersion{}, false + } + + semVersion := antigravitySemVersion{raw: version} + for i, part := range parts { + if part == "" { + return antigravitySemVersion{}, false + } + for _, ch := range part { + if ch < '0' || ch > '9' { + return antigravitySemVersion{}, false + } + } + value, errParse := strconv.Atoi(part) + if errParse != nil { + return antigravitySemVersion{}, false + } + semVersion.parts[i] = value + } + + return semVersion, true +} + +func compareAntigravitySemVersion(left antigravitySemVersion, right antigravitySemVersion) int { + for i := range left.parts { + if left.parts[i] > right.parts[i] { + return 1 + } + if left.parts[i] < right.parts[i] { + return -1 + } + } + return 0 +} diff --git a/internal/misc/antigravity_version_test.go b/internal/misc/antigravity_version_test.go new file mode 100644 index 00000000..0f985037 --- /dev/null +++ b/internal/misc/antigravity_version_test.go @@ -0,0 +1,148 @@ +package misc + +import ( + "context" + "net/http" + "net/http/httptest" + "sync/atomic" + "testing" + "time" +) + +func overrideAntigravityVersionURLsForTest(t *testing.T, hubURL string, legacyURL string) func() { + t.Helper() + + oldHubURL := antigravityHubGCSListURL + oldLegacyURL := antigravityReleasesURL + antigravityHubGCSListURL = hubURL + antigravityReleasesURL = legacyURL + + return func() { + antigravityHubGCSListURL = oldHubURL + antigravityReleasesURL = oldLegacyURL + } +} + +func overrideAntigravityVersionCacheForTest(t *testing.T, version string, expiry time.Time) func() { + t.Helper() + + antigravityVersionMu.Lock() + oldVersion := cachedAntigravityVersion + oldExpiry := antigravityVersionExpiry + cachedAntigravityVersion = version + antigravityVersionExpiry = expiry + antigravityVersionMu.Unlock() + + return func() { + antigravityVersionMu.Lock() + cachedAntigravityVersion = oldVersion + antigravityVersionExpiry = oldExpiry + antigravityVersionMu.Unlock() + } +} + +func TestAntigravityLatestVersionUsesCurrentHubFallback(t *testing.T) { + restore := overrideAntigravityVersionCacheForTest(t, "", time.Time{}) + defer restore() + + version := AntigravityLatestVersion() + if version != "2.1.0" { + t.Fatalf("AntigravityLatestVersion() = %q, want %q", version, "2.1.0") + } +} + +func TestFetchAntigravityLatestVersionPrefersHubGCSList(t *testing.T) { + var legacyRequests atomic.Int32 + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/gcs": + w.Header().Set("Content-Type", "application/xml") + _, _ = w.Write([]byte(` + + antigravity-hub/2.0.9-4666288509943808/ + antigravity-hub/2.0.11-6560309696135168/ + antigravity-hub/2.1.0-6066040229199872/ +`)) + case "/legacy": + legacyRequests.Add(1) + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`[{"version":"9.9.9","execution_id":"1"}]`)) + default: + http.NotFound(w, r) + } + })) + defer server.Close() + + restore := overrideAntigravityVersionURLsForTest(t, server.URL+"/gcs", server.URL+"/legacy") + defer restore() + + version, errFetch := fetchAntigravityLatestVersion(context.Background()) + if errFetch != nil { + t.Fatalf("fetchAntigravityLatestVersion() error = %v", errFetch) + } + if version != "2.1.0" { + t.Fatalf("fetchAntigravityLatestVersion() = %q, want %q", version, "2.1.0") + } + if got := legacyRequests.Load(); got != 0 { + t.Fatalf("legacy releases API requests = %d, want 0", got) + } +} + +func TestFetchAntigravityLatestVersionFallsBackToLegacyReleases(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/gcs": + http.Error(w, "temporary outage", http.StatusInternalServerError) + case "/legacy": + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`[{"version":"2.0.0","execution_id":"6324554176528384"}]`)) + default: + http.NotFound(w, r) + } + })) + defer server.Close() + + restore := overrideAntigravityVersionURLsForTest(t, server.URL+"/gcs", server.URL+"/legacy") + defer restore() + + version, errFetch := fetchAntigravityLatestVersion(context.Background()) + if errFetch != nil { + t.Fatalf("fetchAntigravityLatestVersion() error = %v", errFetch) + } + if version != "2.0.0" { + t.Fatalf("fetchAntigravityLatestVersion() = %q, want %q", version, "2.0.0") + } +} + +func TestLatestAntigravityHubVersionFromPrefixesSortsByNumericSemver(t *testing.T) { + prefixes := []string{ + "antigravity-hub/2.0.9-4666288509943808/", + "antigravity-hub/2.0.10-5119448496078848/", + "antigravity-hub/2.0.11-6560309696135168/", + "antigravity-hub/not-a-version/", + } + + version, errParse := latestAntigravityHubVersionFromPrefixes(prefixes) + if errParse != nil { + t.Fatalf("latestAntigravityHubVersionFromPrefixes() error = %v", errParse) + } + if version != "2.0.11" { + t.Fatalf("latestAntigravityHubVersionFromPrefixes() = %q, want %q", version, "2.0.11") + } +} + +func TestLatestAntigravityHubVersionFromPrefixesIgnoresSignedVersionParts(t *testing.T) { + prefixes := []string{ + "antigravity-hub/9.+9.9-4666288509943808/", + "antigravity-hub/2.1.0-6066040229199872/", + } + + version, errParse := latestAntigravityHubVersionFromPrefixes(prefixes) + if errParse != nil { + t.Fatalf("latestAntigravityHubVersionFromPrefixes() error = %v", errParse) + } + if version != "2.1.0" { + t.Fatalf("latestAntigravityHubVersionFromPrefixes() = %q, want %q", version, "2.1.0") + } +} -- 2.51.2