Co-authored-by: Clément Drouin <clement.drouin@mistral.ai>
Co-authored-by: Clément Sirieix <clement.sirieix@mistral.ai>
Co-authored-by: Guillaume LE GOFF <guillaume.lgf@gmail.com>
Co-authored-by: Mert Unsal <mertunsal1905@gmail.com>
Co-authored-by: Michel Thomazo <51709227+michelTho@users.noreply.github.com>
Co-authored-by: Quentin <quentin.torroba@mistral.ai>
Co-authored-by: Val <102326092+vdeva@users.noreply.github.com>
Co-authored-by: Vincent G <10739306+VinceOPS@users.noreply.github.com>
Co-authored-by: maximevoisin-pm <maxime.voisin@mistral.ai>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
Mathias Gesbert 2026-05-27 17:10:40 +02:00 committed by GitHub
parent adb1ca74ce
commit cf3f4ca58f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
143 changed files with 3457 additions and 1351 deletions

View file

@ -0,0 +1,112 @@
from __future__ import annotations
from vibe.core.compaction import collect_prior_user_messages
from vibe.core.types import LLMMessage, Role
_PREFIX = "Another language model started to solve this problem"
def _user(content: str, *, injected: bool = False) -> LLMMessage:
return LLMMessage(role=Role.user, content=content, injected=injected)
def test_empty_messages() -> None:
assert collect_prior_user_messages([], _PREFIX) == []
def test_only_non_user_messages() -> None:
messages = [
LLMMessage(role=Role.system, content="sys"),
LLMMessage(role=Role.assistant, content="hi"),
]
assert collect_prior_user_messages(messages, _PREFIX) == []
def test_single_user_message_preserved() -> None:
messages = [LLMMessage(role=Role.system, content="sys"), _user("first question")]
out = collect_prior_user_messages(messages, _PREFIX)
assert [m.content for m in out] == ["first question"]
def test_chronological_order_preserved() -> None:
messages = [_user("first"), _user("second"), _user("third")]
out = collect_prior_user_messages(messages, _PREFIX)
assert [m.content for m in out] == ["first", "second", "third"]
def test_injected_messages_filtered_out() -> None:
messages = [
_user("real ask"),
_user("middleware reminder", injected=True),
_user("follow-up"),
]
out = collect_prior_user_messages(messages, _PREFIX)
assert [m.content for m in out] == ["real ask", "follow-up"]
def test_empty_content_filtered_out() -> None:
messages = [_user(""), _user("real")]
out = collect_prior_user_messages(messages, _PREFIX)
assert [m.content for m in out] == ["real"]
def test_prior_summary_filtered_out() -> None:
# A user message starting with the summary prefix represents a previous
# compaction summary and must not be re-injected (would stack).
messages = [
_user("original ask"),
_user(f"{_PREFIX}\nold summary content"),
_user("newer ask"),
]
out = collect_prior_user_messages(messages, _PREFIX)
assert [m.content for m in out] == ["original ask", "newer ask"]
def test_budget_drops_oldest_first() -> None:
# max_tokens=2 → 8 char budget. Walks newest-first, so "old" gets dropped.
messages = [
_user("old message that is long enough to matter"),
_user("abc"), # 1 token, fits
_user("def"), # 1 token, fits
]
out = collect_prior_user_messages(messages, _PREFIX, max_tokens=2)
assert [m.content for m in out] == ["abc", "def"]
def test_spillover_message_middle_truncated() -> None:
# newest fits whole, middle one is partially trimmed, oldest dropped.
messages = [
_user("OLDEST" + "x" * 10_000 + "OLDEST_END"),
_user("MIDDLE_HEAD" + "y" * 1_000 + "MIDDLE_TAIL"),
_user("recent"), # ~2 tokens
]
out = collect_prior_user_messages(messages, _PREFIX, max_tokens=50)
assert len(out) == 2 # oldest dropped
assert out[-1].content == "recent"
middle = out[0].content
assert middle is not None
assert middle.startswith("MIDDLE_HEAD")
assert middle.endswith("MIDDLE_TAIL")
assert "[... truncated ...]" in middle
def test_fresh_message_ids() -> None:
# Returned messages must have new message_ids — they'll live in a fresh
# session and reusing the source ids would cause collisions.
original = _user("hello")
out = collect_prior_user_messages([original], _PREFIX)
assert len(out) == 1
assert out[0].message_id != original.message_id
def test_only_assistant_and_system_around_users() -> None:
messages = [
LLMMessage(role=Role.system, content="sys"),
_user("u1"),
LLMMessage(role=Role.assistant, content="a1"),
_user("u2"),
LLMMessage(role=Role.assistant, content="a2"),
]
out = collect_prior_user_messages(messages, _PREFIX)
assert [m.content for m in out] == ["u1", "u2"]
assert all(m.role == Role.user for m in out)

