"""Output-only reasoning filtering; never treats tool-call markup as thinking."""
from __future__ import annotations
import math
def strict_bool(config: dict, name: str, default: bool = False) -> bool:
value = config.get(name, default)
if type(value) is not bool:
raise ValueError(f"{name} must be a boolean")
return value
def positive_timeout(config: dict) -> float:
value = config.get("timeout", 120)
try:
timeout = float(value)
except (TypeError, ValueError, OverflowError):
raise ValueError("timeout must be a finite positive number") from None
if isinstance(value, bool) or not math.isfinite(timeout) or timeout <= 0:
raise ValueError("timeout must be a finite positive number")
return timeout
class ResponseBudget:
"""Match the native 1 MiB generated-text limit, even for hidden output."""
def __init__(self, error_type, code: str):
self.size = 0
self.error_type = error_type
self.code = code
def add(self, *values: str) -> None:
self.size += sum(len(value or "") for value in values)
if self.size > 1024 * 1024:
raise self.error_type("Response exceeds the runtime limit", code=self.code)
def check_rendered(self, value: str) -> None:
"""Check a snapshot without charging repeated streaming snapshots."""
if len(value) > 1024 * 1024:
raise self.error_type("Response exceeds the runtime limit", code=self.code)
class ThinkingFilter:
"""Filter delta text while retaining incomplete delimiters between chunks.
Only declared reasoning delimiters are recognized. Ordinary text (including
tool-call markup and whitespace) is not normalized. Unterminated reasoning
stays hidden; an incomplete opening delimiter outside reasoning is flushed
as literal text at EOF. Disabled mode is an exact pass-through.
"""
def __init__(self, enabled: bool, extra_pairs: tuple = ()):
self.enabled = enabled
self.pairs = {"": "", "": "", **dict(extra_pairs)}
self.pending = ""
self.closers: list[str] = []
def feed(self, text: str, *, final: bool = False) -> str:
if not self.enabled:
return text
self.pending += text
result = []
while self.pending:
tokens = list(self.pairs)
if self.closers:
tokens.append(self.closers[-1])
found = [(self.pending.find(token), token) for token in tokens if token in self.pending]
if found:
position, token = min(found)
if not self.closers:
result.append(self.pending[:position])
self.pending = self.pending[position + len(token) :]
if self.closers and token == self.closers[-1]:
self.closers.pop()
else:
self.closers.append(self.pairs[token])
continue
keep = 0
if not final:
for token in tokens:
for size in range(1, min(len(token), len(self.pending) + 1)):
if self.pending.endswith(token[:size]):
keep = max(keep, size)
split = len(self.pending) - keep
if not self.closers:
result.append(self.pending[:split])
self.pending = self.pending[split:]
break
return "".join(result)