mirror of
https://github.com/usestrix/strix.git
synced 2026-08-21 02:45:31 +02:00
fix(context): reserve continuation-hint bytes within the retrieval page ceiling
A full non-final retrieval page consumed the entire byte budget and then appended the pagination hint, so the returned result (which bypasses the result-bounding wrapper) exceeded the ceiling. Reserve the hint's bytes out of the page budget so content plus hint always fits.
This commit is contained in:
@@ -33,6 +33,14 @@ _DEFAULT_STORE_DIR = Path.home() / ".strix" / "tool-output"
|
||||
# instead of returned whole.
|
||||
_PAGE_MAX_BYTES = 50 * 1024
|
||||
|
||||
# Appended to a non-final page so the agent can request the next one. Its bytes
|
||||
# are reserved out of the page budget so a full page plus this hint still fits
|
||||
# ``_PAGE_MAX_BYTES``.
|
||||
_CONTINUATION_HINT = (
|
||||
"\n\n[... more; call read_tool_output("
|
||||
'output_id="{output_id}", offset={offset}) to continue ...]'
|
||||
)
|
||||
|
||||
# Single-key holder so the configured path can be swapped per scan without a
|
||||
# module-level ``global`` rebind.
|
||||
_config: dict[str, Path] = {}
|
||||
@@ -243,12 +251,16 @@ def read_stored_output(output_id: str, *, offset: int = 0, limit: int = _PAGE_MA
|
||||
size = path.stat().st_size
|
||||
if start >= size:
|
||||
return ""
|
||||
# Floor at 4 bytes (the max UTF-8 char length) so a page always makes
|
||||
# progress past a single multi-byte character.
|
||||
budget = min(max(4, limit), _PAGE_MAX_BYTES)
|
||||
# Reserve the continuation hint's bytes so a full page plus its appended
|
||||
# metadata still fits the ceiling (retrieval bypasses the result wrapper).
|
||||
# next_offset can never exceed the file size, so formatting with ``size``
|
||||
# is an exact upper bound on the hint length. Floor content at 4 bytes (the
|
||||
# max UTF-8 char length) so a page always makes progress past one char.
|
||||
hint_reserve = len(_CONTINUATION_HINT.format(output_id=output_id, offset=size))
|
||||
content_budget = min(max(4, limit), _PAGE_MAX_BYTES - hint_reserve)
|
||||
with path.open("rb") as handle:
|
||||
handle.seek(start)
|
||||
chunk = handle.read(budget)
|
||||
chunk = handle.read(content_budget)
|
||||
|
||||
has_more = start + len(chunk) < size
|
||||
if has_more:
|
||||
@@ -262,8 +274,5 @@ def read_stored_output(output_id: str, *, offset: int = 0, limit: int = _PAGE_MA
|
||||
chunk = _trim_incomplete_utf8_head(chunk)
|
||||
shown = chunk.decode("utf-8", errors="replace")
|
||||
if has_more:
|
||||
shown += (
|
||||
"\n\n[... more; call read_tool_output("
|
||||
f'output_id="{output_id}", offset={next_offset}) to continue ...]'
|
||||
)
|
||||
shown += _CONTINUATION_HINT.format(output_id=output_id, offset=next_offset)
|
||||
return shown
|
||||
|
||||
@@ -6,6 +6,7 @@ import re
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from strix.tools.output_store import (
|
||||
_PAGE_MAX_BYTES,
|
||||
bound_and_store,
|
||||
bound_text,
|
||||
configure_output_store,
|
||||
@@ -125,6 +126,21 @@ def test_read_stored_output_paginates(tmp_path: Path) -> None:
|
||||
assert "offset=10" in page
|
||||
|
||||
|
||||
def test_read_stored_output_page_including_hint_stays_within_ceiling(tmp_path: Path) -> None:
|
||||
# A full non-final page plus its continuation hint must not exceed the page
|
||||
# ceiling — retrieval bypasses the general result-bounding wrapper.
|
||||
configure_output_store(tmp_path)
|
||||
text = "x" * (_PAGE_MAX_BYTES * 3)
|
||||
output_id = re.search(
|
||||
r'output_id="([0-9a-f]{32})"',
|
||||
bound_and_store(text, max_lines=4, max_bytes=1_000),
|
||||
)
|
||||
assert output_id is not None
|
||||
page = read_stored_output(output_id.group(1), offset=0, limit=_PAGE_MAX_BYTES)
|
||||
assert "more;" in page # a hint was appended (non-final page)
|
||||
assert len(page.encode("utf-8")) <= _PAGE_MAX_BYTES
|
||||
|
||||
|
||||
def test_read_stored_output_pages_long_lines_losslessly(tmp_path: Path) -> None:
|
||||
# A single line far larger than the page budget must be split across pages
|
||||
# (never returned whole), and paging forward must reconstruct the output
|
||||
|
||||
Reference in New Issue
Block a user