View file

@ -10,6 +10,7 @@ from vibe.core.config.layer import (
ConfigLayer,
LayerImplementationError,
RawConfig,
TrustNotResolvedError,
UntrustedLayerError,
)
@ -122,6 +123,7 @@ async def test_resolve_trust_no_callback_when_unchanged() -> None:
@pytest.mark.asyncio
async def test_grant_trust() -> None:
layer = StubLayer(trusted=False)
await layer.resolve_trust()
await layer.grant_trust()
assert layer.is_trusted is True
@ -129,8 +131,10 @@ async def test_grant_trust() -> None:
@pytest.mark.asyncio
async def test_grant_trust_fires_callback() -> None:
layer = ObservableStubLayer(trusted=False)
await layer.resolve_trust()
layer.trust_changes.clear()
await layer.grant_trust()
assert layer.trust_changes == [(None, True)]
assert layer.trust_changes == [(False, True)]
@pytest.mark.asyncio
@ -184,10 +188,17 @@ async def test_on_trust_changed_failure_preserves_state() -> None:
layer = FailingLayer(trusted=False)
assert layer.is_trusted is None
# Resolve trust without failing (so we can test grant_trust separately)
layer.should_fail = False
await layer.resolve_trust()
assert layer.is_trusted is False
# Now make _on_trust_changed fail during grant_trust
layer.should_fail = True
with pytest.raises(LayerImplementationError, match="_on_trust_changed") as exc_info:
await layer.grant_trust()
assert isinstance(exc_info.value.__cause__, RuntimeError)
assert layer.is_trusted is None
assert layer.is_trusted is False
layer.should_fail = False
await layer.grant_trust()
@ -251,6 +262,7 @@ async def test_load_untrusted_raises() -> None:
@pytest.mark.asyncio
async def test_load_after_grant_trust() -> None:
layer = StubLayer(trusted=False)
await layer.resolve_trust()
await layer.grant_trust()
result = await layer.load()
assert isinstance(result, RawConfig)
@ -392,6 +404,42 @@ async def test_apply_not_implemented() -> None:
await layer.apply({"op": "set"})
@pytest.mark.asyncio
async def test_grant_trust_raises_when_trust_not_resolved() -> None:
"""grant_trust() must fail if trust has never been resolved."""
layer = StubLayer(trusted=True, data={"k": "v"})
# Trust not yet resolved — grant_trust should raise
with pytest.raises(TrustNotResolvedError):
await layer.grant_trust()
assert layer.is_trusted is None
# Resolve trust first, then grant_trust succeeds
await layer.resolve_trust()
await layer.grant_trust()
assert layer.is_trusted is True
@pytest.mark.asyncio
async def test_revoke_trust_raises_when_trust_not_resolved() -> None:
"""revoke_trust() must fail if trust has never been resolved."""
trust_store: dict[str, bool] = {"/tmp/proj": True}
layer = FakeLocalProjectLayer(
project_path="/tmp/proj", data={"key": "val"}, trust_store=trust_store
)
# Trust not yet resolved — revoke_trust should raise
with pytest.raises(TrustNotResolvedError):
await layer.revoke_trust()
assert layer.is_trusted is None
# Resolve trust (storage says trusted), then revoke succeeds
await layer.resolve_trust()
assert layer.is_trusted is True
await layer.revoke_trust()
assert layer.is_trusted is False
# Scenario: LocalUserConfigLayer

View file

