package geminiwebapi import ( "fmt" "os" "strings" log "github.com/sirupsen/logrus" ) // init honors GEMINI_WEBAPI_LOG to keep parity with the Python client. func init() { if lvl := os.Getenv("GEMINI_WEBAPI_LOG"); lvl != "" { SetLogLevel(lvl) } } // SetLogLevel adjusts logging verbosity using CLI-style strings. func SetLogLevel(level string) { switch strings.ToUpper(level) { case "TRACE": log.SetLevel(log.TraceLevel) case "DEBUG": log.SetLevel(log.DebugLevel) case "INFO": log.SetLevel(log.InfoLevel) case "WARNING", "WARN": log.SetLevel(log.WarnLevel) case "ERROR": log.SetLevel(log.ErrorLevel) case "CRITICAL", "FATAL": log.SetLevel(log.FatalLevel) default: log.SetLevel(log.InfoLevel) } } func prefix(format string) string { return "[gemini_webapi] " + format } func Debug(format string, v ...any) { log.Debugf(prefix(format), v...) } // DebugRaw logs without the module prefix; use sparingly for messages // that should integrate with global formatting without extra tags. func DebugRaw(format string, v ...any) { log.Debugf(format, v...) } func Info(format string, v ...any) { log.Infof(prefix(format), v...) } func Warning(format string, v ...any) { log.Warnf(prefix(format), v...) } func Error(format string, v ...any) { log.Errorf(prefix(format), v...) } func Success(format string, v ...any) { log.Infof(prefix("SUCCESS "+format), v...) } // MaskToken28 returns a fixed-length (28) masked representation showing: // first 8 chars + 8 asterisks + 4 middle chars + last 8 chars. // If the input is shorter than 20 characters, it returns a fully masked string // of length min(len(s), 28). func MaskToken28(s string) string { n := len(s) if n == 0 { return "" } if n < 20 { return strings.Repeat("*", n) } // Pick 4 middle characters around the center midStart := n/2 - 2 if midStart < 8 { midStart = 8 } if midStart+4 > n-8 { midStart = n - 8 - 4 if midStart < 8 { midStart = 8 } } prefixByte := s[:8] middle := s[midStart : midStart+4] suffix := s[n-8:] return prefixByte + strings.Repeat("*", 4) + middle + strings.Repeat("*", 4) + suffix } // BuildUpstreamRequestLog builds a compact preview string for upstream request logging. func BuildUpstreamRequestLog(account string, contextOn bool, useTags, explicitContext bool, prompt string, filesCount int, reuse bool, metaLen int, gem *Gem) string { var sb strings.Builder sb.WriteString("\n\n=== GEMINI WEB UPSTREAM ===\n") sb.WriteString(fmt.Sprintf("account: %s\n", account)) if contextOn { sb.WriteString("context_mode: on\n") } else { sb.WriteString("context_mode: off\n") } if reuse { sb.WriteString("reuseIdx: 1\n") } else { sb.WriteString("reuseIdx: 0\n") } sb.WriteString(fmt.Sprintf("useTags: %t\n", useTags)) sb.WriteString(fmt.Sprintf("metadata_len: %d\n", metaLen)) if explicitContext { sb.WriteString("explicit_context: true\n") } else { sb.WriteString("explicit_context: false\n") } if filesCount > 0 { sb.WriteString(fmt.Sprintf("files: %d\n", filesCount)) } if gem != nil { sb.WriteString("gem:\n") if gem.ID != "" { sb.WriteString(fmt.Sprintf(" id: %s\n", gem.ID)) } if gem.Name != "" { sb.WriteString(fmt.Sprintf(" name: %s\n", gem.Name)) } sb.WriteString(fmt.Sprintf(" predefined: %t\n", gem.Predefined)) } else { sb.WriteString("gem: none\n") } chunks := ChunkByRunes(prompt, 4096) preview := prompt truncated := false if len(chunks) > 1 { preview = chunks[0] truncated = true } sb.WriteString("prompt_preview:\n") sb.WriteString(preview) if truncated { sb.WriteString("\n... [truncated]\n") } return sb.String() }