fix(context): clamp shell output cap and count byte-trimmed dropped lines

Treat tool_output_max_tokens as a ceiling so an explicit model-supplied
cap can't exceed it, and derive the truncation notice's dropped-line
count from the lines actually kept after the byte-trim pass. Also cast
the pygments fallback lexer so it satisfies the resolve_lexer return
type under the pre-commit mypy hook.
This commit is contained in:
Ahmed Allam
2026-07-25 22:32:56 +00:00
parent 3b8980c47b
commit dab93bcc12
5 changed files with 51 additions and 10 deletions
+11 -5
View File
@@ -241,12 +241,18 @@ def _format_validation_error(tool_name: str, exc: ValidationError) -> str:
def _apply_shell_output_cap(parsed: dict[str, Any]) -> None:
"""Default the SDK shell tools' own token cap so a single command can't
dump unbounded output into history. The SDK truncates head+tail when the
model omits the field; respect an explicit model-supplied value.
"""Bound the SDK shell tools' own token cap so a single command can't dump
unbounded output into history. The SDK truncates head+tail from this value.
The configured cap is a ceiling: a missing value defaults to it, and a
larger model-supplied value is clamped down to it. A smaller explicit value
is respected, so the model can still ask for less.
"""
if parsed.get("max_output_tokens") is None:
parsed["max_output_tokens"] = load_settings().context.tool_output_max_tokens
ceiling = load_settings().context.tool_output_max_tokens
requested = parsed.get("max_output_tokens")
parsed["max_output_tokens"] = (
ceiling if not isinstance(requested, int) or requested > ceiling else requested
)
def _wrap_exec_command(tool: FunctionTool) -> FunctionTool:
+3 -3
View File
@@ -10,7 +10,7 @@ import re
import tempfile
from datetime import UTC, datetime
from pathlib import Path
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, cast
from pygments.lexers import PythonLexer, get_lexer_by_name, guess_lexer
from pygments.lexers.special import TextLexer
@@ -74,10 +74,10 @@ def resolve_lexer(language: str | None, code: str) -> Lexer:
try:
lexer = guess_lexer(code)
except ClassNotFound:
return PythonLexer()
return cast("Lexer", PythonLexer())
# ``guess_lexer`` returns the plain-text lexer when it can't detect anything.
if isinstance(lexer, TextLexer):
return PythonLexer()
return cast("Lexer", PythonLexer())
return lexer
+5 -1
View File
@@ -66,7 +66,11 @@ def bound_text(text: str, *, max_lines: int, max_bytes: int) -> str:
if tail and _byte_len(tail) > half_bytes:
tail = _take_suffix(tail, half_bytes)
dropped_lines = max(0, len(lines) - head_lines - tail_lines)
# Count kept lines from the final slices: the byte pass above may have
# dropped whole lines from head/tail, so deriving this from the original
# head_lines/tail_lines would undercount what was actually removed.
kept_lines = len(head.split("\n")) + (len(tail.split("\n")) if tail else 0)
dropped_lines = max(0, len(lines) - kept_lines)
dropped_bytes = max(0, total_bytes - _byte_len(head) - _byte_len(tail))
notice = _TRUNCATION_NOTICE.format(lines=dropped_lines, bytes=dropped_bytes)
return f"{head}\n\n{notice}\n\n{tail}" if tail else f"{head}\n\n{notice}"