refactor(provider): remove Gemini Web cookie-based support

This commit is contained in:
hkfires
2025-10-11 12:56:07 +08:00
parent b895018ff5
commit c3f88126e6
17 changed files with 0 additions and 296 deletions

View File

@@ -639,19 +639,6 @@ These endpoints initiate provider login flows and return a URL to open in a brow
{ "status": "ok", "url": "https://..." } { "status": "ok", "url": "https://..." }
``` ```
- POST `/gemini-web-token` — Save Gemini Web cookies directly
- Request:
```bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' \
-H 'Content-Type: application/json' \
-d '{"secure_1psid": "<__Secure-1PSID>", "secure_1psidts": "<__Secure-1PSIDTS>", "label": "<LABEL>"}' \
http://localhost:8317/v0/management/gemini-web-token
```
- Response:
```json
{ "status": "ok", "file": "gemini-web-<hash>.json" }
```
- GET `/qwen-auth-url` — Start Qwen login (device flow) - GET `/qwen-auth-url` — Start Qwen login (device flow)
- Request: - Request:
```bash ```bash

View File

@@ -639,19 +639,6 @@
{ "status": "ok", "url": "https://..." } { "status": "ok", "url": "https://..." }
``` ```
- POST `/gemini-web-token` — 直接保存 Gemini Web Cookie
- 请求:
```bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' \
-H 'Content-Type: application/json' \
-d '{"secure_1psid": "<__Secure-1PSID>", "secure_1psidts": "<__Secure-1PSIDTS>", "label": "<LABEL>"}' \
http://localhost:8317/v0/management/gemini-web-token
```
- 响应:
```json
{ "status": "ok", "file": "gemini-web-<hash>.json" }
```
- GET `/qwen-auth-url` — 开始 Qwen 登录(设备授权流程) - GET `/qwen-auth-url` — 开始 Qwen 登录(设备授权流程)
- 请求: - 请求:
```bash ```bash

View File

