| package synthesizer |
|
|
| import ( |
| "encoding/json" |
| "fmt" |
| "os" |
| "path/filepath" |
| "strings" |
| "time" |
|
|
| "github.com/router-for-me/CLIProxyAPI/v6/internal/runtime/geminicli" |
| coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" |
| ) |
|
|
| |
| |
| type FileSynthesizer struct{} |
|
|
| |
| func NewFileSynthesizer() *FileSynthesizer { |
| return &FileSynthesizer{} |
| } |
|
|
| |
| func (s *FileSynthesizer) Synthesize(ctx *SynthesisContext) ([]*coreauth.Auth, error) { |
| out := make([]*coreauth.Auth, 0, 16) |
| if ctx == nil || ctx.AuthDir == "" { |
| return out, nil |
| } |
|
|
| entries, err := os.ReadDir(ctx.AuthDir) |
| if err != nil { |
| |
| return out, nil |
| } |
|
|
| now := ctx.Now |
| cfg := ctx.Config |
|
|
| for _, e := range entries { |
| if e.IsDir() { |
| continue |
| } |
| name := e.Name() |
| if !strings.HasSuffix(strings.ToLower(name), ".json") { |
| continue |
| } |
| full := filepath.Join(ctx.AuthDir, name) |
| data, errRead := os.ReadFile(full) |
| if errRead != nil || len(data) == 0 { |
| continue |
| } |
| var metadata map[string]any |
| if errUnmarshal := json.Unmarshal(data, &metadata); errUnmarshal != nil { |
| continue |
| } |
| t, _ := metadata["type"].(string) |
| if t == "" { |
| continue |
| } |
| provider := strings.ToLower(t) |
| if provider == "gemini" { |
| provider = "gemini-cli" |
| } |
| label := provider |
| if email, _ := metadata["email"].(string); email != "" { |
| label = email |
| } |
| |
| id := full |
| if rel, errRel := filepath.Rel(ctx.AuthDir, full); errRel == nil && rel != "" { |
| id = rel |
| } |
|
|
| proxyURL := "" |
| if p, ok := metadata["proxy_url"].(string); ok { |
| proxyURL = p |
| } |
|
|
| prefix := "" |
| if rawPrefix, ok := metadata["prefix"].(string); ok { |
| trimmed := strings.TrimSpace(rawPrefix) |
| trimmed = strings.Trim(trimmed, "/") |
| if trimmed != "" && !strings.Contains(trimmed, "/") { |
| prefix = trimmed |
| } |
| } |
|
|
| disabled, _ := metadata["disabled"].(bool) |
| status := coreauth.StatusActive |
| if disabled { |
| status = coreauth.StatusDisabled |
| } |
|
|
| a := &coreauth.Auth{ |
| ID: id, |
| Provider: provider, |
| Label: label, |
| Prefix: prefix, |
| Status: status, |
| Disabled: disabled, |
| Attributes: map[string]string{ |
| "source": full, |
| "path": full, |
| }, |
| ProxyURL: proxyURL, |
| Metadata: metadata, |
| CreatedAt: now, |
| UpdatedAt: now, |
| } |
| ApplyAuthExcludedModelsMeta(a, cfg, nil, "oauth") |
| if provider == "gemini-cli" { |
| if virtuals := SynthesizeGeminiVirtualAuths(a, metadata, now); len(virtuals) > 0 { |
| for _, v := range virtuals { |
| ApplyAuthExcludedModelsMeta(v, cfg, nil, "oauth") |
| } |
| out = append(out, a) |
| out = append(out, virtuals...) |
| continue |
| } |
| } |
| out = append(out, a) |
| } |
| return out, nil |
| } |
|
|
| |
| |
| func SynthesizeGeminiVirtualAuths(primary *coreauth.Auth, metadata map[string]any, now time.Time) []*coreauth.Auth { |
| if primary == nil || metadata == nil { |
| return nil |
| } |
| projects := splitGeminiProjectIDs(metadata) |
| if len(projects) <= 1 { |
| return nil |
| } |
| email, _ := metadata["email"].(string) |
| shared := geminicli.NewSharedCredential(primary.ID, email, metadata, projects) |
| primary.Disabled = true |
| primary.Status = coreauth.StatusDisabled |
| primary.Runtime = shared |
| if primary.Attributes == nil { |
| primary.Attributes = make(map[string]string) |
| } |
| primary.Attributes["gemini_virtual_primary"] = "true" |
| primary.Attributes["virtual_children"] = strings.Join(projects, ",") |
| source := primary.Attributes["source"] |
| authPath := primary.Attributes["path"] |
| originalProvider := primary.Provider |
| if originalProvider == "" { |
| originalProvider = "gemini-cli" |
| } |
| label := primary.Label |
| if label == "" { |
| label = originalProvider |
| } |
| virtuals := make([]*coreauth.Auth, 0, len(projects)) |
| for _, projectID := range projects { |
| attrs := map[string]string{ |
| "runtime_only": "true", |
| "gemini_virtual_parent": primary.ID, |
| "gemini_virtual_project": projectID, |
| } |
| if source != "" { |
| attrs["source"] = source |
| } |
| if authPath != "" { |
| attrs["path"] = authPath |
| } |
| metadataCopy := map[string]any{ |
| "email": email, |
| "project_id": projectID, |
| "virtual": true, |
| "virtual_parent_id": primary.ID, |
| "type": metadata["type"], |
| } |
| if v, ok := metadata["disable_cooling"]; ok { |
| metadataCopy["disable_cooling"] = v |
| } else if v, ok := metadata["disable-cooling"]; ok { |
| metadataCopy["disable_cooling"] = v |
| } |
| if v, ok := metadata["request_retry"]; ok { |
| metadataCopy["request_retry"] = v |
| } else if v, ok := metadata["request-retry"]; ok { |
| metadataCopy["request_retry"] = v |
| } |
| proxy := strings.TrimSpace(primary.ProxyURL) |
| if proxy != "" { |
| metadataCopy["proxy_url"] = proxy |
| } |
| virtual := &coreauth.Auth{ |
| ID: buildGeminiVirtualID(primary.ID, projectID), |
| Provider: originalProvider, |
| Label: fmt.Sprintf("%s [%s]", label, projectID), |
| Status: coreauth.StatusActive, |
| Attributes: attrs, |
| Metadata: metadataCopy, |
| ProxyURL: primary.ProxyURL, |
| Prefix: primary.Prefix, |
| CreatedAt: primary.CreatedAt, |
| UpdatedAt: primary.UpdatedAt, |
| Runtime: geminicli.NewVirtualCredential(projectID, shared), |
| } |
| virtuals = append(virtuals, virtual) |
| } |
| return virtuals |
| } |
|
|
| |
| func splitGeminiProjectIDs(metadata map[string]any) []string { |
| raw, _ := metadata["project_id"].(string) |
| trimmed := strings.TrimSpace(raw) |
| if trimmed == "" { |
| return nil |
| } |
| parts := strings.Split(trimmed, ",") |
| result := make([]string, 0, len(parts)) |
| seen := make(map[string]struct{}, len(parts)) |
| for _, part := range parts { |
| id := strings.TrimSpace(part) |
| if id == "" { |
| continue |
| } |
| if _, ok := seen[id]; ok { |
| continue |
| } |
| seen[id] = struct{}{} |
| result = append(result, id) |
| } |
| return result |
| } |
|
|
| |
| func buildGeminiVirtualID(baseID, projectID string) string { |
| project := strings.TrimSpace(projectID) |
| if project == "" { |
| project = "project" |
| } |
| replacer := strings.NewReplacer("/", "_", "\\", "_", " ", "_") |
| return fmt.Sprintf("%s::%s", baseID, replacer.Replace(project)) |
| } |
|
|