v2.9.0 (#641)
Co-authored-by: Antoine <33425718+anth2o@users.noreply.github.com> Co-authored-by: Bastien <bastien.baret@gmail.com> Co-authored-by: Clément Sirieix <clement.sirieix@mistral.ai> Co-authored-by: Kim-Adeline Miguel <51720070+kimadeline@users.noreply.github.com> Co-authored-by: Mathias Gesbert <mathias.gesbert@mistral.ai> Co-authored-by: Maxime Dolores <maxime.dolores@ext.mistral.ai> Co-authored-by: Michel Thomazo <51709227+michelTho@users.noreply.github.com> Co-authored-by: Nelson PROIA <144663685+Nelson-PROIA@users.noreply.github.com> Co-authored-by: Pierre Rossinès <pierre.rossines@mistral.ai> Co-authored-by: Quentin <quentin.torroba@mistral.ai> Co-authored-by: Robin Gullo <robin.gullo@mistral.ai> Co-authored-by: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
parent
a83c81ecf5
commit
632ea8c032
253 changed files with 13965 additions and 2525 deletions
192
tests/acp/test_fork_session.py
Normal file
192
tests/acp/test_fork_session.py
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
|
||||
from acp.schema import TextContentBlock
|
||||
import pytest
|
||||
|
||||
from vibe.acp.acp_agent_loop import VibeAcpAgentLoop
|
||||
from vibe.acp.exceptions import InvalidRequestError
|
||||
from vibe.core.agents.models import BuiltinAgentName
|
||||
from vibe.core.session.session_id import extract_suffix
|
||||
from vibe.core.types import FunctionCall, LLMMessage, Role, ToolCall
|
||||
|
||||
|
||||
class TestACPForkSession:
|
||||
@pytest.mark.asyncio
|
||||
async def test_fork_session_clones_history_and_mode(
|
||||
self, acp_agent_loop: VibeAcpAgentLoop
|
||||
) -> None:
|
||||
session_response = await acp_agent_loop.new_session(
|
||||
cwd=str(Path.cwd()), mcp_servers=[]
|
||||
)
|
||||
source_session = acp_agent_loop.sessions[session_response.session_id]
|
||||
|
||||
await acp_agent_loop.set_session_mode(
|
||||
session_id=source_session.id, mode_id=BuiltinAgentName.PLAN
|
||||
)
|
||||
await acp_agent_loop.prompt(
|
||||
prompt=[TextContentBlock(type="text", text="Say hi")],
|
||||
session_id=source_session.id,
|
||||
)
|
||||
|
||||
response = await acp_agent_loop.fork_session(
|
||||
cwd=str(Path.cwd()), session_id=source_session.id, mcp_servers=[]
|
||||
)
|
||||
|
||||
assert response.session_id != source_session.id
|
||||
assert response.modes is not None
|
||||
assert response.modes.current_mode_id == BuiltinAgentName.PLAN
|
||||
assert response.models is not None
|
||||
assert (
|
||||
response.models.current_model_id
|
||||
== source_session.agent_loop.config.active_model
|
||||
)
|
||||
assert response.config_options is not None
|
||||
assert len(response.config_options) == 3
|
||||
|
||||
forked_session = acp_agent_loop.sessions[response.session_id]
|
||||
assert forked_session.agent_loop.agent_profile.name == BuiltinAgentName.PLAN
|
||||
|
||||
source_messages = [
|
||||
message.model_dump(mode="json", exclude_none=True)
|
||||
for message in source_session.agent_loop.messages
|
||||
if message.role != Role.system
|
||||
]
|
||||
forked_messages = [
|
||||
message.model_dump(mode="json", exclude_none=True)
|
||||
for message in forked_session.agent_loop.messages
|
||||
if message.role != Role.system
|
||||
]
|
||||
assert forked_messages == source_messages
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fork_session_from_user_message_keeps_full_turn(
|
||||
self, acp_agent_loop: VibeAcpAgentLoop
|
||||
) -> None:
|
||||
session_response = await acp_agent_loop.new_session(
|
||||
cwd=str(Path.cwd()), mcp_servers=[]
|
||||
)
|
||||
source_session = acp_agent_loop.sessions[session_response.session_id]
|
||||
source_session.agent_loop.messages.extend([
|
||||
LLMMessage(role=Role.user, content="First", message_id="user-1"),
|
||||
LLMMessage(
|
||||
role=Role.assistant, content="First answer", message_id="assistant-1"
|
||||
),
|
||||
LLMMessage(
|
||||
role=Role.assistant,
|
||||
content="",
|
||||
message_id="assistant-2",
|
||||
tool_calls=[
|
||||
ToolCall(
|
||||
id="call-1",
|
||||
index=0,
|
||||
function=FunctionCall(
|
||||
name="read_file", arguments='{"path":"a.txt"}'
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
LLMMessage(role=Role.tool, content="contents", tool_call_id="call-1"),
|
||||
LLMMessage(role=Role.user, content="Second", message_id="user-2"),
|
||||
LLMMessage(
|
||||
role=Role.assistant, content="Second answer", message_id="assistant-3"
|
||||
),
|
||||
])
|
||||
|
||||
response = await acp_agent_loop.fork_session(
|
||||
cwd=str(Path.cwd()),
|
||||
session_id=source_session.id,
|
||||
mcp_servers=[],
|
||||
messageId="user-1",
|
||||
)
|
||||
|
||||
forked_session = acp_agent_loop.sessions[response.session_id]
|
||||
assert [
|
||||
(message.role, message.content, message.message_id, message.tool_call_id)
|
||||
for message in forked_session.agent_loop.messages
|
||||
if message.role != Role.system
|
||||
] == [
|
||||
(Role.user, "First", "user-1", None),
|
||||
(Role.assistant, "First answer", "assistant-1", None),
|
||||
(Role.assistant, "", "assistant-2", None),
|
||||
(Role.tool, "contents", None, "call-1"),
|
||||
]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fork_session_rejects_non_user_message_id(
|
||||
self, acp_agent_loop: VibeAcpAgentLoop
|
||||
) -> None:
|
||||
session_response = await acp_agent_loop.new_session(
|
||||
cwd=str(Path.cwd()), mcp_servers=[]
|
||||
)
|
||||
source_session = acp_agent_loop.sessions[session_response.session_id]
|
||||
source_session.agent_loop.messages.extend([
|
||||
LLMMessage(role=Role.user, content="First", message_id="user-1"),
|
||||
LLMMessage(
|
||||
role=Role.assistant, content="First answer", message_id="assistant-1"
|
||||
),
|
||||
])
|
||||
|
||||
with pytest.raises(
|
||||
InvalidRequestError,
|
||||
match="Fork from message_id is only supported for user messages",
|
||||
):
|
||||
await acp_agent_loop.fork_session(
|
||||
cwd=str(Path.cwd()),
|
||||
session_id=source_session.id,
|
||||
mcp_servers=[],
|
||||
messageId="assistant-1",
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fork_session_sets_parent_session_id(
|
||||
self, acp_agent_loop: VibeAcpAgentLoop
|
||||
) -> None:
|
||||
session_response = await acp_agent_loop.new_session(
|
||||
cwd=str(Path.cwd()), mcp_servers=[]
|
||||
)
|
||||
source_session = acp_agent_loop.sessions[session_response.session_id]
|
||||
|
||||
response = await acp_agent_loop.fork_session(
|
||||
cwd=str(Path.cwd()), session_id=source_session.id, mcp_servers=[]
|
||||
)
|
||||
|
||||
forked_session = acp_agent_loop.sessions[response.session_id]
|
||||
assert forked_session.agent_loop.parent_session_id == source_session.id
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fork_session_rejects_running_session(
|
||||
self, acp_agent_loop: VibeAcpAgentLoop
|
||||
) -> None:
|
||||
session_response = await acp_agent_loop.new_session(
|
||||
cwd=str(Path.cwd()), mcp_servers=[]
|
||||
)
|
||||
source_session = acp_agent_loop.sessions[session_response.session_id]
|
||||
source_session.set_prompt_task(asyncio.sleep(10))
|
||||
|
||||
with pytest.raises(
|
||||
InvalidRequestError,
|
||||
match="Cannot fork a session while the agent loop is running",
|
||||
):
|
||||
await acp_agent_loop.fork_session(
|
||||
cwd=str(Path.cwd()), session_id=source_session.id, mcp_servers=[]
|
||||
)
|
||||
|
||||
await source_session.cancel_prompt()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fork_session_preserves_session_id_suffix(
|
||||
self, acp_agent_loop: VibeAcpAgentLoop
|
||||
) -> None:
|
||||
session_response = await acp_agent_loop.new_session(
|
||||
cwd=str(Path.cwd()), mcp_servers=[]
|
||||
)
|
||||
source_session = acp_agent_loop.sessions[session_response.session_id]
|
||||
|
||||
response = await acp_agent_loop.fork_session(
|
||||
cwd=str(Path.cwd()), session_id=source_session.id, mcp_servers=[]
|
||||
)
|
||||
|
||||
assert extract_suffix(response.session_id) == extract_suffix(source_session.id)
|
||||
Loading…
Add table
Add a link
Reference in a new issue