@@ -17,7 +17,6 @@ Chinese providers have now been added: [Qwen Code](https://github.com/QwenLM/qwe
- Claude Code support via OAuth login - Claude Code support via OAuth login
- Qwen Code support via OAuth login - Qwen Code support via OAuth login
- iFlow support via OAuth login - iFlow support via OAuth login
- Gemini Web support via cookie-based login
- Streaming and non-streaming responses - Streaming and non-streaming responses
- Function calling/tools support - Function calling/tools support
- Multimodal input support (text and images) - Multimodal input support (text and images)
@@ -99,13 +98,6 @@ You can authenticate for Gemini, OpenAI, Claude, Qwen, and/or iFlow. All can coe
Options: add `--no-browser` to print the login URL instead of opening a browser. The local OAuth callback uses port `8085`. Options: add `--no-browser` to print the login URL instead of opening a browser. The local OAuth callback uses port `8085`.
- Gemini Web (via Cookies):
This method authenticates by simulating a browser, using cookies obtained from the Gemini website.
```bash
./cli-proxy-api --gemini-web-auth
```
You will be prompted to enter your `__Secure-1PSID` and `__Secure-1PSIDTS` values. Please retrieve these cookies from your browser's developer tools.
- OpenAI (Codex/GPT via OAuth): - OpenAI (Codex/GPT via OAuth):
```bash ```bash
./cli-proxy-api --codex-login ./cli-proxy-api --codex-login
@@ -334,11 +326,6 @@ The server uses a YAML configuration file (`config.yaml`) located in the project
| `openai-compatibility.*.models` | object[] | [] | The actual model name. | | `openai-compatibility.*.models` | object[] | [] | The actual model name. |
| `openai-compatibility.*.models.*.name` | string | "" | The models supported by the provider. | | `openai-compatibility.*.models.*.name` | string | "" | The models supported by the provider. |
| `openai-compatibility.*.models.*.alias` | string | "" | The alias used in the API. | | `openai-compatibility.*.models.*.alias` | string | "" | The alias used in the API. |
| `gemini-web` | object | {} | Configuration specific to the Gemini Web client. |
| `gemini-web.context` | boolean | true | Enables conversation context reuse for continuous dialogue. |
| `gemini-web.gem-mode` | string | "" | Selects a predefined Gem to attach for Gemini Web requests; allowed values: `coding-partner`, `writing-editor`. When empty, no Gem is attached. |
| `gemini-web.max-chars-per-request` | integer | 1,000,000 | The maximum number of characters to send to Gemini Web in a single request. |
| `gemini-web.disable-continuation-hint` | boolean | false | Disables the continuation hint for split prompts. |
### Example Configuration File ### Example Configuration File
@@ -388,12 +375,6 @@ quota-exceeded:
switch-project: true # Whether to automatically switch to another project when a quota is exceeded switch-project: true # Whether to automatically switch to another project when a quota is exceeded
switch-preview-model: true # Whether to automatically switch to a preview model when a quota is exceeded switch-preview-model: true # Whether to automatically switch to a preview model when a quota is exceeded
# Gemini Web client configuration
gemini-web:
context: true # Enable conversation context reuse
gem-mode: "" # Select Gem: "coding-partner" or "writing-editor"; empty means no Gem
max-chars-per-request: 1000000 # Max characters per request
# API keys for official Generative Language API # API keys for official Generative Language API
generative-language-api-key: generative-language-api-key:
- "AIzaSy...01" - "AIzaSy...01"
@@ -604,12 +585,6 @@ Run the following command to login (Gemini OAuth on port 8085):
docker run --rm -p 8085:8085 -v /path/to/your/config.yaml:/CLIProxyAPI/config.yaml -v /path/to/your/auth-dir:/root/.cli-proxy-api eceasy/cli-proxy-api:latest /CLIProxyAPI/CLIProxyAPI --login docker run --rm -p 8085:8085 -v /path/to/your/config.yaml:/CLIProxyAPI/config.yaml -v /path/to/your/auth-dir:/root/.cli-proxy-api eceasy/cli-proxy-api:latest /CLIProxyAPI/CLIProxyAPI --login
``` ```
Run the following command to login (Gemini Web Cookies):
```bash
docker run -it --rm -v /path/to/your/config.yaml:/CLIProxyAPI/config.yaml -v /path/to/your/auth-dir:/root/.cli-proxy-api eceasy/cli-proxy-api:latest /CLIProxyAPI/CLIProxyAPI --gemini-web-auth
```
Run the following command to login (OpenAI OAuth on port 1455): Run the following command to login (OpenAI OAuth on port 1455):
```bash ```bash
@@ -680,10 +655,6 @@ docker run --rm -p 8317:8317 -v /path/to/your/config.yaml:/CLIProxyAPI/config.ya
```bash ```bash
docker compose exec cli-proxy-api /CLIProxyAPI/CLIProxyAPI -no-browser --login docker compose exec cli-proxy-api /CLIProxyAPI/CLIProxyAPI -no-browser --login
``` ```
- **Gemini Web**:
```bash
docker compose exec cli-proxy-api /CLIProxyAPI/CLIProxyAPI --gemini-web-auth
```
- **OpenAI (Codex)**: - **OpenAI (Codex)**:
```bash ```bash
docker compose exec cli-proxy-api /CLIProxyAPI/CLIProxyAPI -no-browser --codex-login docker compose exec cli-proxy-api /CLIProxyAPI/CLIProxyAPI -no-browser --codex-login

View File

@@ -37,7 +37,6 @@
- 新增 Claude Code 支持OAuth 登录) - 新增 Claude Code 支持OAuth 登录)
- 新增 Qwen Code 支持OAuth 登录) - 新增 Qwen Code 支持OAuth 登录)
- 新增 iFlow 支持OAuth 登录) - 新增 iFlow 支持OAuth 登录)
- 新增 Gemini Web 支持(通过 Cookie 登录)
- 支持流式与非流式响应 - 支持流式与非流式响应
- 函数调用/工具支持 - 函数调用/工具支持
- 多模态输入(文本、图片) - 多模态输入(文本、图片)
@@ -113,13 +112,6 @@ CLIProxyAPI 的基于 Web 的管理中心。
选项:加上 `--no-browser` 可打印登录地址而不自动打开浏览器。本地 OAuth 回调端口为 `8085`。 选项:加上 `--no-browser` 可打印登录地址而不自动打开浏览器。本地 OAuth 回调端口为 `8085`。
- Gemini Web (通过 Cookie):
此方法通过模拟浏览器行为,使用从 Gemini 网站获取的 Cookie 进行身份验证。
```bash
./cli-proxy-api --gemini-web-auth
```
程序将提示您输入 `__Secure-1PSID` 和 `__Secure-1PSIDTS` 的值。请从您的浏览器开发者工具中获取这些 Cookie。
- OpenAICodex/GPTOAuth - OpenAICodex/GPTOAuth
```bash ```bash
./cli-proxy-api --codex-login ./cli-proxy-api --codex-login
@@ -347,11 +339,6 @@ console.log(await claudeResponse.json());
| `openai-compatibility.*.models` | object[] | [] | 实际的模型名称。 | | `openai-compatibility.*.models` | object[] | [] | 实际的模型名称。 |
| `openai-compatibility.*.models.*.name` | string | "" | 提供商支持的模型。 | | `openai-compatibility.*.models.*.name` | string | "" | 提供商支持的模型。 |
| `openai-compatibility.*.models.*.alias` | string | "" | 在API中使用的别名。 | | `openai-compatibility.*.models.*.alias` | string | "" | 在API中使用的别名。 |
| `gemini-web` | object | {} | Gemini Web 客户端的特定配置。 |
| `gemini-web.context` | boolean | true | 是否启用会话上下文重用,以实现连续对话。 |
| `gemini-web.gem-mode` | string | "" | 选择要附加的预设 Gem`coding-partner` 或 `writing-editor`);为空表示不附加。 |
| `gemini-web.max-chars-per-request` | integer | 1,000,000 | 单次请求发送给 Gemini Web 的最大字符数。 |
| `gemini-web.disable-continuation-hint` | boolean | false | 当提示被拆分时,是否禁用连续提示的暗示。 |
### 配置文件示例 ### 配置文件示例
@@ -401,12 +388,6 @@ quota-exceeded:
switch-project: true # 当配额超限时是否自动切换到另一个项目 switch-project: true # 当配额超限时是否自动切换到另一个项目
switch-preview-model: true # 当配额超限时是否自动切换到预览模型 switch-preview-model: true # 当配额超限时是否自动切换到预览模型
# Gemini Web 客户端配置
gemini-web:
context: true # 启用会话上下文重用
gem-mode: "" # 选择 Gem"coding-partner" 或 "writing-editor";为空表示不附加
max-chars-per-request: 1000000 # 单次请求最大字符数
# AIStduio Gemini API 的 API 密钥 # AIStduio Gemini API 的 API 密钥
generative-language-api-key: generative-language-api-key:
- "AIzaSy...01" - "AIzaSy...01"
@@ -613,12 +594,6 @@ auth.json:
docker run --rm -p 8085:8085 -v /path/to/your/config.yaml:/CLIProxyAPI/config.yaml -v /path/to/your/auth-dir:/root/.cli-proxy-api eceasy/cli-proxy-api:latest /CLIProxyAPI/CLIProxyAPI --login docker run --rm -p 8085:8085 -v /path/to/your/config.yaml:/CLIProxyAPI/config.yaml -v /path/to/your/auth-dir:/root/.cli-proxy-api eceasy/cli-proxy-api:latest /CLIProxyAPI/CLIProxyAPI --login
``` ```
运行以下命令进行登录Gemini Web Cookie
```bash
docker run -it --rm -v /path/to/your/config.yaml:/CLIProxyAPI/config.yaml -v /path/to/your/auth-dir:/root/.cli-proxy-api eceasy/cli-proxy-api:latest /CLIProxyAPI/CLIProxyAPI --gemini-web-auth
```
运行以下命令进行登录OpenAI OAuth端口 1455 运行以下命令进行登录OpenAI OAuth端口 1455
```bash ```bash
@@ -690,10 +665,6 @@ docker run --rm -p 8317:8317 -v /path/to/your/config.yaml:/CLIProxyAPI/config.ya
```bash ```bash
docker compose exec cli-proxy-api /CLIProxyAPI/CLIProxyAPI -no-browser --login docker compose exec cli-proxy-api /CLIProxyAPI/CLIProxyAPI -no-browser --login
``` ```
- **Gemini Web**:
```bash
docker compose exec cli-proxy-api /CLIProxyAPI/CLIProxyAPI --gemini-web-auth
```
- **OpenAI (Codex)**: - **OpenAI (Codex)**:
```bash ```bash
docker compose exec cli-proxy-api /CLIProxyAPI/CLIProxyAPI -no-browser --codex-login docker compose exec cli-proxy-api /CLIProxyAPI/CLIProxyAPI -no-browser --codex-login

