v2.9.4 (#669)
Co-authored-by: Clément Drouin <clement.drouin@mistral.ai> Co-authored-by: Jean-Baptiste Muscat <jeanbaptiste.muscatdupuis@mistral.ai> Co-authored-by: JeroenvdV <JeroenvdV@users.noreply.github.com> Co-authored-by: Kim-Adeline Miguel <51720070+kimadeline@users.noreply.github.com> Co-authored-by: Mathias Gesbert <mathias.gesbert@mistral.ai> Co-authored-by: Paul Cacheux <paul.cacheux@mistral.ai> Co-authored-by: Pierre Rossinès <pierre.rossines@mistral.ai> Co-authored-by: Simon Van de Kerckhove <simon.vandekerckhove@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: allansimon-mistral <allan.simon@ext.mistral.ai> Co-authored-by: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
parent
71f373c60c
commit
4972dd5694
50 changed files with 2892 additions and 842 deletions
178
tests/session/test_saved_sessions.py
Normal file
178
tests/session/test_saved_sessions.py
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from vibe.core.config import SessionLoggingConfig
|
||||
from vibe.core.session.saved_sessions import update_saved_session_title
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def temp_session_dir(tmp_path: Path) -> Path:
|
||||
session_dir = tmp_path / "sessions"
|
||||
session_dir.mkdir()
|
||||
return session_dir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def session_config(temp_session_dir: Path) -> SessionLoggingConfig:
|
||||
return SessionLoggingConfig(
|
||||
save_dir=str(temp_session_dir), session_prefix="test", enabled=True
|
||||
)
|
||||
|
||||
|
||||
class TestUpdateSavedSessionTitle:
|
||||
@pytest.mark.asyncio
|
||||
async def test_updates_saved_session_title_without_losing_existing_metadata(
|
||||
self, session_config: SessionLoggingConfig
|
||||
) -> None:
|
||||
session_dir = Path(session_config.save_dir)
|
||||
saved_session_dir = session_dir / "test_20240101_120000_aaaaaaaa"
|
||||
saved_session_dir.mkdir()
|
||||
|
||||
(saved_session_dir / "messages.jsonl").write_text(
|
||||
'{"role": "user", "content": "Hello"}\n', encoding="utf-8"
|
||||
)
|
||||
|
||||
original_metadata = {
|
||||
"session_id": "aaaaaaaa-1111",
|
||||
"start_time": "2024-01-01T12:00:00Z",
|
||||
"end_time": "2024-01-01T12:05:00Z",
|
||||
"git_commit": None,
|
||||
"git_branch": None,
|
||||
"username": "test-user",
|
||||
"environment": {"working_directory": "/home/user/project"},
|
||||
"title": "Old title",
|
||||
"stats": {"steps": 2},
|
||||
"total_messages": 1,
|
||||
"tools_available": [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {"name": "bash", "description": "Run shell commands"},
|
||||
}
|
||||
],
|
||||
"config": {"active_model": "test-model"},
|
||||
"system_prompt": {"role": "system", "content": "You are helpful"},
|
||||
}
|
||||
metadata_file = saved_session_dir / "meta.json"
|
||||
metadata_file.write_text(json.dumps(original_metadata), encoding="utf-8")
|
||||
|
||||
updated_metadata = await update_saved_session_title(
|
||||
"aaaaaaaa-1111", "Renamed session", session_config
|
||||
)
|
||||
|
||||
assert updated_metadata == {
|
||||
**original_metadata,
|
||||
"title": "Renamed session",
|
||||
"title_source": "manual",
|
||||
}
|
||||
assert json.loads(metadata_file.read_text(encoding="utf-8")) == updated_metadata
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_rejects_empty_title(
|
||||
self, session_config: SessionLoggingConfig
|
||||
) -> None:
|
||||
session_dir = Path(session_config.save_dir)
|
||||
saved_session_dir = session_dir / "test_20240101_120000_bbbbbbbb"
|
||||
saved_session_dir.mkdir()
|
||||
|
||||
(saved_session_dir / "messages.jsonl").write_text(
|
||||
'{"role": "user", "content": "Hello"}\n', encoding="utf-8"
|
||||
)
|
||||
|
||||
original_metadata = {
|
||||
"session_id": "bbbbbbbb-2222",
|
||||
"start_time": "2024-01-01T12:00:00Z",
|
||||
"end_time": "2024-01-01T12:05:00Z",
|
||||
"git_commit": None,
|
||||
"git_branch": None,
|
||||
"username": "test-user",
|
||||
"environment": {"working_directory": "/home/user/project"},
|
||||
"title": "Manual title",
|
||||
"title_source": "manual",
|
||||
"stats": {"steps": 2},
|
||||
}
|
||||
metadata_file = saved_session_dir / "meta.json"
|
||||
metadata_file.write_text(json.dumps(original_metadata), encoding="utf-8")
|
||||
|
||||
with pytest.raises(ValueError, match="Session title cannot be empty."):
|
||||
await update_saved_session_title("bbbbbbbb-2222", " ", session_config)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_preserves_saved_session_end_time_when_updating_title(
|
||||
self, session_config: SessionLoggingConfig
|
||||
) -> None:
|
||||
session_dir = Path(session_config.save_dir)
|
||||
saved_session_dir = session_dir / "test_20240101_120000_cccccccc"
|
||||
saved_session_dir.mkdir()
|
||||
|
||||
(saved_session_dir / "messages.jsonl").write_text(
|
||||
'{"role": "user", "content": "Hello"}\n', encoding="utf-8"
|
||||
)
|
||||
|
||||
original_metadata = {
|
||||
"session_id": "cccccccc-3333",
|
||||
"start_time": "2024-01-01T12:00:00Z",
|
||||
"end_time": "2024-01-01T12:05:00Z",
|
||||
"git_commit": None,
|
||||
"git_branch": None,
|
||||
"username": "test-user",
|
||||
"environment": {"working_directory": "/home/user/project"},
|
||||
"title": "Old title",
|
||||
}
|
||||
metadata_file = saved_session_dir / "meta.json"
|
||||
metadata_file.write_text(json.dumps(original_metadata), encoding="utf-8")
|
||||
|
||||
updated_metadata = await update_saved_session_title(
|
||||
"cccccccc-3333", "Renamed session", session_config
|
||||
)
|
||||
|
||||
assert updated_metadata == {
|
||||
**original_metadata,
|
||||
"title": "Renamed session",
|
||||
"title_source": "manual",
|
||||
}
|
||||
assert json.loads(metadata_file.read_text(encoding="utf-8")) == updated_metadata
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_raises_for_missing_saved_session(
|
||||
self, session_config: SessionLoggingConfig
|
||||
) -> None:
|
||||
with pytest.raises(ValueError, match="Session not found: missing-session"):
|
||||
await update_saved_session_title(
|
||||
"missing-session", "Renamed", session_config
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_requires_exact_saved_session_id(
|
||||
self, session_config: SessionLoggingConfig
|
||||
) -> None:
|
||||
session_dir = Path(session_config.save_dir)
|
||||
saved_session_dir = session_dir / "test_20240101_120000_dddddddd"
|
||||
saved_session_dir.mkdir()
|
||||
|
||||
(saved_session_dir / "messages.jsonl").write_text(
|
||||
'{"role": "user", "content": "Hello"}\n', encoding="utf-8"
|
||||
)
|
||||
|
||||
original_metadata = {
|
||||
"session_id": "dddddddd-4444",
|
||||
"start_time": "2024-01-01T12:00:00Z",
|
||||
"end_time": "2024-01-01T12:05:00Z",
|
||||
"git_commit": None,
|
||||
"git_branch": None,
|
||||
"username": "test-user",
|
||||
"environment": {"working_directory": "/home/user/project"},
|
||||
"title": "Old title",
|
||||
}
|
||||
metadata_file = saved_session_dir / "meta.json"
|
||||
metadata_file.write_text(json.dumps(original_metadata), encoding="utf-8")
|
||||
|
||||
with pytest.raises(ValueError, match="Session not found: dddddddd"):
|
||||
await update_saved_session_title("dddddddd", "Renamed", session_config)
|
||||
|
||||
assert (
|
||||
json.loads(metadata_file.read_text(encoding="utf-8")) == original_metadata
|
||||
)
|
||||
|
|
@ -1049,6 +1049,36 @@ class TestSessionLoaderGetFirstUserMessage:
|
|||
|
||||
|
||||
class TestSessionLoaderUTF8Encoding:
|
||||
def test_load_metadata_defaults_title_source_for_existing_sessions(
|
||||
self, session_config: SessionLoggingConfig
|
||||
) -> None:
|
||||
session_dir = Path(session_config.save_dir)
|
||||
session_folder = session_dir / "test_20230101_120000_legacyttl"
|
||||
session_folder.mkdir()
|
||||
|
||||
metadata_content = {
|
||||
"session_id": "legacy-title-test",
|
||||
"start_time": "2023-01-01T12:00:00Z",
|
||||
"end_time": "2023-01-01T12:05:00Z",
|
||||
"environment": {"working_directory": "/home/user/project"},
|
||||
"username": "testuser",
|
||||
"git_commit": None,
|
||||
"git_branch": None,
|
||||
"title": "Existing title",
|
||||
}
|
||||
|
||||
metadata_file = session_folder / "meta.json"
|
||||
with metadata_file.open("w", encoding="utf-8") as f:
|
||||
json.dump(metadata_content, f, indent=2, ensure_ascii=False)
|
||||
|
||||
messages_file = session_folder / "messages.jsonl"
|
||||
messages_file.write_text('{"role": "user", "content": "Hello"}\n')
|
||||
|
||||
metadata = SessionLoader.load_metadata(session_folder)
|
||||
|
||||
assert metadata.title == "Existing title"
|
||||
assert metadata.title_source == "auto"
|
||||
|
||||
def test_load_metadata_with_utf8_encoding(
|
||||
self, session_config: SessionLoggingConfig, create_test_session
|
||||
) -> None:
|
||||
|
|
|
|||
|
|
@ -139,6 +139,8 @@ class TestSessionLoggerMetadata:
|
|||
assert metadata.username == "testuser"
|
||||
assert "working_directory" in metadata.environment
|
||||
assert metadata.environment["working_directory"] == str(Path.cwd())
|
||||
assert metadata.title is None
|
||||
assert metadata.title_source == "auto"
|
||||
|
||||
@patch("vibe.core.session.session_logger.subprocess.run")
|
||||
@patch("vibe.core.session.session_logger.getpass.getuser")
|
||||
|
|
@ -161,6 +163,50 @@ class TestSessionLoggerMetadata:
|
|||
assert metadata.username == "testuser"
|
||||
|
||||
|
||||
class TestSessionLoggerTitleManagement:
|
||||
def test_set_title_marks_live_session_title_as_manual(
|
||||
self, session_config: SessionLoggingConfig
|
||||
) -> None:
|
||||
logger = SessionLogger(session_config, "test-session-123")
|
||||
|
||||
logger.set_title("Manual title")
|
||||
|
||||
assert logger.session_metadata is not None
|
||||
assert logger.session_metadata.title == "Manual title"
|
||||
assert logger.session_metadata.title_source == "manual"
|
||||
|
||||
def test_set_title_none_returns_live_session_to_auto_mode(
|
||||
self, session_config: SessionLoggingConfig
|
||||
) -> None:
|
||||
logger = SessionLogger(session_config, "test-session-123")
|
||||
logger.set_title("Manual title")
|
||||
|
||||
logger.set_title(None)
|
||||
|
||||
assert logger.session_metadata is not None
|
||||
assert logger.session_metadata.title is None
|
||||
assert logger.session_metadata.title_source == "auto"
|
||||
|
||||
def test_set_title_rejects_empty_title(
|
||||
self, session_config: SessionLoggingConfig
|
||||
) -> None:
|
||||
logger = SessionLogger(session_config, "test-session-123")
|
||||
|
||||
with pytest.raises(ValueError, match="Session title cannot be empty."):
|
||||
logger.set_title(" ")
|
||||
|
||||
def test_set_title_preserves_live_session_end_time(
|
||||
self, session_config: SessionLoggingConfig
|
||||
) -> None:
|
||||
logger = SessionLogger(session_config, "test-session-123")
|
||||
assert logger.session_metadata is not None
|
||||
logger.session_metadata.end_time = "2026-01-01T10:00:00+00:00"
|
||||
|
||||
logger.set_title("Manual title")
|
||||
|
||||
assert logger.session_metadata.end_time == "2026-01-01T10:00:00+00:00"
|
||||
|
||||
|
||||
class TestSessionLoggerSaveInteraction:
|
||||
@pytest.mark.asyncio
|
||||
async def test_save_interaction_disabled(
|
||||
|
|
@ -234,6 +280,7 @@ class TestSessionLoggerSaveInteraction:
|
|||
assert metadata["stats"]["steps"] == stats.steps
|
||||
assert "title" in metadata
|
||||
assert metadata["title"] == "Hello"
|
||||
assert metadata["title_source"] == "auto"
|
||||
assert "system_prompt" in metadata
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -447,6 +494,7 @@ class TestSessionLoggerSaveInteraction:
|
|||
assert metadata["total_messages"] == 1
|
||||
assert metadata["stats"]["steps"] == stats.steps
|
||||
assert metadata["title"] == "Untitled session"
|
||||
assert metadata["title_source"] == "auto"
|
||||
|
||||
messages_file = logger.session_dir / "messages.jsonl"
|
||||
assert messages_file.exists()
|
||||
|
|
@ -496,6 +544,46 @@ class TestSessionLoggerSaveInteraction:
|
|||
assert metadata["stats"]["steps"] == stats.steps
|
||||
expected_title = long_message[:50] + "…"
|
||||
assert metadata["title"] == expected_title
|
||||
assert metadata["title_source"] == "auto"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_save_interaction_preserves_manual_title(
|
||||
self,
|
||||
session_config: SessionLoggingConfig,
|
||||
mock_vibe_config: VibeConfig,
|
||||
mock_tool_manager: ToolManager,
|
||||
mock_agent_profile: AgentProfile,
|
||||
) -> None:
|
||||
session_id = "test-session-123"
|
||||
logger = SessionLogger(session_config, session_id)
|
||||
assert logger.session_metadata is not None
|
||||
|
||||
logger.set_title("Manual title")
|
||||
|
||||
messages = [
|
||||
LLMMessage(role=Role.system, content="System prompt"),
|
||||
LLMMessage(role=Role.user, content="Hello"),
|
||||
LLMMessage(role=Role.assistant, content="Hi there!"),
|
||||
]
|
||||
stats = AgentStats(
|
||||
steps=1, session_prompt_tokens=10, session_completion_tokens=20
|
||||
)
|
||||
|
||||
await logger.save_interaction(
|
||||
messages=messages,
|
||||
stats=stats,
|
||||
base_config=mock_vibe_config,
|
||||
tool_manager=mock_tool_manager,
|
||||
agent_profile=mock_agent_profile,
|
||||
)
|
||||
|
||||
assert logger.session_dir is not None
|
||||
metadata_file = logger.session_dir / "meta.json"
|
||||
with open(metadata_file) as f:
|
||||
metadata = json.load(f)
|
||||
|
||||
assert metadata["title"] == "Manual title"
|
||||
assert metadata["title_source"] == "manual"
|
||||
|
||||
messages_file = logger.session_dir / "messages.jsonl"
|
||||
assert messages_file.exists()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue