... sections. * * Key properties: * - Handles token fragmentation (partial tags across tokens) * - Stateful per stream, stateless per request * - Does not buffer full responses * - Deterministic and predictable */ final class ThinkSuppressor { /** Indicates whether the stream is currently inside a block. */ private bool $insideThink = false; /** Indicates whether the think section has been fully closed. */ private bool $thinkSectionCompleted = false; /** * Rolling buffer for detecting fragmented tags across tokens. */ private string $rollingBuffer = ''; /** * Maximum buffer length needed to safely detect tags. */ private int $maxBufferLength = 32; /** * Filters a single token from the LLM stream. * * @param string $token Raw token from the LLM * @return string Cleaned token safe for user output */ public function filter(string $token): string { // Append to rolling buffer $this->rollingBuffer .= $token; if (strlen($this->rollingBuffer) > $this->maxBufferLength) { $this->rollingBuffer = substr($this->rollingBuffer, -$this->maxBufferLength); } // If think section is already completed, just strip stray closing tags if ($this->thinkSectionCompleted) { return str_replace('', '', $token); } // Detect fragmented opening tag if (!$this->insideThink && str_contains($this->rollingBuffer, '')) { $this->insideThink = true; return ''; } // Detect fragmented closing tag if ($this->insideThink && str_contains($this->rollingBuffer, '')) { $this->insideThink = false; $this->thinkSectionCompleted = true; // Emit a single line break after think section ends return "\n"; } // Suppress all content while inside ... if ($this->insideThink) { return ''; } return $token; } /** * Resets the suppressor state. * Must be called before starting a new stream. */ public function reset(): void { $this->insideThink = false; $this->thinkSectionCompleted = false; $this->rollingBuffer = ''; } }