View File

@@ -44,7 +44,6 @@ func main() {
var claudeLogin bool var claudeLogin bool
var qwenLogin bool var qwenLogin bool
var iflowLogin bool var iflowLogin bool
var geminiWebAuth bool
var noBrowser bool var noBrowser bool
var projectID string var projectID string
var configPath string var configPath string
@@ -56,7 +55,6 @@ func main() {
flag.BoolVar(&claudeLogin, "claude-login", false, "Login to Claude using OAuth") flag.BoolVar(&claudeLogin, "claude-login", false, "Login to Claude using OAuth")
flag.BoolVar(&qwenLogin, "qwen-login", false, "Login to Qwen using OAuth") flag.BoolVar(&qwenLogin, "qwen-login", false, "Login to Qwen using OAuth")
flag.BoolVar(&iflowLogin, "iflow-login", false, "Login to iFlow using OAuth") flag.BoolVar(&iflowLogin, "iflow-login", false, "Login to iFlow using OAuth")
flag.BoolVar(&geminiWebAuth, "gemini-web-auth", false, "Auth Gemini Web using cookies")
flag.BoolVar(&noBrowser, "no-browser", false, "Don't open browser automatically for OAuth") flag.BoolVar(&noBrowser, "no-browser", false, "Don't open browser automatically for OAuth")
flag.StringVar(&projectID, "project_id", "", "Project ID (Gemini only, not required)") flag.StringVar(&projectID, "project_id", "", "Project ID (Gemini only, not required)")
flag.StringVar(&configPath, "config", DefaultConfigPath, "Configure File Path") flag.StringVar(&configPath, "config", DefaultConfigPath, "Configure File Path")
@@ -182,8 +180,6 @@ func main() {
cmd.DoQwenLogin(cfg, options) cmd.DoQwenLogin(cfg, options)
} else if iflowLogin { } else if iflowLogin {
cmd.DoIFlowLogin(cfg, options) cmd.DoIFlowLogin(cfg, options)
} else if geminiWebAuth {
cmd.DoGeminiWebAuth(cfg)
} else { } else {
// In cloud deploy mode without config file, just wait for shutdown signals // In cloud deploy mode without config file, just wait for shutdown signals
if isCloudDeploy && !configFileExists { if isCloudDeploy && !configFileExists {

View File

@@ -79,19 +79,3 @@ quota-exceeded:
# models: # The models supported by the provider. # models: # The models supported by the provider.
# - name: "moonshotai/kimi-k2:free" # The actual model name. # - name: "moonshotai/kimi-k2:free" # The actual model name.
# alias: "kimi-k2" # The alias used in the API. # alias: "kimi-k2" # The alias used in the API.
# Gemini Web settings
#gemini-web:
# # Conversation reuse: set to true to enable (default), false to disable.
# context: true
# # Maximum characters per single request to Gemini Web. Requests exceeding this
# # size split into chunks. Only the last chunk carries files and yields the final answer.
# max-chars-per-request: 1000000
# # Disable the short continuation hint appended to intermediate chunks
# # when splitting long prompts. Default is false (hint enabled by default).
# disable-continuation-hint: false
# # Gem selection (Gem Mode):
# # - "coding-partner": attach the predefined Coding partner Gem
# # - "writing-editor": attach the predefined Writing editor Gem
# # - empty: do not attach any Gem
# gem-mode: ""

View File

@@ -3,8 +3,6 @@ package management
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/sha256"
"encoding/hex"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@@ -25,7 +23,6 @@ import (
geminiAuth "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/gemini" geminiAuth "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/gemini"
iflowauth "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/iflow" iflowauth "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/iflow"
"github.com/router-for-me/CLIProxyAPI/v6/internal/auth/qwen" "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/qwen"
// legacy client removed
"github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces"
"github.com/router-for-me/CLIProxyAPI/v6/internal/misc" "github.com/router-for-me/CLIProxyAPI/v6/internal/misc"
"github.com/router-for-me/CLIProxyAPI/v6/internal/util" "github.com/router-for-me/CLIProxyAPI/v6/internal/util"
@@ -903,65 +900,6 @@ func (h *Handler) RequestGeminiCLIToken(c *gin.Context) {
c.JSON(200, gin.H{"status": "ok", "url": authURL, "state": state}) c.JSON(200, gin.H{"status": "ok", "url": authURL, "state": state})
} }
func (h *Handler) CreateGeminiWebToken(c *gin.Context) {
ctx := c.Request.Context()
var payload struct {
Secure1PSID string `json:"secure_1psid"`
Secure1PSIDTS string `json:"secure_1psidts"`
Label string `json:"label"`
}
if err := c.ShouldBindJSON(&payload); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid body"})
return
}
payload.Secure1PSID = strings.TrimSpace(payload.Secure1PSID)
payload.Secure1PSIDTS = strings.TrimSpace(payload.Secure1PSIDTS)
payload.Label = strings.TrimSpace(payload.Label)
if payload.Secure1PSID == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "secure_1psid is required"})
return
}
if payload.Secure1PSIDTS == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "secure_1psidts is required"})
return
}
if payload.Label == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "label is required"})
return
}
sha := sha256.New()
sha.Write([]byte(payload.Secure1PSID))
hash := hex.EncodeToString(sha.Sum(nil))
fileName := fmt.Sprintf("gemini-web-%s.json", hash[:16])
tokenStorage := &geminiAuth.GeminiWebTokenStorage{
Secure1PSID: payload.Secure1PSID,
Secure1PSIDTS: payload.Secure1PSIDTS,
Label: payload.Label,
}
// Provide a stable label (gemini-web-<hash>) for logging and identification
tokenStorage.Label = strings.TrimSuffix(fileName, ".json")
record := &coreauth.Auth{
ID: fileName,
Provider: "gemini-web",
FileName: fileName,
Storage: tokenStorage,
}
savedPath, errSave := h.saveTokenRecord(ctx, record)
if errSave != nil {
log.Errorf("Failed to save Gemini Web token: %v", errSave)
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save token"})
return
}
fmt.Printf("Successfully saved Gemini Web token to: %s\n", savedPath)
c.JSON(http.StatusOK, gin.H{"status": "ok", "file": filepath.Base(savedPath)})
}
func (h *Handler) RequestCodexToken(c *gin.Context) { func (h *Handler) RequestCodexToken(c *gin.Context) {
ctx := context.Background() ctx := context.Background()

View File

@@ -419,7 +419,6 @@ func (s *Server) registerManagementRoutes() {
mgmt.GET("/anthropic-auth-url", s.mgmt.RequestAnthropicToken) mgmt.GET("/anthropic-auth-url", s.mgmt.RequestAnthropicToken)
mgmt.GET("/codex-auth-url", s.mgmt.RequestCodexToken) mgmt.GET("/codex-auth-url", s.mgmt.RequestCodexToken)
mgmt.GET("/gemini-cli-auth-url", s.mgmt.RequestGeminiCLIToken) mgmt.GET("/gemini-cli-auth-url", s.mgmt.RequestGeminiCLIToken)
mgmt.POST("/gemini-web-token", s.mgmt.CreateGeminiWebToken)
mgmt.GET("/qwen-auth-url", s.mgmt.RequestQwenToken) mgmt.GET("/qwen-auth-url", s.mgmt.RequestQwenToken)
mgmt.GET("/iflow-auth-url", s.mgmt.RequestIFlowToken) mgmt.GET("/iflow-auth-url", s.mgmt.RequestIFlowToken)
mgmt.GET("/get-auth-status", s.mgmt.GetAuthStatus) mgmt.GET("/get-auth-status", s.mgmt.GetAuthStatus)

View File

@@ -53,42 +53,6 @@ type Config struct {
// RemoteManagement nests management-related options under 'remote-management'. // RemoteManagement nests management-related options under 'remote-management'.
RemoteManagement RemoteManagement `yaml:"remote-management" json:"-"` RemoteManagement RemoteManagement `yaml:"remote-management" json:"-"`
// GeminiWeb groups configuration for Gemini Web client
GeminiWeb GeminiWebConfig `yaml:"gemini-web" json:"gemini-web"`
}
// GeminiWebConfig nests Gemini Web related options under 'gemini-web'.
type GeminiWebConfig struct {
// Context enables JSON-based conversation reuse.
// Defaults to true if not set in YAML (see LoadConfig).
Context bool `yaml:"context" json:"context"`
// GemMode selects a predefined Gem to attach for Gemini Web requests.
// Allowed values:
// - "coding-partner"
// - "writing-editor"
// When empty, no Gem is attached by configuration.
// This is independent from CodeMode below, which is kept for backwards compatibility.
GemMode string `yaml:"gem-mode" json:"gem-mode"`
// CodeMode enables legacy coding-mode behaviors for Gemini Web.
// Backwards compatibility: when true, the service behaves as before by
// attaching the predefined "Coding partner" Gem and enabling extra
// conveniences (e.g., XML wrapping hints). Prefer GemMode for selecting
// a Gem going forward.
CodeMode bool `yaml:"code-mode" json:"code-mode"`
// MaxCharsPerRequest caps the number of characters (runes) sent to
// Gemini Web in a single request. Long prompts will be split into
// multiple requests with a continuation hint, and only the final
// request will carry any files. When unset or <=0, a conservative
// default of 1,000,000 will be used.
MaxCharsPerRequest int `yaml:"max-chars-per-request" json:"max-chars-per-request"`
// DisableContinuationHint, when true, disables the continuation hint for split prompts.
// The hint is enabled by default.
DisableContinuationHint bool `yaml:"disable-continuation-hint,omitempty" json:"disable-continuation-hint,omitempty"`
} }
// RemoteManagement holds management API configuration under 'remote-management'. // RemoteManagement holds management API configuration under 'remote-management'.
@@ -212,7 +176,6 @@ func LoadConfigOptional(configFile string, optional bool) (*Config, error) {
// Set defaults before unmarshal so that absent keys keep defaults. // Set defaults before unmarshal so that absent keys keep defaults.
cfg.LoggingToFile = true cfg.LoggingToFile = true
cfg.UsageStatisticsEnabled = true cfg.UsageStatisticsEnabled = true
cfg.GeminiWeb.Context = true
if err = yaml.Unmarshal(data, &cfg); err != nil { if err = yaml.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("failed to parse config file: %w", err) return nil, fmt.Errorf("failed to parse config file: %w", err)
} }

View File

@@ -10,9 +10,6 @@ const (
// GeminiCLI represents the Google Gemini CLI provider identifier. // GeminiCLI represents the Google Gemini CLI provider identifier.
GeminiCLI = "gemini-cli" GeminiCLI = "gemini-cli"
// GeminiWeb represents the Google Gemini Web provider identifier.
GeminiWeb = "gemini-web"
// Codex represents the OpenAI Codex provider identifier. // Codex represents the OpenAI Codex provider identifier.
Codex = "codex" Codex = "codex"

View File

@@ -23,9 +23,6 @@ import (
_ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/openai/chat-completions" _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/openai/chat-completions"
_ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/openai/responses" _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/openai/responses"
_ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini-web/openai/chat-completions"
_ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini-web/openai/responses"
_ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/openai/claude" _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/openai/claude"
_ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/openai/gemini" _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/openai/gemini"
_ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/openai/gemini-cli" _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/openai/gemini-cli"

View File

@@ -512,21 +512,6 @@ func (w *Watcher) reloadConfig() bool {
if oldConfig.RequestRetry != newConfig.RequestRetry { if oldConfig.RequestRetry != newConfig.RequestRetry {
log.Debugf(" request-retry: %d -> %d", oldConfig.RequestRetry, newConfig.RequestRetry) log.Debugf(" request-retry: %d -> %d", oldConfig.RequestRetry, newConfig.RequestRetry)
} }
if oldConfig.GeminiWeb.Context != newConfig.GeminiWeb.Context {
log.Debugf(" gemini-web.context: %t -> %t", oldConfig.GeminiWeb.Context, newConfig.GeminiWeb.Context)
}
if oldConfig.GeminiWeb.MaxCharsPerRequest != newConfig.GeminiWeb.MaxCharsPerRequest {
log.Debugf(" gemini-web.max-chars-per-request: %d -> %d", oldConfig.GeminiWeb.MaxCharsPerRequest, newConfig.GeminiWeb.MaxCharsPerRequest)
}
if oldConfig.GeminiWeb.DisableContinuationHint != newConfig.GeminiWeb.DisableContinuationHint {
log.Debugf(" gemini-web.disable-continuation-hint: %t -> %t", oldConfig.GeminiWeb.DisableContinuationHint, newConfig.GeminiWeb.DisableContinuationHint)
}
if oldConfig.GeminiWeb.GemMode != newConfig.GeminiWeb.GemMode {
log.Debugf(" gemini-web.gem-mode: %s -> %s", oldConfig.GeminiWeb.GemMode, newConfig.GeminiWeb.GemMode)
}
if oldConfig.GeminiWeb.CodeMode != newConfig.GeminiWeb.CodeMode {
log.Debugf(" gemini-web.code-mode: %t -> %t", oldConfig.GeminiWeb.CodeMode, newConfig.GeminiWeb.CodeMode)
}
if len(oldConfig.APIKeys) != len(newConfig.APIKeys) { if len(oldConfig.APIKeys) != len(newConfig.APIKeys) {
log.Debugf(" api-keys count: %d -> %d", len(oldConfig.APIKeys), len(newConfig.APIKeys)) log.Debugf(" api-keys count: %d -> %d", len(oldConfig.APIKeys), len(newConfig.APIKeys))
} }

View File

@@ -9,7 +9,6 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces"
conversation "github.com/router-for-me/CLIProxyAPI/v6/internal/provider/gemini-web/conversation"
"github.com/router-for-me/CLIProxyAPI/v6/internal/util" "github.com/router-for-me/CLIProxyAPI/v6/internal/util"
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
coreexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" coreexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
@@ -49,8 +48,6 @@ type BaseAPIHandler struct {
Cfg *config.SDKConfig Cfg *config.SDKConfig
} }
const geminiWebProvider = "gemini-web"
// NewBaseAPIHandlers creates a new API handlers instance. // NewBaseAPIHandlers creates a new API handlers instance.
// It takes a slice of clients and configuration as input. // It takes a slice of clients and configuration as input.
// //
@@ -140,7 +137,6 @@ func (h *BaseAPIHandler) ExecuteWithAuthManager(ctx context.Context, handlerType
if len(providers) == 0 { if len(providers) == 0 {
return nil, &interfaces.ErrorMessage{StatusCode: http.StatusBadRequest, Error: fmt.Errorf("unknown provider for model %s", modelName)} return nil, &interfaces.ErrorMessage{StatusCode: http.StatusBadRequest, Error: fmt.Errorf("unknown provider for model %s", modelName)}
} }
metadata := h.buildGeminiWebMetadata(handlerType, providers, rawJSON)
req := coreexecutor.Request{ req := coreexecutor.Request{
Model: modelName, Model: modelName,
Payload: cloneBytes(rawJSON), Payload: cloneBytes(rawJSON),
@@ -150,7 +146,6 @@ func (h *BaseAPIHandler) ExecuteWithAuthManager(ctx context.Context, handlerType
Alt: alt, Alt: alt,
OriginalRequest: cloneBytes(rawJSON), OriginalRequest: cloneBytes(rawJSON),
SourceFormat: sdktranslator.FromString(handlerType), SourceFormat: sdktranslator.FromString(handlerType),
Metadata: metadata,
} }
resp, err := h.AuthManager.Execute(ctx, providers, req, opts) resp, err := h.AuthManager.Execute(ctx, providers, req, opts)
if err != nil { if err != nil {
@@ -166,7 +161,6 @@ func (h *BaseAPIHandler) ExecuteCountWithAuthManager(ctx context.Context, handle
if len(providers) == 0 { if len(providers) == 0 {
return nil, &interfaces.ErrorMessage{StatusCode: http.StatusBadRequest, Error: fmt.Errorf("unknown provider for model %s", modelName)} return nil, &interfaces.ErrorMessage{StatusCode: http.StatusBadRequest, Error: fmt.Errorf("unknown provider for model %s", modelName)}
} }
metadata := h.buildGeminiWebMetadata(handlerType, providers, rawJSON)
req := coreexecutor.Request{ req := coreexecutor.Request{
Model: modelName, Model: modelName,
Payload: cloneBytes(rawJSON), Payload: cloneBytes(rawJSON),
@@ -176,7 +170,6 @@ func (h *BaseAPIHandler) ExecuteCountWithAuthManager(ctx context.Context, handle
Alt: alt, Alt: alt,
OriginalRequest: cloneBytes(rawJSON), OriginalRequest: cloneBytes(rawJSON),
SourceFormat: sdktranslator.FromString(handlerType), SourceFormat: sdktranslator.FromString(handlerType),
Metadata: metadata,
} }
resp, err := h.AuthManager.ExecuteCount(ctx, providers, req, opts) resp, err := h.AuthManager.ExecuteCount(ctx, providers, req, opts)
if err != nil { if err != nil {
@@ -195,7 +188,6 @@ func (h *BaseAPIHandler) ExecuteStreamWithAuthManager(ctx context.Context, handl
close(errChan) close(errChan)
return nil, errChan return nil, errChan
} }
metadata := h.buildGeminiWebMetadata(handlerType, providers, rawJSON)
req := coreexecutor.Request{ req := coreexecutor.Request{
Model: modelName, Model: modelName,
Payload: cloneBytes(rawJSON), Payload: cloneBytes(rawJSON),
@@ -205,7 +197,6 @@ func (h *BaseAPIHandler) ExecuteStreamWithAuthManager(ctx context.Context, handl
Alt: alt, Alt: alt,
OriginalRequest: cloneBytes(rawJSON), OriginalRequest: cloneBytes(rawJSON),
SourceFormat: sdktranslator.FromString(handlerType), SourceFormat: sdktranslator.FromString(handlerType),
Metadata: metadata,
} }
chunks, err := h.AuthManager.ExecuteStream(ctx, providers, req, opts) chunks, err := h.AuthManager.ExecuteStream(ctx, providers, req, opts)
if err != nil { if err != nil {
@@ -241,18 +232,6 @@ func cloneBytes(src []byte) []byte {
return dst return dst
} }
func (h *BaseAPIHandler) buildGeminiWebMetadata(handlerType string, providers []string, rawJSON []byte) map[string]any {
if !util.InArray(providers, geminiWebProvider) {
return nil
}
meta := make(map[string]any)
msgs := conversation.ExtractMessages(handlerType, rawJSON)
if len(msgs) > 0 {
meta[conversation.MetadataMessagesKey] = msgs
}
return meta
}
// WriteErrorResponse writes an error message to the response writer using the HTTP status embedded in the message. // WriteErrorResponse writes an error message to the response writer using the HTTP status embedded in the message.
func (h *BaseAPIHandler) WriteErrorResponse(c *gin.Context, msg *interfaces.ErrorMessage) { func (h *BaseAPIHandler) WriteErrorResponse(c *gin.Context, msg *interfaces.ErrorMessage) {
status := http.StatusInternalServerError status := http.StatusInternalServerError

View File

@@ -13,7 +13,6 @@ func init() {
registerRefreshLead("iflow", func() Authenticator { return NewIFlowAuthenticator() }) registerRefreshLead("iflow", func() Authenticator { return NewIFlowAuthenticator() })
registerRefreshLead("gemini", func() Authenticator { return NewGeminiAuthenticator() }) registerRefreshLead("gemini", func() Authenticator { return NewGeminiAuthenticator() })
registerRefreshLead("gemini-cli", func() Authenticator { return NewGeminiAuthenticator() }) registerRefreshLead("gemini-cli", func() Authenticator { return NewGeminiAuthenticator() })
registerRefreshLead("gemini-web", func() Authenticator { return NewGeminiWebAuthenticator() })
} }
func registerRefreshLead(provider string, factory func() Authenticator) { func registerRefreshLead(provider string, factory func() Authenticator) {

View File

@@ -285,9 +285,6 @@ func (m *Manager) executeWithProvider(ctx context.Context, provider string, req
log.Debugf("Use API key %s for model %s", util.HideAPIKey(accountInfo), req.Model) log.Debugf("Use API key %s for model %s", util.HideAPIKey(accountInfo), req.Model)
} else if accountType == "oauth" { } else if accountType == "oauth" {
log.Debugf("Use OAuth %s for model %s", accountInfo, req.Model) log.Debugf("Use OAuth %s for model %s", accountInfo, req.Model)
} else if accountType == "cookie" {
// Only Gemini Web uses cookie; print stable account label as-is.
log.Debugf("Use Cookie %s for model %s", accountInfo, req.Model)
} }
tried[auth.ID] = struct{}{} tried[auth.ID] = struct{}{}
@@ -333,8 +330,6 @@ func (m *Manager) executeCountWithProvider(ctx context.Context, provider string,
log.Debugf("Use API key %s for model %s", util.HideAPIKey(accountInfo), req.Model) log.Debugf("Use API key %s for model %s", util.HideAPIKey(accountInfo), req.Model)
} else if accountType == "oauth" { } else if accountType == "oauth" {
log.Debugf("Use OAuth %s for model %s", accountInfo, req.Model) log.Debugf("Use OAuth %s for model %s", accountInfo, req.Model)
} else if accountType == "cookie" {
log.Debugf("Use Cookie %s for model %s", accountInfo, req.Model)
} }
tried[auth.ID] = struct{}{} tried[auth.ID] = struct{}{}
@@ -380,8 +375,6 @@ func (m *Manager) executeStreamWithProvider(ctx context.Context, provider string
log.Debugf("Use API key %s for model %s", util.HideAPIKey(accountInfo), req.Model) log.Debugf("Use API key %s for model %s", util.HideAPIKey(accountInfo), req.Model)
} else if accountType == "oauth" { } else if accountType == "oauth" {
log.Debugf("Use OAuth %s for model %s", accountInfo, req.Model) log.Debugf("Use OAuth %s for model %s", accountInfo, req.Model)
} else if accountType == "cookie" {
log.Debugf("Use Cookie %s for model %s", accountInfo, req.Model)
} }
tried[auth.ID] = struct{}{} tried[auth.ID] = struct{}{}

View File

@@ -134,24 +134,6 @@ func (a *Auth) AccountInfo() (string, string) {
if a == nil { if a == nil {
return "", "" return "", ""
} }
// For Gemini Web, prefer explicit cookie label for stability.
if strings.ToLower(a.Provider) == "gemini-web" {
// Prefer explicit label written into auth file (e.g., gemini-web-<hash>)
if a.Metadata != nil {
if v, ok := a.Metadata["label"].(string); ok && strings.TrimSpace(v) != "" {
return "cookie", strings.TrimSpace(v)
}
}
// Minimal fallback to cookie value for backward compatibility
if a.Metadata != nil {
if v, ok := a.Metadata["secure_1psid"].(string); ok && v != "" {
return "cookie", v
}
if v, ok := a.Metadata["__Secure-1PSID"].(string); ok && v != "" {
return "cookie", v
}
}
}
// For Gemini CLI, include project ID in the OAuth account info if present. // For Gemini CLI, include project ID in the OAuth account info if present.
if strings.ToLower(a.Provider) == "gemini-cli" { if strings.ToLower(a.Provider) == "gemini-cli" {
if a.Metadata != nil { if a.Metadata != nil {

View File

@@ -14,8 +14,6 @@ import (
"github.com/router-for-me/CLIProxyAPI/v6/internal/api" "github.com/router-for-me/CLIProxyAPI/v6/internal/api"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config" "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
geminiwebclient "github.com/router-for-me/CLIProxyAPI/v6/internal/provider/gemini-web"
conversation "github.com/router-for-me/CLIProxyAPI/v6/internal/provider/gemini-web/conversation"
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry" "github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
"github.com/router-for-me/CLIProxyAPI/v6/internal/runtime/executor" "github.com/router-for-me/CLIProxyAPI/v6/internal/runtime/executor"
_ "github.com/router-for-me/CLIProxyAPI/v6/internal/usage" _ "github.com/router-for-me/CLIProxyAPI/v6/internal/usage"
@@ -207,23 +205,6 @@ func (s *Service) applyCoreAuthRemoval(ctx context.Context, id string) {
} }
GlobalModelRegistry().UnregisterClient(id) GlobalModelRegistry().UnregisterClient(id)
if existing, ok := s.coreManager.GetByID(id); ok && existing != nil { if existing, ok := s.coreManager.GetByID(id); ok && existing != nil {
if strings.EqualFold(existing.Provider, "gemini-web") {
// Prefer the stable cookie label stored in metadata when available.
var label string
if existing.Metadata != nil {
if v, ok := existing.Metadata["label"].(string); ok {
label = strings.TrimSpace(v)
}
}
if label == "" {
label = strings.TrimSpace(existing.Label)
}
if label != "" {
if err := conversation.RemoveMatchesByLabel(label); err != nil {
log.Debugf("failed to remove gemini web sticky entries for %s: %v", label, err)
}
}
}
existing.Disabled = true existing.Disabled = true
existing.Status = coreauth.StatusDisabled existing.Status = coreauth.StatusDisabled
if _, err := s.coreManager.Update(ctx, existing); err != nil { if _, err := s.coreManager.Update(ctx, existing); err != nil {
@@ -271,9 +252,6 @@ func (s *Service) ensureExecutorsForAuth(a *coreauth.Auth) {
s.coreManager.RegisterExecutor(executor.NewGeminiExecutor(s.cfg)) s.coreManager.RegisterExecutor(executor.NewGeminiExecutor(s.cfg))
case "gemini-cli": case "gemini-cli":
s.coreManager.RegisterExecutor(executor.NewGeminiCLIExecutor(s.cfg)) s.coreManager.RegisterExecutor(executor.NewGeminiCLIExecutor(s.cfg))
case "gemini-web":
s.coreManager.RegisterExecutor(executor.NewGeminiWebExecutor(s.cfg))
s.coreManager.EnableGeminiWebStickySelector()
case "claude": case "claude":
s.coreManager.RegisterExecutor(executor.NewClaudeExecutor(s.cfg)) s.coreManager.RegisterExecutor(executor.NewClaudeExecutor(s.cfg))
case "codex": case "codex":
@@ -536,8 +514,6 @@ func (s *Service) registerModelsForAuth(a *coreauth.Auth) {
models = registry.GetGeminiModels() models = registry.GetGeminiModels()
case "gemini-cli": case "gemini-cli":
models = registry.GetGeminiCLIModels() models = registry.GetGeminiCLIModels()
case "gemini-web":
models = geminiwebclient.GetGeminiWebAliasedModels()
case "claude": case "claude":
models = registry.GetClaudeModels() models = registry.GetClaudeModels()
case "codex": case "codex":