Spaces:
Paused
Paused
| // Package util provides utility functions for the CLI Proxy API server. | |
| // It includes helper functions for logging configuration, file system operations, | |
| // and other common utilities used throughout the application. | |
| package util | |
| import ( | |
| "context" | |
| "fmt" | |
| "os" | |
| "path/filepath" | |
| "regexp" | |
| "strings" | |
| "github.com/router-for-me/CLIProxyAPI/v6/internal/config" | |
| log "github.com/sirupsen/logrus" | |
| ) | |
| var functionNameSanitizer = regexp.MustCompile(`[^a-zA-Z0-9_.:-]`) | |
| // SanitizeFunctionName ensures a function name matches the requirements for Gemini/Vertex AI. | |
| // It replaces invalid characters with underscores, ensures it starts with a letter or underscore, | |
| // and truncates it to 64 characters if necessary. | |
| // Regex Rule: [^a-zA-Z0-9_.:-] replaced with _. | |
| func SanitizeFunctionName(name string) string { | |
| if name == "" { | |
| return "" | |
| } | |
| // Replace invalid characters with underscore | |
| sanitized := functionNameSanitizer.ReplaceAllString(name, "_") | |
| // Ensure it starts with a letter or underscore | |
| // Re-reading requirements: Must start with a letter or an underscore. | |
| if len(sanitized) > 0 { | |
| first := sanitized[0] | |
| if !((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z') || first == '_') { | |
| // If it starts with an allowed character but not allowed at the beginning (digit, dot, colon, dash), | |
| // we must prepend an underscore. | |
| // To stay within the 64-character limit while prepending, we must truncate first. | |
| if len(sanitized) >= 64 { | |
| sanitized = sanitized[:63] | |
| } | |
| sanitized = "_" + sanitized | |
| } | |
| } else { | |
| sanitized = "_" | |
| } | |
| // Truncate to 64 characters | |
| if len(sanitized) > 64 { | |
| sanitized = sanitized[:64] | |
| } | |
| return sanitized | |
| } | |
| // SetLogLevel configures the logrus log level based on the configuration. | |
| // It sets the log level to DebugLevel if debug mode is enabled, otherwise to InfoLevel. | |
| func SetLogLevel(cfg *config.Config) { | |
| currentLevel := log.GetLevel() | |
| var newLevel log.Level | |
| if cfg.Debug { | |
| newLevel = log.DebugLevel | |
| } else { | |
| newLevel = log.InfoLevel | |
| } | |
| if currentLevel != newLevel { | |
| log.SetLevel(newLevel) | |
| log.Infof("log level changed from %s to %s (debug=%t)", currentLevel, newLevel, cfg.Debug) | |
| } | |
| } | |
| // ResolveAuthDir normalizes the auth directory path for consistent reuse throughout the app. | |
| // It expands a leading tilde (~) to the user's home directory and returns a cleaned path. | |
| func ResolveAuthDir(authDir string) (string, error) { | |
| if authDir == "" { | |
| return "", nil | |
| } | |
| if strings.HasPrefix(authDir, "~") { | |
| home, err := os.UserHomeDir() | |
| if err != nil { | |
| return "", fmt.Errorf("resolve auth dir: %w", err) | |
| } | |
| remainder := strings.TrimPrefix(authDir, "~") | |
| remainder = strings.TrimLeft(remainder, "/\\") | |
| if remainder == "" { | |
| return filepath.Clean(home), nil | |
| } | |
| normalized := strings.ReplaceAll(remainder, "\\", "/") | |
| return filepath.Clean(filepath.Join(home, filepath.FromSlash(normalized))), nil | |
| } | |
| return filepath.Clean(authDir), nil | |
| } | |
| // CountAuthFiles returns the number of auth records available through the provided Store. | |
| // For filesystem-backed stores, this reflects the number of JSON auth files under the configured directory. | |
| func CountAuthFiles[T any](ctx context.Context, store interface { | |
| List(context.Context) ([]T, error) | |
| }) int { | |
| if store == nil { | |
| return 0 | |
| } | |
| if ctx == nil { | |
| ctx = context.Background() | |
| } | |
| entries, err := store.List(ctx) | |
| if err != nil { | |
| log.Debugf("countAuthFiles: failed to list auth records: %v", err) | |
| return 0 | |
| } | |
| return len(entries) | |
| } | |
| // WritablePath returns the cleaned WRITABLE_PATH environment variable when it is set. | |
| // It accepts both uppercase and lowercase variants for compatibility with existing conventions. | |
| func WritablePath() string { | |
| for _, key := range []string{"WRITABLE_PATH", "writable_path"} { | |
| if value, ok := os.LookupEnv(key); ok { | |
| trimmed := strings.TrimSpace(value) | |
| if trimmed != "" { | |
| return filepath.Clean(trimmed) | |
| } | |
| } | |
| } | |
| return "" | |
| } | |