-
-
Notifications
You must be signed in to change notification settings - Fork 352
v6.8.52 #439
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
v6.8.52 #439
Changes from all commits
7b7b258
ddaa9d2
70988d3
163fe28
2b79d7f
89d7be9
8c92cb0
861537c
a6c3042
dea3e74
ec24baf
dbd42a4
683f370
817cebb
0ac52da
5484489
c3d5dbe
1db2397
86d5db4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,275 @@ | ||||||
| // Command fetch_antigravity_models connects to the Antigravity API using the | ||||||
| // stored auth credentials and saves the dynamically fetched model list to a | ||||||
| // JSON file for inspection or offline use. | ||||||
| // | ||||||
| // Usage: | ||||||
| // | ||||||
| // go run ./cmd/fetch_antigravity_models [flags] | ||||||
| // | ||||||
| // Flags: | ||||||
| // | ||||||
| // --auths-dir <path> Directory containing auth JSON files (default: "auths") | ||||||
| // --output <path> Output JSON file path (default: "antigravity_models.json") | ||||||
| // --pretty Pretty-print the output JSON (default: true) | ||||||
| package main | ||||||
|
|
||||||
| import ( | ||||||
| "context" | ||||||
| "encoding/json" | ||||||
| "flag" | ||||||
| "fmt" | ||||||
| "io" | ||||||
| "net/http" | ||||||
| "os" | ||||||
| "path/filepath" | ||||||
| "strings" | ||||||
| "time" | ||||||
|
|
||||||
| "github.com/router-for-me/CLIProxyAPI/v6/internal/logging" | ||||||
| sdkauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth" | ||||||
| coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" | ||||||
| "github.com/router-for-me/CLIProxyAPI/v6/sdk/proxyutil" | ||||||
| log "github.com/sirupsen/logrus" | ||||||
| "github.com/tidwall/gjson" | ||||||
| ) | ||||||
|
|
||||||
| const ( | ||||||
| antigravityBaseURLDaily = "https://daily-cloudcode-pa.googleapis.com" | ||||||
| antigravitySandboxBaseURLDaily = "https://daily-cloudcode-pa.sandbox.googleapis.com" | ||||||
| antigravityBaseURLProd = "https://cloudcode-pa.googleapis.com" | ||||||
| antigravityModelsPath = "/v1internal:fetchAvailableModels" | ||||||
| ) | ||||||
|
|
||||||
| func init() { | ||||||
| logging.SetupBaseLogger() | ||||||
| log.SetLevel(log.InfoLevel) | ||||||
| } | ||||||
|
|
||||||
| // modelOutput wraps the fetched model list with fetch metadata. | ||||||
| type modelOutput struct { | ||||||
| Models []modelEntry `json:"models"` | ||||||
| } | ||||||
|
|
||||||
| // modelEntry contains only the fields we want to keep for static model definitions. | ||||||
| type modelEntry struct { | ||||||
| ID string `json:"id"` | ||||||
| Object string `json:"object"` | ||||||
| OwnedBy string `json:"owned_by"` | ||||||
| Type string `json:"type"` | ||||||
| DisplayName string `json:"display_name"` | ||||||
| Name string `json:"name"` | ||||||
| Description string `json:"description"` | ||||||
| ContextLength int `json:"context_length,omitempty"` | ||||||
| MaxCompletionTokens int `json:"max_completion_tokens,omitempty"` | ||||||
| } | ||||||
|
|
||||||
| func main() { | ||||||
| var authsDir string | ||||||
| var outputPath string | ||||||
| var pretty bool | ||||||
|
|
||||||
| flag.StringVar(&authsDir, "auths-dir", "auths", "Directory containing auth JSON files") | ||||||
| flag.StringVar(&outputPath, "output", "antigravity_models.json", "Output JSON file path") | ||||||
| flag.BoolVar(&pretty, "pretty", true, "Pretty-print the output JSON") | ||||||
| flag.Parse() | ||||||
|
|
||||||
| // Resolve relative paths against the working directory. | ||||||
| wd, err := os.Getwd() | ||||||
| if err != nil { | ||||||
| fmt.Fprintf(os.Stderr, "error: cannot get working directory: %v\n", err) | ||||||
| os.Exit(1) | ||||||
| } | ||||||
| if !filepath.IsAbs(authsDir) { | ||||||
| authsDir = filepath.Join(wd, authsDir) | ||||||
| } | ||||||
| if !filepath.IsAbs(outputPath) { | ||||||
| outputPath = filepath.Join(wd, outputPath) | ||||||
| } | ||||||
|
|
||||||
| fmt.Printf("Scanning auth files in: %s\n", authsDir) | ||||||
|
|
||||||
| // Load all auth records from the directory. | ||||||
| fileStore := sdkauth.NewFileTokenStore() | ||||||
| fileStore.SetBaseDir(authsDir) | ||||||
|
|
||||||
| ctx := context.Background() | ||||||
| auths, err := fileStore.List(ctx) | ||||||
| if err != nil { | ||||||
| fmt.Fprintf(os.Stderr, "error: failed to list auth files: %v\n", err) | ||||||
| os.Exit(1) | ||||||
| } | ||||||
| if len(auths) == 0 { | ||||||
| fmt.Fprintf(os.Stderr, "error: no auth files found in %s\n", authsDir) | ||||||
| os.Exit(1) | ||||||
| } | ||||||
|
|
||||||
| // Find the first enabled antigravity auth. | ||||||
| var chosen *coreauth.Auth | ||||||
| for _, a := range auths { | ||||||
| if a == nil || a.Disabled { | ||||||
| continue | ||||||
| } | ||||||
| if strings.EqualFold(strings.TrimSpace(a.Provider), "antigravity") { | ||||||
| chosen = a | ||||||
| break | ||||||
| } | ||||||
| } | ||||||
| if chosen == nil { | ||||||
| fmt.Fprintf(os.Stderr, "error: no enabled antigravity auth found in %s\n", authsDir) | ||||||
| os.Exit(1) | ||||||
| } | ||||||
|
|
||||||
| fmt.Printf("Using auth: id=%s label=%s\n", chosen.ID, chosen.Label) | ||||||
|
|
||||||
| // Fetch models from the upstream Antigravity API. | ||||||
| fmt.Println("Fetching Antigravity model list from upstream...") | ||||||
|
|
||||||
| fetchCtx, cancel := context.WithTimeout(ctx, 30*time.Second) | ||||||
| defer cancel() | ||||||
|
|
||||||
| models := fetchModels(fetchCtx, chosen) | ||||||
| if len(models) == 0 { | ||||||
| fmt.Fprintln(os.Stderr, "warning: no models returned (API may be unavailable or token expired)") | ||||||
| } else { | ||||||
| fmt.Printf("Fetched %d models.\n", len(models)) | ||||||
| } | ||||||
|
|
||||||
| // Build the output payload. | ||||||
| out := modelOutput{ | ||||||
| Models: models, | ||||||
| } | ||||||
|
|
||||||
| // Marshal to JSON. | ||||||
| var raw []byte | ||||||
| if pretty { | ||||||
| raw, err = json.MarshalIndent(out, "", " ") | ||||||
| } else { | ||||||
| raw, err = json.Marshal(out) | ||||||
| } | ||||||
| if err != nil { | ||||||
| fmt.Fprintf(os.Stderr, "error: failed to marshal JSON: %v\n", err) | ||||||
| os.Exit(1) | ||||||
| } | ||||||
|
|
||||||
| if err = os.WriteFile(outputPath, raw, 0o644); err != nil { | ||||||
| fmt.Fprintf(os.Stderr, "error: failed to write output file %s: %v\n", outputPath, err) | ||||||
| os.Exit(1) | ||||||
| } | ||||||
|
|
||||||
| fmt.Printf("Model list saved to: %s\n", outputPath) | ||||||
| } | ||||||
|
|
||||||
| func fetchModels(ctx context.Context, auth *coreauth.Auth) []modelEntry { | ||||||
| accessToken := metaStringValue(auth.Metadata, "access_token") | ||||||
| if accessToken == "" { | ||||||
| fmt.Fprintln(os.Stderr, "error: no access token found in auth") | ||||||
| return nil | ||||||
| } | ||||||
|
|
||||||
| baseURLs := []string{antigravityBaseURLProd, antigravityBaseURLDaily, antigravitySandboxBaseURLDaily} | ||||||
|
|
||||||
| for _, baseURL := range baseURLs { | ||||||
| modelsURL := baseURL + antigravityModelsPath | ||||||
|
|
||||||
| var payload []byte | ||||||
| if auth != nil && auth.Metadata != nil { | ||||||
| if pid, ok := auth.Metadata["project_id"].(string); ok && strings.TrimSpace(pid) != "" { | ||||||
| payload = []byte(fmt.Sprintf(`{"project": "%s"}`, strings.TrimSpace(pid))) | ||||||
| } | ||||||
| } | ||||||
| if len(payload) == 0 { | ||||||
| payload = []byte(`{}`) | ||||||
| } | ||||||
|
|
||||||
| httpReq, errReq := http.NewRequestWithContext(ctx, http.MethodPost, modelsURL, strings.NewReader(string(payload))) | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Converting the byte slice
Suggested change
|
||||||
| if errReq != nil { | ||||||
| continue | ||||||
| } | ||||||
| httpReq.Close = true | ||||||
| httpReq.Header.Set("Content-Type", "application/json") | ||||||
| httpReq.Header.Set("Authorization", "Bearer "+accessToken) | ||||||
| httpReq.Header.Set("User-Agent", "antigravity/1.19.6 darwin/arm64") | ||||||
|
|
||||||
| httpClient := &http.Client{Timeout: 30 * time.Second} | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The context passed to
Suggested change
|
||||||
| if transport, _, errProxy := proxyutil.BuildHTTPTransport(auth.ProxyURL); errProxy == nil && transport != nil { | ||||||
| httpClient.Transport = transport | ||||||
| } | ||||||
| httpResp, errDo := httpClient.Do(httpReq) | ||||||
| if errDo != nil { | ||||||
| continue | ||||||
| } | ||||||
|
|
||||||
| bodyBytes, errRead := io.ReadAll(httpResp.Body) | ||||||
| httpResp.Body.Close() | ||||||
| if errRead != nil { | ||||||
| continue | ||||||
| } | ||||||
|
|
||||||
| if httpResp.StatusCode < http.StatusOK || httpResp.StatusCode >= http.StatusMultipleChoices { | ||||||
| continue | ||||||
| } | ||||||
|
|
||||||
| result := gjson.GetBytes(bodyBytes, "models") | ||||||
| if !result.Exists() { | ||||||
| continue | ||||||
| } | ||||||
|
|
||||||
| var models []modelEntry | ||||||
|
|
||||||
| for originalName, modelData := range result.Map() { | ||||||
| modelID := strings.TrimSpace(originalName) | ||||||
| if modelID == "" { | ||||||
| continue | ||||||
| } | ||||||
| // Skip internal/experimental models | ||||||
| switch modelID { | ||||||
| case "chat_20706", "chat_23310", "tab_flash_lite_preview", "tab_jump_flash_lite_preview", "gemini-2.5-flash-thinking", "gemini-2.5-pro": | ||||||
| continue | ||||||
| } | ||||||
|
|
||||||
| displayName := modelData.Get("displayName").String() | ||||||
| if displayName == "" { | ||||||
| displayName = modelID | ||||||
| } | ||||||
|
|
||||||
| entry := modelEntry{ | ||||||
| ID: modelID, | ||||||
| Object: "model", | ||||||
| OwnedBy: "antigravity", | ||||||
| Type: "antigravity", | ||||||
| DisplayName: displayName, | ||||||
| Name: modelID, | ||||||
| Description: displayName, | ||||||
| } | ||||||
|
|
||||||
| if maxTok := modelData.Get("maxTokens").Int(); maxTok > 0 { | ||||||
| entry.ContextLength = int(maxTok) | ||||||
| } | ||||||
| if maxOut := modelData.Get("maxOutputTokens").Int(); maxOut > 0 { | ||||||
| entry.MaxCompletionTokens = int(maxOut) | ||||||
| } | ||||||
|
|
||||||
| models = append(models, entry) | ||||||
| } | ||||||
|
|
||||||
| return models | ||||||
| } | ||||||
|
|
||||||
| return nil | ||||||
| } | ||||||
|
|
||||||
| func metaStringValue(m map[string]interface{}, key string) string { | ||||||
| if m == nil { | ||||||
| return "" | ||||||
| } | ||||||
| v, ok := m[key] | ||||||
| if !ok { | ||||||
| return "" | ||||||
| } | ||||||
| switch val := v.(type) { | ||||||
| case string: | ||||||
| return val | ||||||
| default: | ||||||
| return "" | ||||||
| } | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -68,7 +68,8 @@ error-logs-max-files: 10 | |
| usage-statistics-enabled: false | ||
|
|
||
| # Proxy URL. Supports socks5/http/https protocols. Example: socks5://user:[email protected]:1080/ | ||
| proxy-url: '' | ||
| # Per-entry proxy-url also supports "direct" or "none" to bypass both the global proxy-url and environment proxies explicitly. | ||
| proxy-url: "" | ||
|
|
||
| # When true, unprefixed model requests only use credentials without a prefix (except when prefix == model name). | ||
| force-model-prefix: false | ||
|
|
@@ -115,6 +116,7 @@ nonstream-keepalive-interval: 0 | |
| # headers: | ||
| # X-Custom-Header: "custom-value" | ||
| # proxy-url: "socks5://proxy.example.com:1080" | ||
| # # proxy-url: "direct" # optional: explicit direct connect for this credential | ||
| # models: | ||
| # - name: "gemini-2.5-flash" # upstream model name | ||
| # alias: "gemini-flash" # client alias mapped to the upstream model | ||
|
|
@@ -133,6 +135,7 @@ nonstream-keepalive-interval: 0 | |
| # headers: | ||
| # X-Custom-Header: "custom-value" | ||
| # proxy-url: "socks5://proxy.example.com:1080" # optional: per-key proxy override | ||
| # # proxy-url: "direct" # optional: explicit direct connect for this credential | ||
| # models: | ||
| # - name: "gpt-5-codex" # upstream model name | ||
| # alias: "codex-latest" # client alias mapped to the upstream model | ||
|
|
@@ -151,6 +154,7 @@ nonstream-keepalive-interval: 0 | |
| # headers: | ||
| # X-Custom-Header: "custom-value" | ||
| # proxy-url: "socks5://proxy.example.com:1080" # optional: per-key proxy override | ||
| # # proxy-url: "direct" # optional: explicit direct connect for this credential | ||
| # models: | ||
| # - name: "claude-3-5-sonnet-20241022" # upstream model name | ||
| # alias: "claude-sonnet-latest" # client alias mapped to the upstream model | ||
|
|
@@ -178,6 +182,14 @@ nonstream-keepalive-interval: 0 | |
| # runtime-version: "v24.3.0" | ||
| # timeout: "600" | ||
|
|
||
| # Default headers for Codex OAuth model requests. | ||
| # These are used only for file-backed/OAuth Codex requests when the client | ||
| # does not send the header. `user-agent` applies to HTTP and websocket requests; | ||
| # `beta-features` only applies to websocket requests. They do not apply to codex-api-key entries. | ||
| # codex-header-defaults: | ||
| # user-agent: "codex_cli_rs/0.114.0 (Mac OS 14.2.0; x86_64) vscode/1.111.0" | ||
| # beta-features: "multi_agent" | ||
|
|
||
| # Kiro (AWS CodeWhisperer) configuration | ||
| # Note: Kiro API currently only operates in us-east-1 region | ||
| #kiro: | ||
|
|
@@ -215,6 +227,7 @@ nonstream-keepalive-interval: 0 | |
| # api-key-entries: | ||
| # - api-key: "sk-or-v1-...b780" | ||
| # proxy-url: "socks5://proxy.example.com:1080" # optional: per-key proxy override | ||
| # # proxy-url: "direct" # optional: explicit direct connect for this credential | ||
| # - api-key: "sk-or-v1-...b781" # without proxy-url | ||
| # models: # The models supported by the provider. | ||
| # - name: "moonshotai/kimi-k2:free" # The actual model name. | ||
|
|
@@ -237,6 +250,7 @@ nonstream-keepalive-interval: 0 | |
| # prefix: "test" # optional: require calls like "test/vertex-pro" to target this credential | ||
| # base-url: "https://example.com/api" # e.g. https://zenmux.ai/api | ||
| # proxy-url: "socks5://proxy.example.com:1080" # optional per-key proxy override | ||
| # # proxy-url: "direct" # optional: explicit direct connect for this credential | ||
| # headers: | ||
| # X-Custom-Header: "custom-value" | ||
| # models: # optional: map aliases to upstream model names | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For consistency with the logging setup in this file, please use the
logpackage for handling errors and warnings instead of writing directly toos.Stderr.log.Fatalf("error: cannot get working directory: %v", err)can be used. It will print the message and exit the program, simplifying the code. This applies to lines 98-99, 102-103, 118-119, 150-151, and 155-156 as well.log.Warnf(...).log.Errorf(...).