Co-Authored-By: Quentin Torroba <quentin.torroba@mistral.ai>
Co-Authored-By: Michel Thomazo <michel.thomazo@mistral.ai>
Co-Authored-By: Clément Drouin <clement.drouin@mistral.ai>
Co-Authored-By: Vincent Guilloux <vincent.guilloux@mistral.ai>
Co-Authored-By: Clément Siriex <clement.sirieix@mistral.ai>
Co-Authored-By: Kim-Adeline Miguel <kimadeline.miguel@mistral.ai>
Co-Authored-By: Thaddee Tyl <thaddee.tyl@gmail.com>
Co-Authored-By: David Brochart <david.brochart@gmail.com>
Co-Authored-By: Joseph Guhlin <joseph.guhlin@gmail.com>
Co-Authored-By: Thomas Kenbeek <thomaskenbeek@gmail.com>
Co-Authored-By: Remenby31 <baptiste.cruvellier31@gmail.com>
This commit is contained in:
Mathias Gesbert 2026-01-27 16:39:30 +01:00 committed by Mathias Gesbert
parent 79f215d91c
commit d33db9fff8
217 changed files with 16911 additions and 4305 deletions

View file

@ -189,7 +189,7 @@ class WriteTextFileJsonRpcResponse(JsonRpcResponse):
result: None = None
async def get_acp_agent_process(
async def get_acp_agent_loop_process(
mock_env: dict[str, str], vibe_home: Path
) -> AsyncGenerator[asyncio.subprocess.Process]:
current_env = os.environ.copy()
@ -358,43 +358,43 @@ def parse_conversation(message_texts: list[str]) -> list[JsonRpcMessage]:
return parsed_messages
async def initialize_session(acp_agent_process: asyncio.subprocess.Process) -> str:
async def initialize_session(acp_agent_loop_process: asyncio.subprocess.Process) -> str:
await send_json_rpc(
acp_agent_process,
InitializeJsonRpcRequest(id=1, params=InitializeRequest(protocolVersion=1)),
acp_agent_loop_process,
InitializeJsonRpcRequest(id=1, params=InitializeRequest(protocol_version=1)),
)
initialize_response = await read_response_for_id(
acp_agent_process, expected_id=1, timeout=5.0
acp_agent_loop_process, expected_id=1, timeout=5.0
)
assert initialize_response is not None
await send_json_rpc(
acp_agent_process,
acp_agent_loop_process,
NewSessionJsonRpcRequest(
id=2, params=NewSessionRequest(cwd=str(PLAYGROUND_DIR), mcpServers=[])
id=2, params=NewSessionRequest(cwd=str(PLAYGROUND_DIR), mcp_servers=[])
),
)
session_response = await read_response_for_id(acp_agent_process, expected_id=2)
session_response = await read_response_for_id(acp_agent_loop_process, expected_id=2)
assert session_response is not None
session_response_json = json.loads(session_response)
session_response_obj = NewSessionJsonRpcResponse.model_validate(
session_response_json
)
assert session_response_obj.result is not None, "No result in response"
return session_response_obj.result.sessionId
return session_response_obj.result.session_id
class TestSessionManagement:
@pytest.mark.asyncio
async def test_multiple_sessions_unique_ids(self, vibe_home_dir: Path) -> None:
mock_env = get_mocking_env(mock_chunks=[mock_llm_chunk() for _ in range(3)])
async for process in get_acp_agent_process(
async for process in get_acp_agent_loop_process(
mock_env=mock_env, vibe_home=vibe_home_dir
):
await send_json_rpc(
process,
InitializeJsonRpcRequest(
id=1, params=InitializeRequest(protocolVersion=1)
id=1, params=InitializeRequest(protocol_version=1)
),
)
await read_response_for_id(process, expected_id=1, timeout=5.0)
@ -406,7 +406,7 @@ class TestSessionManagement:
NewSessionJsonRpcRequest(
id=i + 2,
params=NewSessionRequest(
cwd=str(PLAYGROUND_DIR), mcpServers=[]
cwd=str(PLAYGROUND_DIR), mcp_servers=[]
),
),
)
@ -418,16 +418,18 @@ class TestSessionManagement:
response = NewSessionJsonRpcResponse.model_validate(response_json)
assert response.error is None, f"JSON-RPC error: {response.error}"
assert response.result is not None, "No result in response"
session_ids.append(response.result.sessionId)
session_ids.append(response.result.session_id)
assert len(set(session_ids)) == 3
class TestSessionUpdates:
@pytest.mark.asyncio
async def test_agent_message_chunk_structure(self, vibe_home_dir: Path) -> None:
async def test_agent_loop_message_chunk_structure(
self, vibe_home_dir: Path
) -> None:
mock_env = get_mocking_env([mock_llm_chunk(content="Hi")])
async for process in get_acp_agent_process(
async for process in get_acp_agent_loop_process(
mock_env=mock_env, vibe_home=vibe_home_dir
):
# Check stderr for error details if process failed
@ -444,11 +446,19 @@ class TestSessionUpdates:
PromptJsonRpcRequest(
id=3,
params=PromptRequest(
sessionId=session_id,
session_id=session_id,
prompt=[TextContentBlock(type="text", text="Just say hi")],
),
),
)
user_response_text = await read_response(process)
assert user_response_text is not None
user_response = UpdateJsonRpcNotification.model_validate(
json.loads(user_response_text)
)
assert user_response.params is not None
assert user_response.params.update.session_update == "user_message_chunk"
text_response = await read_response(process)
assert text_response is not None
response = UpdateJsonRpcNotification.model_validate(
@ -456,8 +466,9 @@ class TestSessionUpdates:
)
assert response.params is not None
assert response.params.update.sessionUpdate == "agent_message_chunk"
assert response.params.update.session_update == "agent_message_chunk"
assert response.params.update.content is not None
assert isinstance(response.params.update.content, TextContentBlock)
assert response.params.update.content.type == "text"
assert response.params.update.content.text is not None
assert response.params.update.content.text == "Hi"
@ -478,7 +489,7 @@ class TestSessionUpdates:
),
mock_llm_chunk(content="The files containing the pattern 'auth' are ..."),
])
async for process in get_acp_agent_process(
async for process in get_acp_agent_loop_process(
mock_env=mock_env, vibe_home=vibe_home_dir
):
session_id = await initialize_session(process)
@ -488,7 +499,7 @@ class TestSessionUpdates:
PromptJsonRpcRequest(
id=3,
params=PromptRequest(
sessionId=session_id,
session_id=session_id,
prompt=[
TextContentBlock(
type="text",
@ -511,7 +522,7 @@ class TestSessionUpdates:
for r in responses
if isinstance(r, UpdateJsonRpcNotification)
and r.params is not None
and r.params.update.sessionUpdate == "tool_call"
and r.params.update.session_update == "tool_call"
),
None,
)
@ -519,11 +530,11 @@ class TestSessionUpdates:
assert tool_call.params is not None
assert tool_call.params.update is not None
assert tool_call.params.update.sessionUpdate == "tool_call"
assert tool_call.params.update.session_update == "tool_call"
assert tool_call.params.update.kind == "search"
assert tool_call.params.update.title == "grep: 'auth'"
assert tool_call.params.update.title == "Grepping 'auth'"
assert (
tool_call.params.update.rawInput
tool_call.params.update.raw_input
== '{"pattern":"auth","path":".","max_matches":null,"use_default_ignore":true}'
)
@ -537,7 +548,7 @@ async def start_session_with_request_permission(
PromptJsonRpcRequest(
id=3,
params=PromptRequest(
sessionId=session_id,
session_id=session_id,
prompt=[TextContentBlock(type="text", text=prompt)],
),
),
@ -575,7 +586,7 @@ class TestToolCallStructure:
)
]
mock_env = get_mocking_env(custom_results)
async for process in get_acp_agent_process(
async for process in get_acp_agent_loop_process(
mock_env=mock_env, vibe_home=vibe_home_grep_ask
):
session_id = await initialize_session(process)
@ -584,7 +595,7 @@ class TestToolCallStructure:
PromptJsonRpcRequest(
id=3,
params=PromptRequest(
sessionId=session_id,
session_id=session_id,
prompt=[
TextContentBlock(
type="text",
@ -611,8 +622,8 @@ class TestToolCallStructure:
first_request = permission_requests[0]
assert first_request.params is not None
assert first_request.params.toolCall is not None
assert first_request.params.toolCall.toolCallId is not None
assert first_request.params.tool_call is not None
assert first_request.params.tool_call.tool_call_id is not None
@pytest.mark.asyncio
async def test_tool_call_update_approved_structure(
@ -635,7 +646,7 @@ class TestToolCallStructure:
mock_llm_chunk(content="The file test.txt has been created"),
]
mock_env = get_mocking_env(custom_results)
async for process in get_acp_agent_process(
async for process in get_acp_agent_loop_process(
mock_env=mock_env, vibe_home=vibe_home_grep_ask
):
permission_request = await start_session_with_request_permission(
@ -649,7 +660,7 @@ class TestToolCallStructure:
id=permission_request.id,
result=RequestPermissionResponse(
outcome=AllowedOutcome(
outcome="selected", optionId=selected_option_id
outcome="selected", option_id=selected_option_id
)
),
),
@ -665,9 +676,9 @@ class TestToolCallStructure:
and r.method == "session/update"
and r.params is not None
and r.params.update is not None
and r.params.update.sessionUpdate == "tool_call_update"
and r.params.update.toolCallId
== (permission_request.params.toolCall.toolCallId)
and r.params.update.session_update == "tool_call_update"
and r.params.update.tool_call_id
== (permission_request.params.tool_call.tool_call_id)
and r.params.update.status == "completed"
),
None,
@ -697,7 +708,7 @@ class TestToolCallStructure:
),
]
mock_env = get_mocking_env(custom_results)
async for process in get_acp_agent_process(
async for process in get_acp_agent_loop_process(
mock_env=mock_env, vibe_home=vibe_home_grep_ask
):
permission_request = await start_session_with_request_permission(
@ -712,7 +723,7 @@ class TestToolCallStructure:
id=permission_request.id,
result=RequestPermissionResponse(
outcome=AllowedOutcome(
outcome="selected", optionId=selected_option_id
outcome="selected", option_id=selected_option_id
)
),
),
@ -727,9 +738,9 @@ class TestToolCallStructure:
if isinstance(r, UpdateJsonRpcNotification)
and r.method == "session/update"
and r.params is not None
and r.params.update.sessionUpdate == "tool_call_update"
and r.params.update.toolCallId
== (permission_request.params.toolCall.toolCallId)
and r.params.update.session_update == "tool_call_update"
and r.params.update.tool_call_id
== (permission_request.params.tool_call.tool_call_id)
and r.params.update.status == "failed"
),
None,
@ -758,7 +769,7 @@ class TestToolCallStructure:
mock_llm_chunk(content="The command sleep 3 has been run"),
]
mock_env = get_mocking_env(custom_results)
async for process in get_acp_agent_process(
async for process in get_acp_agent_loop_process(
mock_env=mock_env, vibe_home=vibe_home_grep_ask
):
session_id = await initialize_session(process)
@ -767,7 +778,7 @@ class TestToolCallStructure:
PromptJsonRpcRequest(
id=3,
params=PromptRequest(
sessionId=session_id,
session_id=session_id,
prompt=[
TextContentBlock(
type="text",
@ -786,7 +797,7 @@ class TestToolCallStructure:
for r in responses
if isinstance(r, UpdateJsonRpcNotification)
and r.params is not None
and r.params.update.sessionUpdate == "tool_call_update"
and r.params.update.session_update == "tool_call_update"
and r.params.update.status == "in_progress"
]
@ -817,7 +828,7 @@ class TestToolCallStructure:
),
]
mock_env = get_mocking_env(custom_results)
async for process in get_acp_agent_process(
async for process in get_acp_agent_loop_process(
mock_env=mock_env, vibe_home=vibe_home_grep_ask
):
permission_request = await start_session_with_request_permission(
@ -832,7 +843,7 @@ class TestToolCallStructure:
id=permission_request.id,
result=RequestPermissionResponse(
outcome=AllowedOutcome(
outcome="selected", optionId=selected_option_id
outcome="selected", option_id=selected_option_id
)
),
),
@ -847,10 +858,10 @@ class TestToolCallStructure:
for r in responses
if isinstance(r, UpdateJsonRpcNotification)
and r.params is not None
and r.params.update.sessionUpdate == "tool_call_update"
and r.params.update.session_update == "tool_call_update"
and r.params.update.status == "failed"
and r.params.update.rawOutput is not None
and r.params.update.toolCallId is not None
and r.params.update.raw_output is not None
and r.params.update.tool_call_id is not None
),
None,
)
@ -888,7 +899,7 @@ class TestCancellationStructure:
),
]
mock_env = get_mocking_env(custom_results)
async for process in get_acp_agent_process(
async for process in get_acp_agent_loop_process(
mock_env=mock_env, vibe_home=vibe_home_dir
):
permission_request = await start_session_with_request_permission(
@ -920,9 +931,9 @@ class TestCancellationStructure:
if isinstance(r, UpdateJsonRpcNotification)
and r.method == "session/update"
and r.params is not None
and r.params.update.sessionUpdate == "tool_call_update"
and r.params.update.toolCallId
== (permission_request.params.toolCall.toolCallId)
and r.params.update.session_update == "tool_call_update"
and r.params.update.tool_call_id
== (permission_request.params.tool_call.tool_call_id)
and r.params.update.status == "failed"
),
None,
@ -935,7 +946,7 @@ class TestCancellationStructure:
for r in responses
if isinstance(r, PromptJsonRpcResponse)
and r.result is not None
and r.result.stopReason == "cancelled"
and r.result.stop_reason == "cancelled"
),
None,
)