返回 DeepSeek-Reasonix
bounded.go
根目录 / internal / shellrun / bounded.go
1 package shellrun
2
3 import "io"
4
5 // The foreground output caps are one contract shared by every shell execution
6 // path. They live here so a second runner cannot ship a different memory bound
7 // than the one #6473/#6528 established.
8
9 // BoundedOutput collects model-visible combined output under the shared cap:
10 // complete text up to 10 MiB, then a fixed head plus a rolling 64 KiB tail
11 // separated by an explicit truncation notice. Write never short-writes.
12 type BoundedOutput struct {
13 buf *boundedBuffer
14 }
15
16 // NewBoundedOutput returns a collector using the shared foreground caps.
17 func NewBoundedOutput() *BoundedOutput {
18 c := &outputCollector{}
19 c.combined = &boundedBuffer{
20 mu: &c.mu,
21 limit: combinedOutputMaxBytes,
22 tailLimit: combinedOutputTailBytes,
23 marker: combinedOutputTruncated,
24 }
25 return &BoundedOutput{buf: c.combined}
26 }
27
28 // Write appends p, evicting middle output once the cap is crossed.
29 func (o *BoundedOutput) Write(p []byte) (int, error) { return o.buf.Write(p) }
30
31 // WriteString appends s.
32 func (o *BoundedOutput) WriteString(s string) {
33 if s == "" {
34 return
35 }
36 _, _ = o.buf.Write([]byte(s))
37 }
38
39 // String returns the collected output, including the truncation notice when the
40 // cap was crossed.
41 func (o *BoundedOutput) String() string { return o.buf.String() }
42
43 // Truncated reports whether output was evicted.
44 func (o *BoundedOutput) Truncated() bool {
45 o.buf.mu.Lock()
46 defer o.buf.mu.Unlock()
47 return o.buf.truncated
48 }
49
50 // ProgressWriter buffers partial UTF-8 characters until its owner ends the stream.
51 type ProgressWriter interface {
52 io.Writer
53 Flush()
54 }
55
56 // NewProgressWriter returns the shared live-progress sink. Live output crosses
57 // async UI queues and append-only reducers before the bounded final result
58 // replaces it, so it is capped far below the final output cap; a never-ending
59 // command must not exhaust memory on the transient path.
60 func NewProgressWriter(emit func(string)) ProgressWriter {
61 return newProgressWriter(emit, progressOutputMaxBytes, progressOutputTruncated)
62 }
63
63 lines GO