@ -0,0 +1,84 @@
from __future__ import annotations
import pytest
from vibe.core.config.layers.overrides import OverridesLayer
@pytest.mark.asyncio
async def test_returns_provided_dict() -> None:
data = {"active_model": "custom", "api_timeout": 30.0}
layer = OverridesLayer(data=data)
result = await layer.load()
assert result.model_extra == data
@pytest.mark.asyncio
async def test_always_trusted() -> None:
layer = OverridesLayer(data={})
assert await layer.resolve_trust() is True
@pytest.mark.asyncio
async def test_apply_raises_not_implemented() -> None:
layer = OverridesLayer(data={})
with pytest.raises(NotImplementedError, match="M2"):
await layer.apply({"op": "set"})
@pytest.mark.asyncio
async def test_default_name() -> None:
layer = OverridesLayer(data={})
assert layer.name == "overrides"
@pytest.mark.asyncio
async def test_custom_name() -> None:
layer = OverridesLayer(data={}, name="cli-overrides")
assert layer.name == "cli-overrides"
@pytest.mark.asyncio
async def test_empty_dict() -> None:
layer = OverridesLayer(data={})
result = await layer.load()
assert result.model_extra == {}
@pytest.mark.asyncio
async def test_nested_data_preserved() -> None:
data = {"models": {"active_model": "test"}, "tools": {"enabled_tools": ["a"]}}
layer = OverridesLayer(data=data)
result = await layer.load()
assert result.model_extra == data
@pytest.mark.asyncio
async def test_force_reload_returns_same_data() -> None:
data = {"key": "value"}
layer = OverridesLayer(data=data)
await layer.load()
result = await layer.load(force=True)
assert result.model_extra == data
@pytest.mark.asyncio
async def test_output_isolated_from_internal_data() -> None:
data = {"key": "value"}
layer = OverridesLayer(data=data)
result = await layer.load()
# Mutating the returned model_extra must not affect subsequent loads
assert result.model_extra is not None
result.model_extra["key"] = "mutated"
result2 = await layer.load(force=True)
assert result2.model_extra == {"key": "value"}
@pytest.mark.asyncio
async def test_live_reference_picks_up_caller_mutation() -> None:
data: dict[str, object] = {"key": "original"}
layer = OverridesLayer(data=data)
await layer.load()
data["key"] = "updated"
result = await layer.load(force=True)
assert result.model_extra == {"key": "updated"}

View file

