| package shared |
|
|
| import ( |
| "strings" |
|
|
| "ds2api/internal/sse" |
| ) |
|
|
| type StreamAccumulator struct { |
| ThinkingEnabled bool |
| SearchEnabled bool |
| StripReferenceMarkers bool |
|
|
| RawThinking strings.Builder |
| Thinking strings.Builder |
| ToolDetectionThinking strings.Builder |
| RawText strings.Builder |
| Text strings.Builder |
| } |
|
|
| type StreamPartDelta struct { |
| Type string |
| RawText string |
| VisibleText string |
| CitationOnly bool |
| } |
|
|
| type StreamAccumulatorResult struct { |
| ContentSeen bool |
| Parts []StreamPartDelta |
| } |
|
|
| func (a *StreamAccumulator) Apply(parsed sse.LineResult) StreamAccumulatorResult { |
| out := StreamAccumulatorResult{} |
| for _, p := range parsed.ToolDetectionThinkingParts { |
| trimmed := sse.TrimContinuationOverlapFromBuilder(&a.ToolDetectionThinking, p.Text) |
| if trimmed != "" { |
| a.ToolDetectionThinking.WriteString(trimmed) |
| } |
| } |
| for _, p := range parsed.Parts { |
| if p.Type == "thinking" { |
| delta := a.applyThinkingPart(p.Text) |
| if delta.RawText != "" { |
| out.ContentSeen = true |
| } |
| if delta.RawText != "" || delta.VisibleText != "" { |
| out.Parts = append(out.Parts, delta) |
| } |
| continue |
| } |
| delta := a.applyTextPart(p.Text) |
| if delta.RawText != "" { |
| out.ContentSeen = true |
| } |
| if delta.RawText != "" || delta.VisibleText != "" || delta.CitationOnly { |
| out.Parts = append(out.Parts, delta) |
| } |
| } |
| return out |
| } |
|
|
| func (a *StreamAccumulator) applyThinkingPart(text string) StreamPartDelta { |
| rawTrimmed := sse.TrimContinuationOverlapFromBuilder(&a.RawThinking, text) |
| if rawTrimmed != "" { |
| a.RawThinking.WriteString(rawTrimmed) |
| } |
| delta := StreamPartDelta{Type: "thinking", RawText: rawTrimmed} |
| if !a.ThinkingEnabled || rawTrimmed == "" { |
| return delta |
| } |
| cleanedText := CleanVisibleOutput(rawTrimmed, a.StripReferenceMarkers) |
| if cleanedText == "" { |
| return delta |
| } |
| trimmed := sse.TrimContinuationOverlapFromBuilder(&a.Thinking, cleanedText) |
| if trimmed == "" { |
| return delta |
| } |
| a.Thinking.WriteString(trimmed) |
| delta.VisibleText = trimmed |
| return delta |
| } |
|
|
| func (a *StreamAccumulator) applyTextPart(text string) StreamPartDelta { |
| rawTrimmed := sse.TrimContinuationOverlapFromBuilder(&a.RawText, text) |
| if rawTrimmed == "" { |
| return StreamPartDelta{Type: "text"} |
| } |
| a.RawText.WriteString(rawTrimmed) |
| delta := StreamPartDelta{Type: "text", RawText: rawTrimmed} |
| if a.SearchEnabled && sse.IsCitation(rawTrimmed) { |
| delta.CitationOnly = true |
| return delta |
| } |
| cleanedText := CleanVisibleOutput(rawTrimmed, a.StripReferenceMarkers) |
| trimmed := sse.TrimContinuationOverlapFromBuilder(&a.Text, cleanedText) |
| if trimmed == "" { |
| return delta |
| } |
| a.Text.WriteString(trimmed) |
| delta.VisibleText = trimmed |
| return delta |
| } |
|
|