From 49232372a7d17abb5f8aa5f8c477673e59c27f3a Mon Sep 17 00:00:00 2001 From: hkfires <10558748+hkfires@users.noreply.github.com> Date: Tue, 23 Sep 2025 09:05:40 +0800 Subject: [PATCH 1/3] fix(log): Trim trailing newlines to prevent blank log lines --- cmd/server/main.go | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/cmd/server/main.go b/cmd/server/main.go index 6d6c84cd..5289f209 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -46,10 +46,12 @@ func (m *LogFormatter) Format(entry *log.Entry) ([]byte, error) { b = &bytes.Buffer{} } - timestamp := entry.Time.Format("2006-01-02 15:04:05") - var newLog string - // Customize the log format to include timestamp, level, caller file/line, and message. - newLog = fmt.Sprintf("[%s] [%s] [%s:%d] %s\n", timestamp, entry.Level, filepath.Base(entry.Caller.File), entry.Caller.Line, entry.Message) + timestamp := entry.Time.Format("2006-01-02 15:04:05") + var newLog string + // Ensure message doesn't carry trailing newlines; formatter appends one. + msg := strings.TrimRight(entry.Message, "\r\n") + // Customize the log format to include timestamp, level, caller file/line, and message. + newLog = fmt.Sprintf("[%s] [%s] [%s:%d] %s\n", timestamp, entry.Level, filepath.Base(entry.Caller.File), entry.Caller.Line, msg) b.WriteString(newLog) return b.Bytes(), nil @@ -83,9 +85,13 @@ func init() { gin.DefaultWriter = ginInfoWriter ginErrorWriter = log.StandardLogger().WriterLevel(log.ErrorLevel) gin.DefaultErrorWriter = ginErrorWriter - gin.DebugPrintFunc = func(format string, values ...interface{}) { - log.StandardLogger().Infof(format, values...) - } + gin.DebugPrintFunc = func(format string, values ...interface{}) { + // Trim trailing newlines from Gin's formatted messages to avoid blank lines. + // Gin's debug prints usually include a trailing "\n"; our formatter also appends one. + // Removing it here ensures a single newline per entry. + format = strings.TrimRight(format, "\r\n") + log.StandardLogger().Infof(format, values...) + } log.RegisterExitHandler(func() { if logWriter != nil { _ = logWriter.Close() From e8e00d4cb898e60123b097f0cf3f186639e24f0b Mon Sep 17 00:00:00 2001 From: hkfires <10558748+hkfires@users.noreply.github.com> Date: Tue, 23 Sep 2025 09:31:06 +0800 Subject: [PATCH 2/3] refactor(watcher): Remove unnecessary log separator --- internal/watcher/watcher.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/watcher/watcher.go b/internal/watcher/watcher.go index 905d1908..7d1f43fd 100644 --- a/internal/watcher/watcher.go +++ b/internal/watcher/watcher.go @@ -26,7 +26,7 @@ import ( // "github.com/router-for-me/CLIProxyAPI/v6/internal/client" "github.com/router-for-me/CLIProxyAPI/v6/internal/config" // "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/util" coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" @@ -807,7 +807,6 @@ func (w *Watcher) loadFileClients(cfg *config.Config) int { } if !info.IsDir() && strings.HasSuffix(strings.ToLower(info.Name()), ".json") { authFileCount++ - misc.LogCredentialSeparator() log.Debugf("processing auth file %d: %s", authFileCount, filepath.Base(path)) // Count readable JSON files as successful auth entries if data, errCreate := util.ReadAuthFileWithRetry(path, authFileReadMaxAttempts, authFileReadRetryDelay); errCreate == nil && len(data) > 0 { From 50c8f7f96f38435ee60aee05357a7642b567b573 Mon Sep 17 00:00:00 2001 From: hkfires <10558748+hkfires@users.noreply.github.com> Date: Tue, 23 Sep 2025 10:04:00 +0800 Subject: [PATCH 3/3] feat(gemini-web): Inject fallback text for image-only flash model responses --- cmd/server/main.go | 26 +++++++++---------- internal/runtime/executor/gemini_web_state.go | 19 ++++++++++++++ 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/cmd/server/main.go b/cmd/server/main.go index 5289f209..a9af038e 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -46,12 +46,12 @@ func (m *LogFormatter) Format(entry *log.Entry) ([]byte, error) { b = &bytes.Buffer{} } - timestamp := entry.Time.Format("2006-01-02 15:04:05") - var newLog string - // Ensure message doesn't carry trailing newlines; formatter appends one. - msg := strings.TrimRight(entry.Message, "\r\n") - // Customize the log format to include timestamp, level, caller file/line, and message. - newLog = fmt.Sprintf("[%s] [%s] [%s:%d] %s\n", timestamp, entry.Level, filepath.Base(entry.Caller.File), entry.Caller.Line, msg) + timestamp := entry.Time.Format("2006-01-02 15:04:05") + var newLog string + // Ensure message doesn't carry trailing newlines; formatter appends one. + msg := strings.TrimRight(entry.Message, "\r\n") + // Customize the log format to include timestamp, level, caller file/line, and message. + newLog = fmt.Sprintf("[%s] [%s] [%s:%d] %s\n", timestamp, entry.Level, filepath.Base(entry.Caller.File), entry.Caller.Line, msg) b.WriteString(newLog) return b.Bytes(), nil @@ -85,13 +85,13 @@ func init() { gin.DefaultWriter = ginInfoWriter ginErrorWriter = log.StandardLogger().WriterLevel(log.ErrorLevel) gin.DefaultErrorWriter = ginErrorWriter - gin.DebugPrintFunc = func(format string, values ...interface{}) { - // Trim trailing newlines from Gin's formatted messages to avoid blank lines. - // Gin's debug prints usually include a trailing "\n"; our formatter also appends one. - // Removing it here ensures a single newline per entry. - format = strings.TrimRight(format, "\r\n") - log.StandardLogger().Infof(format, values...) - } + gin.DebugPrintFunc = func(format string, values ...interface{}) { + // Trim trailing newlines from Gin's formatted messages to avoid blank lines. + // Gin's debug prints usually include a trailing "\n"; our formatter also appends one. + // Removing it here ensures a single newline per entry. + format = strings.TrimRight(format, "\r\n") + log.StandardLogger().Infof(format, values...) + } log.RegisterExitHandler(func() { if logWriter != nil { _ = logWriter.Close() diff --git a/internal/runtime/executor/gemini_web_state.go b/internal/runtime/executor/gemini_web_state.go index 11514f6c..5ce4770e 100644 --- a/internal/runtime/executor/gemini_web_state.go +++ b/internal/runtime/executor/gemini_web_state.go @@ -412,6 +412,25 @@ func (s *geminiWebState) send(ctx context.Context, modelName string, reqPayload return nil, s.wrapSendError(err), nil } + // Hook: For gemini-2.5-flash-image-preview, if the API returns only images without any text, + // inject a small textual summary so that conversation persistence has non-empty assistant text. + // This helps conversation recovery (conv store) to match sessions reliably. + if strings.EqualFold(modelName, "gemini-2.5-flash-image-preview") { + if len(output.Candidates) > 0 { + c := output.Candidates[output.Chosen] + hasNoText := strings.TrimSpace(c.Text) == "" + hasImages := len(c.GeneratedImages) > 0 || len(c.WebImages) > 0 + if hasNoText && hasImages { + // Build a stable, concise fallback text. Avoid dynamic details to keep hashes stable. + // Prefer a deterministic phrase with count to aid users while keeping consistency. + fallback := "Done" + // Mutate the chosen candidate's text so both response conversion and + // conversation persistence observe the same fallback. + output.Candidates[output.Chosen].Text = fallback + } + } + } + gemBytes, err := geminiwebapi.ConvertOutputToGemini(&output, modelName, prep.prompt) if err != nil { return nil, &interfaces.ErrorMessage{StatusCode: 500, Error: err}, nil