@ -0,0 +1,281 @@
from __future__ import annotations
from pathlib import Path
import pytest
from vibe.core.config.layer import UntrustedLayerError
from vibe.core.config.layers.project import ProjectConfigLayer
from vibe.core.paths._vibe_home import GlobalPath
from vibe.core.trusted_folders import trusted_folders_manager
@pytest.mark.asyncio
async def test_reads_toml_when_trusted(tmp_working_directory: Path) -> None:
trusted_folders_manager.add_trusted(tmp_working_directory)
config_path = tmp_working_directory / ".vibe" / "config.toml"
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text('active_model = "project-model"\n')
layer = ProjectConfigLayer(path=tmp_working_directory)
data = await layer.load()
assert data.model_extra == {"active_model": "project-model"}
@pytest.mark.asyncio
async def test_untrusted_raises(tmp_working_directory: Path) -> None:
trusted_folders_manager.add_untrusted(tmp_working_directory)
config_path = tmp_working_directory / ".vibe" / "config.toml"
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text('active_model = "project-model"\n')
layer = ProjectConfigLayer(path=tmp_working_directory)
with pytest.raises(UntrustedLayerError):
await layer.load()
@pytest.mark.asyncio
async def test_unresolved_trust_defaults_to_untrusted(
tmp_working_directory: Path,
) -> None:
config_path = tmp_working_directory / ".vibe" / "config.toml"
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text('key = "value"\n')
layer = ProjectConfigLayer(path=tmp_working_directory)
with pytest.raises(UntrustedLayerError):
await layer.load()
@pytest.mark.asyncio
async def test_missing_file_returns_empty(tmp_working_directory: Path) -> None:
trusted_folders_manager.add_trusted(tmp_working_directory)
layer = ProjectConfigLayer(path=tmp_working_directory)
data = await layer.load()
assert data.model_extra == {}
@pytest.mark.asyncio
async def test_default_name(tmp_working_directory: Path) -> None:
layer = ProjectConfigLayer(path=tmp_working_directory)
assert layer.name == "project-toml"
@pytest.mark.asyncio
async def test_apply_raises_not_implemented(tmp_working_directory: Path) -> None:
layer = ProjectConfigLayer(path=tmp_working_directory)
with pytest.raises(NotImplementedError, match="M2"):
await layer.apply({"op": "set"})
@pytest.mark.asyncio
async def test_trust_uses_path_parent_for_resolution(
tmp_working_directory: Path,
) -> None:
project_dir = tmp_working_directory / "my-project"
project_dir.mkdir()
config_path = project_dir / ".vibe" / "config.toml"
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text('value = "trusted"\n')
trusted_folders_manager.add_trusted(project_dir / ".vibe")
layer = ProjectConfigLayer(path=project_dir)
data = await layer.load()
assert data.model_extra == {"value": "trusted"}
@pytest.mark.asyncio
async def test_grant_trust_marks_folder_trusted(tmp_working_directory: Path) -> None:
config_path = tmp_working_directory / ".vibe" / "config.toml"
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text('key = "value"\n')
layer = ProjectConfigLayer(path=tmp_working_directory)
trusted_folders_manager.add_untrusted(tmp_working_directory / ".vibe")
await layer.resolve_trust()
await layer.grant_trust()
assert trusted_folders_manager.is_trusted(tmp_working_directory / ".vibe") is True
@pytest.mark.asyncio
async def test_revoke_trust_marks_folder_untrusted(tmp_working_directory: Path) -> None:
config_path = tmp_working_directory / ".vibe" / "config.toml"
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text('key = "value"\n')
layer = ProjectConfigLayer(path=tmp_working_directory)
trusted_folders_manager.add_trusted(tmp_working_directory / ".vibe")
await layer.resolve_trust()
await layer.revoke_trust()
assert trusted_folders_manager.is_trusted(tmp_working_directory / ".vibe") is False
@pytest.mark.asyncio
async def test_grant_trust_without_config_file_is_noop(
tmp_working_directory: Path,
) -> None:
layer = ProjectConfigLayer(path=tmp_working_directory)
trusted_folders_manager.add_untrusted(tmp_working_directory)
await layer.grant_trust()
assert trusted_folders_manager.is_trusted(tmp_working_directory) is False
@pytest.mark.asyncio
async def test_revoke_trust_without_config_file_is_noop(
tmp_working_directory: Path,
) -> None:
layer = ProjectConfigLayer(path=tmp_working_directory)
trusted_folders_manager.add_trusted(tmp_working_directory)
await layer.revoke_trust()
assert trusted_folders_manager.is_trusted(tmp_working_directory) is True
@pytest.mark.asyncio
async def test_trust_stored_at_vibe_subdir(tmp_working_directory: Path) -> None:
config_path = tmp_working_directory / ".vibe" / "config.toml"
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text('key = "value"\n')
layer = ProjectConfigLayer(path=tmp_working_directory)
await layer.resolve_trust()
await layer.grant_trust()
assert (
str((tmp_working_directory / ".vibe").resolve())
in trusted_folders_manager._trusted
)
assert str(tmp_working_directory.resolve()) not in trusted_folders_manager._trusted
@pytest.mark.asyncio
async def test_find_file_prefers_closest_config(tmp_working_directory: Path) -> None:
parent_config = tmp_working_directory / ".vibe" / "config.toml"
parent_config.parent.mkdir(parents=True, exist_ok=True)
parent_config.write_text('level = "parent"\n')
child = tmp_working_directory / "child"
child.mkdir()
child_config = child / ".vibe" / "config.toml"
child_config.parent.mkdir(parents=True, exist_ok=True)
child_config.write_text('level = "child"\n')
trusted_folders_manager.add_trusted(child)
layer = ProjectConfigLayer(path=child)
data = await layer.load()
assert data.model_extra == {"level": "child"}
@pytest.mark.asyncio
async def test_find_file_result_is_cached(tmp_working_directory: Path) -> None:
config_path = tmp_working_directory / ".vibe" / "config.toml"
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text("x = 1\n")
trusted_folders_manager.add_trusted(tmp_working_directory)
layer = ProjectConfigLayer(path=tmp_working_directory)
await layer.load()
assert layer._is_set is True
cached_path = layer._config_file_path
await layer.resolve_trust()
assert layer._config_file_path is cached_path
@pytest.mark.asyncio
async def test_config_file_added_after_first_search_is_not_detected(
tmp_working_directory: Path,
) -> None:
layer = ProjectConfigLayer(path=tmp_working_directory)
await layer._find_config_file()
assert layer._is_set is True
assert layer._config_file_path is None
config_path = tmp_working_directory / ".vibe" / "config.toml"
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text('active_model = "new-model"\n')
await layer._find_config_file()
assert layer._config_file_path is None
@pytest.mark.asyncio
async def test_finds_config_in_parent_directory(tmp_working_directory: Path) -> None:
trusted_folders_manager.add_trusted(tmp_working_directory)
config_path = tmp_working_directory / ".vibe" / "config.toml"
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text('active_model = "parent-model"\n')
subdir = tmp_working_directory / "sub" / "project"
subdir.mkdir(parents=True)
layer = ProjectConfigLayer(path=subdir)
data = await layer.load()
assert data.model_extra == {"active_model": "parent-model"}
@pytest.mark.asyncio
async def test_trusted_ancestor_satisfies_trust_check(
tmp_working_directory: Path,
) -> None:
parent = tmp_working_directory / "workspace"
parent.mkdir()
child = parent / "project"
child.mkdir()
trusted_folders_manager.add_trusted(parent)
config_path = child / ".vibe" / "config.toml"
config_path.parent.mkdir(parents=True)
config_path.write_text('active_model = "child-model"\n')
layer = ProjectConfigLayer(path=child)
data = await layer.load()
assert data.model_extra == {"active_model": "child-model"}
@pytest.mark.asyncio
async def test_grant_trust_then_load_reads_config_without_prior_resolve(
tmp_working_directory: Path,
) -> None:
config_path = tmp_working_directory / ".vibe" / "config.toml"
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text('key = "value"\n')
layer = ProjectConfigLayer(path=tmp_working_directory)
await layer.resolve_trust()
await layer.grant_trust()
data = await layer.load()
assert data.model_extra == {"key": "value"}
@pytest.mark.asyncio
async def test_walk_stops_at_vibe_home_parent(
tmp_path_factory: pytest.TempPathFactory, monkeypatch: pytest.MonkeyPatch
) -> None:
# Create an isolated tmp root and point VIBE_HOME into it
tmp_root = tmp_path_factory.mktemp("walk_stop")
fake_vibe_home = tmp_root / ".vibe"
fake_vibe_home.mkdir()
monkeypatch.setattr(
"vibe.core.config.layers.project.VIBE_HOME", GlobalPath(lambda: fake_vibe_home)
)
# Place a config at tmp_root/.vibe/config.toml — should NOT be picked up
home_config = fake_vibe_home / "config.toml"
home_config.write_text('active_model = "home-model"\n')
# subdir lives inside tmp_root so the walk would reach the config without the stop guard
subdir = tmp_root / "vibe-test-project"
subdir.mkdir()
trusted_folders_manager.add_trusted(subdir)
layer = ProjectConfigLayer(path=subdir)
data = await layer.load()
assert data.model_extra == {}

