mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-11 00:40:52 +08:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a67b6811d1 | ||
|
|
35fdc4cfd3 | ||
|
|
3ebbab0a9a | ||
|
|
480cd714b2 | ||
|
|
41ee44432d |
@@ -891,6 +891,8 @@ func (h *Handler) RequestAnthropicToken(c *gin.Context) {
|
||||
|
||||
func (h *Handler) RequestGeminiCLIToken(c *gin.Context) {
|
||||
ctx := context.Background()
|
||||
proxyHTTPClient := util.SetProxy(&h.cfg.SDKConfig, &http.Client{})
|
||||
ctx = context.WithValue(ctx, oauth2.HTTPClient, proxyHTTPClient)
|
||||
|
||||
// Optional project ID from query
|
||||
projectID := c.Query("project_id")
|
||||
@@ -976,7 +978,7 @@ func (h *Handler) RequestGeminiCLIToken(c *gin.Context) {
|
||||
requestedProjectID := strings.TrimSpace(projectID)
|
||||
|
||||
// Create token storage (mirrors internal/auth/gemini createTokenStorage)
|
||||
httpClient := conf.Client(ctx, token)
|
||||
authHTTPClient := conf.Client(ctx, token)
|
||||
req, errNewRequest := http.NewRequestWithContext(ctx, "GET", "https://www.googleapis.com/oauth2/v1/userinfo?alt=json", nil)
|
||||
if errNewRequest != nil {
|
||||
log.Errorf("Could not get user info: %v", errNewRequest)
|
||||
@@ -986,7 +988,7 @@ func (h *Handler) RequestGeminiCLIToken(c *gin.Context) {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.AccessToken))
|
||||
|
||||
resp, errDo := httpClient.Do(req)
|
||||
resp, errDo := authHTTPClient.Do(req)
|
||||
if errDo != nil {
|
||||
log.Errorf("Failed to execute request: %v", errDo)
|
||||
oauthStatus[state] = "Failed to execute request"
|
||||
|
||||
@@ -599,6 +599,7 @@ func SaveConfigPreserveComments(configFile string, cfg *Config) error {
|
||||
// Remove deprecated auth block before merging to avoid persisting it again.
|
||||
removeMapKey(original.Content[0], "auth")
|
||||
removeLegacyOpenAICompatAPIKeys(original.Content[0])
|
||||
pruneMappingToGeneratedKeys(original.Content[0], generated.Content[0], "oauth-excluded-models")
|
||||
|
||||
// Merge generated into original in-place, preserving comments/order of existing nodes.
|
||||
mergeMappingPreserve(original.Content[0], generated.Content[0])
|
||||
@@ -797,6 +798,10 @@ func mergeNodePreserve(dst, src *yaml.Node) {
|
||||
continue
|
||||
}
|
||||
mergeNodePreserve(dst.Content[i], src.Content[i])
|
||||
if dst.Content[i] != nil && src.Content[i] != nil &&
|
||||
dst.Content[i].Kind == yaml.MappingNode && src.Content[i].Kind == yaml.MappingNode {
|
||||
pruneMissingMapKeys(dst.Content[i], src.Content[i])
|
||||
}
|
||||
}
|
||||
// Append any extra items from src
|
||||
for i := len(dst.Content); i < len(src.Content); i++ {
|
||||
@@ -1075,6 +1080,73 @@ func removeLegacyOpenAICompatAPIKeys(root *yaml.Node) {
|
||||
}
|
||||
}
|
||||
|
||||
func pruneMappingToGeneratedKeys(dstRoot, srcRoot *yaml.Node, key string) {
|
||||
if key == "" || dstRoot == nil || srcRoot == nil {
|
||||
return
|
||||
}
|
||||
if dstRoot.Kind != yaml.MappingNode || srcRoot.Kind != yaml.MappingNode {
|
||||
return
|
||||
}
|
||||
dstIdx := findMapKeyIndex(dstRoot, key)
|
||||
if dstIdx < 0 || dstIdx+1 >= len(dstRoot.Content) {
|
||||
return
|
||||
}
|
||||
srcIdx := findMapKeyIndex(srcRoot, key)
|
||||
if srcIdx < 0 {
|
||||
removeMapKey(dstRoot, key)
|
||||
return
|
||||
}
|
||||
if srcIdx+1 >= len(srcRoot.Content) {
|
||||
return
|
||||
}
|
||||
srcVal := srcRoot.Content[srcIdx+1]
|
||||
dstVal := dstRoot.Content[dstIdx+1]
|
||||
if srcVal == nil {
|
||||
dstRoot.Content[dstIdx+1] = nil
|
||||
return
|
||||
}
|
||||
if srcVal.Kind != yaml.MappingNode {
|
||||
dstRoot.Content[dstIdx+1] = deepCopyNode(srcVal)
|
||||
return
|
||||
}
|
||||
if dstVal == nil || dstVal.Kind != yaml.MappingNode {
|
||||
dstRoot.Content[dstIdx+1] = deepCopyNode(srcVal)
|
||||
return
|
||||
}
|
||||
pruneMissingMapKeys(dstVal, srcVal)
|
||||
}
|
||||
|
||||
func pruneMissingMapKeys(dstMap, srcMap *yaml.Node) {
|
||||
if dstMap == nil || srcMap == nil || dstMap.Kind != yaml.MappingNode || srcMap.Kind != yaml.MappingNode {
|
||||
return
|
||||
}
|
||||
keep := make(map[string]struct{}, len(srcMap.Content)/2)
|
||||
for i := 0; i+1 < len(srcMap.Content); i += 2 {
|
||||
keyNode := srcMap.Content[i]
|
||||
if keyNode == nil {
|
||||
continue
|
||||
}
|
||||
key := strings.TrimSpace(keyNode.Value)
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
keep[key] = struct{}{}
|
||||
}
|
||||
for i := 0; i+1 < len(dstMap.Content); {
|
||||
keyNode := dstMap.Content[i]
|
||||
if keyNode == nil {
|
||||
i += 2
|
||||
continue
|
||||
}
|
||||
key := strings.TrimSpace(keyNode.Value)
|
||||
if _, ok := keep[key]; !ok {
|
||||
dstMap.Content = append(dstMap.Content[:i], dstMap.Content[i+2:]...)
|
||||
continue
|
||||
}
|
||||
i += 2
|
||||
}
|
||||
}
|
||||
|
||||
// normalizeCollectionNodeStyles forces YAML collections to use block notation, keeping
|
||||
// lists and maps readable. Empty sequences retain flow style ([]) so empty list markers
|
||||
// remain compact.
|
||||
|
||||
@@ -91,6 +91,11 @@ func ConvertGeminiRequestToGemini(_ string, inputRawJSON []byte, _ bool) []byte
|
||||
return true
|
||||
})
|
||||
|
||||
if gjson.GetBytes(rawJSON, "generationConfig.responseSchema").Exists() {
|
||||
strJson, _ := util.RenameKey(string(out), "generationConfig.responseSchema", "generationConfig.responseJsonSchema")
|
||||
out = []byte(strJson)
|
||||
}
|
||||
|
||||
out = common.AttachDefaultSafetySettings(out, "safetySettings")
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -162,14 +162,12 @@ func NewWatcher(configPath, authDir string, reloadCallback func(*config.Config))
|
||||
|
||||
// Start begins watching the configuration file and authentication directory
|
||||
func (w *Watcher) Start(ctx context.Context) error {
|
||||
// Watch the config file's parent directory instead of the file itself.
|
||||
// This handles editors that use atomic save (write to temp, then rename).
|
||||
configDir := filepath.Dir(w.configPath)
|
||||
if errAddConfig := w.watcher.Add(configDir); errAddConfig != nil {
|
||||
log.Errorf("failed to watch config directory %s: %v", configDir, errAddConfig)
|
||||
// Watch the config file
|
||||
if errAddConfig := w.watcher.Add(w.configPath); errAddConfig != nil {
|
||||
log.Errorf("failed to watch config file %s: %v", w.configPath, errAddConfig)
|
||||
return errAddConfig
|
||||
}
|
||||
log.Debugf("watching config directory: %s (for file: %s)", configDir, filepath.Base(w.configPath))
|
||||
log.Debugf("watching config file: %s", w.configPath)
|
||||
|
||||
// Watch the auth directory
|
||||
if errAddAuthDir := w.watcher.Add(w.authDir); errAddAuthDir != nil {
|
||||
@@ -714,23 +712,7 @@ func (w *Watcher) isKnownAuthFile(path string) bool {
|
||||
func (w *Watcher) handleEvent(event fsnotify.Event) {
|
||||
// Filter only relevant events: config file or auth-dir JSON files.
|
||||
configOps := fsnotify.Write | fsnotify.Create | fsnotify.Rename
|
||||
// Check if this event is for our config file (handle both exact match and basename match for directory watching)
|
||||
isConfigEvent := false
|
||||
if event.Op&configOps != 0 {
|
||||
// Exact path match
|
||||
if event.Name == w.configPath {
|
||||
isConfigEvent = true
|
||||
} else {
|
||||
// Check if basename matches and it's in the config directory (for atomic save detection)
|
||||
configDir := filepath.Dir(w.configPath)
|
||||
configBase := filepath.Base(w.configPath)
|
||||
eventDir := filepath.Dir(event.Name)
|
||||
eventBase := filepath.Base(event.Name)
|
||||
if eventDir == configDir && eventBase == configBase {
|
||||
isConfigEvent = true
|
||||
}
|
||||
}
|
||||
}
|
||||
isConfigEvent := event.Name == w.configPath && event.Op&configOps != 0
|
||||
authOps := fsnotify.Create | fsnotify.Write | fsnotify.Remove | fsnotify.Rename
|
||||
isAuthJSON := strings.HasPrefix(event.Name, w.authDir) && strings.HasSuffix(event.Name, ".json") && event.Op&authOps != 0
|
||||
if !isConfigEvent && !isAuthJSON {
|
||||
|
||||
Reference in New Issue
Block a user