mirror of
https://github.com/cexll/myclaude.git
synced 2025-12-24 13:47:58 +08:00
fix(gemini): filter noisy stderr output from gemini backend (#83)
* fix(gemini): filter noisy stderr output from gemini backend - Add filteringWriter to filter [STARTUP], Warning, Session cleanup etc. - Apply filter only for gemini backend stderr output - Add unit tests for filtering logic * fix: use defer for stderrFilter.Flush to cover all return paths Address review feedback: ensure filter is flushed on failure paths
This commit is contained in:
@@ -683,8 +683,17 @@ func runCodexTaskWithContext(parentCtx context.Context, taskSpec TaskSpec, backe
|
|||||||
if stderrLogger != nil {
|
if stderrLogger != nil {
|
||||||
stderrWriters = append(stderrWriters, stderrLogger)
|
stderrWriters = append(stderrWriters, stderrLogger)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For gemini backend, filter noisy stderr output
|
||||||
|
var stderrFilter *filteringWriter
|
||||||
if !silent {
|
if !silent {
|
||||||
stderrWriters = append([]io.Writer{os.Stderr}, stderrWriters...)
|
stderrOut := io.Writer(os.Stderr)
|
||||||
|
if cfg.Backend == "gemini" {
|
||||||
|
stderrFilter = newFilteringWriter(os.Stderr, geminiNoisePatterns)
|
||||||
|
stderrOut = stderrFilter
|
||||||
|
defer stderrFilter.Flush()
|
||||||
|
}
|
||||||
|
stderrWriters = append([]io.Writer{stderrOut}, stderrWriters...)
|
||||||
}
|
}
|
||||||
if len(stderrWriters) == 1 {
|
if len(stderrWriters) == 1 {
|
||||||
cmd.SetStderr(stderrWriters[0])
|
cmd.SetStderr(stderrWriters[0])
|
||||||
|
|||||||
66
codeagent-wrapper/filter.go
Normal file
66
codeagent-wrapper/filter.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// geminiNoisePatterns contains stderr patterns to filter for gemini backend
|
||||||
|
var geminiNoisePatterns = []string{
|
||||||
|
"[STARTUP]",
|
||||||
|
"Session cleanup disabled",
|
||||||
|
"Warning:",
|
||||||
|
"(node:",
|
||||||
|
"(Use `node --trace-warnings",
|
||||||
|
"Loaded cached credentials",
|
||||||
|
"Loading extension:",
|
||||||
|
"YOLO mode is enabled",
|
||||||
|
}
|
||||||
|
|
||||||
|
// filteringWriter wraps an io.Writer and filters out lines matching patterns
|
||||||
|
type filteringWriter struct {
|
||||||
|
w io.Writer
|
||||||
|
patterns []string
|
||||||
|
buf bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFilteringWriter(w io.Writer, patterns []string) *filteringWriter {
|
||||||
|
return &filteringWriter{w: w, patterns: patterns}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filteringWriter) Write(p []byte) (n int, err error) {
|
||||||
|
f.buf.Write(p)
|
||||||
|
for {
|
||||||
|
line, err := f.buf.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
// incomplete line, put it back
|
||||||
|
f.buf.WriteString(line)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !f.shouldFilter(line) {
|
||||||
|
f.w.Write([]byte(line))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filteringWriter) shouldFilter(line string) bool {
|
||||||
|
for _, pattern := range f.patterns {
|
||||||
|
if strings.Contains(line, pattern) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush writes any remaining buffered content
|
||||||
|
func (f *filteringWriter) Flush() {
|
||||||
|
if f.buf.Len() > 0 {
|
||||||
|
remaining := f.buf.String()
|
||||||
|
if !f.shouldFilter(remaining) {
|
||||||
|
f.w.Write([]byte(remaining))
|
||||||
|
}
|
||||||
|
f.buf.Reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
73
codeagent-wrapper/filter_test.go
Normal file
73
codeagent-wrapper/filter_test.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFilteringWriter(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
patterns []string
|
||||||
|
input string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "filter STARTUP lines",
|
||||||
|
patterns: geminiNoisePatterns,
|
||||||
|
input: "[STARTUP] Recording metric\nHello World\n[STARTUP] Another line\n",
|
||||||
|
want: "Hello World\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "filter Warning lines",
|
||||||
|
patterns: geminiNoisePatterns,
|
||||||
|
input: "Warning: something bad\nActual output\n",
|
||||||
|
want: "Actual output\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "filter multiple patterns",
|
||||||
|
patterns: geminiNoisePatterns,
|
||||||
|
input: "YOLO mode is enabled\nSession cleanup disabled\nReal content\nLoading extension: foo\n",
|
||||||
|
want: "Real content\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no filtering needed",
|
||||||
|
patterns: geminiNoisePatterns,
|
||||||
|
input: "Line 1\nLine 2\nLine 3\n",
|
||||||
|
want: "Line 1\nLine 2\nLine 3\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty input",
|
||||||
|
patterns: geminiNoisePatterns,
|
||||||
|
input: "",
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
fw := newFilteringWriter(&buf, tt.patterns)
|
||||||
|
fw.Write([]byte(tt.input))
|
||||||
|
fw.Flush()
|
||||||
|
|
||||||
|
if got := buf.String(); got != tt.want {
|
||||||
|
t.Errorf("got %q, want %q", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilteringWriterPartialLines(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
fw := newFilteringWriter(&buf, geminiNoisePatterns)
|
||||||
|
|
||||||
|
// Write partial line
|
||||||
|
fw.Write([]byte("Hello "))
|
||||||
|
fw.Write([]byte("World\n"))
|
||||||
|
fw.Flush()
|
||||||
|
|
||||||
|
if got := buf.String(); got != "Hello World\n" {
|
||||||
|
t.Errorf("got %q, want %q", got, "Hello World\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user