View file

@ -9,6 +9,7 @@ from vibe.core.teleport.errors import ServiceTeleportError
from vibe.core.teleport.experimental_nuage import (
ExperimentalNuageClient,
ExperimentalNuageContext,
ExperimentalNuageDiff,
ExperimentalNuageMessage,
ExperimentalNuageRepository,
ExperimentalNuageRequest,
@ -125,6 +126,63 @@ async def test_start_omits_empty_branch() -> None:
}
@pytest.mark.asyncio
async def test_start_serializes_commit_sha_and_diff() -> None:
seen_body: dict[str, object] | None = None
async def handler(request: httpx.Request) -> httpx.Response:
nonlocal seen_body
seen_body = json.loads(request.content)
return httpx.Response(
200,
json={
"sessionId": "s",
"webSessionId": "ws",
"projectId": "p",
"status": "running",
"url": "https://chat.example.com/code/p/ws",
},
)
request = ExperimentalNuageRequest(
idempotency_key="idem-1",
message=ExperimentalNuageMessage(
parts=[ExperimentalNuageTextPart(text="prompt")]
),
context=ExperimentalNuageContext(
repositories=[
ExperimentalNuageRepository(
repo_url="https://github.com/owner/repo",
branch="main",
commit_sha="abc123",
diff=ExperimentalNuageDiff(
format="git-diff",
encoding="base64",
compression="zstd",
content="ZGlmZnM=",
),
)
]
),
)
async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client:
nuage = ExperimentalNuageClient(
"https://chat.example.com", "api-key", client=client
)
await nuage.start(request)
assert seen_body is not None
repos = seen_body["context"]["repositories"]
assert len(repos) == 1
assert repos[0]["commitSha"] == "abc123"
assert repos[0]["diff"] == {
"format": "git-diff",
"encoding": "base64",
"compression": "zstd",
"content": "ZGlmZnM=",
}
@pytest.mark.asyncio
async def test_start_raises_for_unsuccessful_response() -> None:
async def handler(request: httpx.Request) -> httpx.Response:

View file

@ -499,7 +499,7 @@ class TestTeleportServiceExecute:
repo="repo",
branch="main",
commit="abc123",
diff="local diff is intentionally not sent",
diff="some local diff",
)
)
service._git.is_commit_pushed = AsyncMock(return_value=True)
@ -523,11 +523,15 @@ class TestTeleportServiceExecute:
"role": "user",
"parts": [{"type": "text", "text": "test prompt"}],
}
assert seen_body["context"] == {
"repositories": [
{"repoUrl": "https://github.com/owner/repo", "branch": "main"}
]
}
repos = seen_body["context"]["repositories"]
assert len(repos) == 1
assert repos[0]["repoUrl"] == "https://github.com/owner/repo"
assert repos[0]["branch"] == "main"
assert repos[0]["commitSha"] == "abc123"
assert repos[0]["diff"]["format"] == "git-diff"
assert repos[0]["diff"]["encoding"] == "base64"
assert repos[0]["diff"]["compression"] == "zstd"
assert len(repos[0]["diff"]["content"]) > 0
assert "idempotencyKey" in seen_body
@pytest.mark.asyncio

50
tests/core/test_tokens.py Normal file
View file

@ -0,0 +1,50 @@
from __future__ import annotations
from vibe.core.utils.tokens import approx_token_count, truncate_middle_to_tokens
_MARKER = "\n\n[... truncated ...]\n\n"
def test_approx_token_count_empty() -> None:
assert approx_token_count("") == 0
def test_approx_token_count_rounds_up() -> None:
# 1..4 chars all round to 1 token; 5 rolls over to 2.
assert approx_token_count("a") == 1
assert approx_token_count("a" * 4) == 1
assert approx_token_count("a" * 5) == 2
def test_approx_token_count_budget_boundary() -> None:
# 80_000 chars == 20_000 tokens exactly; +1 char crosses the boundary.
assert approx_token_count("a" * 80_000) == 20_000
assert approx_token_count("a" * 80_001) == 20_001
def test_truncate_middle_returns_text_when_under_budget() -> None:
assert truncate_middle_to_tokens("hello", 10) == "hello"
def test_truncate_middle_empty_inputs() -> None:
assert truncate_middle_to_tokens("", 10) == ""
assert truncate_middle_to_tokens("anything", 0) == ""
assert truncate_middle_to_tokens("anything", -1) == ""
def test_truncate_middle_keeps_head_and_tail() -> None:
text = "HEAD" + ("x" * 1000) + "TAIL"
out = truncate_middle_to_tokens(text, max_tokens=20) # 80 char budget
assert _MARKER in out
assert out.startswith("HEAD")
assert out.endswith("TAIL")
assert len(out) <= 80
def test_truncate_middle_tight_budget_falls_back_to_head_cut() -> None:
# Budget too tight to fit the marker (24 chars). Function head-truncates.
text = "abcdefghijklmnopqrstuvwxyz"
out = truncate_middle_to_tokens(text, max_tokens=2) # 8 char budget
assert _MARKER not in out
assert out == "abcdefgh"
assert len(out) == 8