Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/runtimeuse-client-python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "runtimeuse-client"
version = "0.9.1"
version = "0.10.0"
description = "Client library for AI agent runtime communication over WebSocket"
readme = "README.md"
license = {"text" = "FSL"}
Expand Down
34 changes: 28 additions & 6 deletions packages/runtimeuse-client-python/test/e2e/test_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -652,14 +652,36 @@ async def on_msg(msg: AssistantMessageInterface):
all_text = [block for msg in received for block in msg.text_blocks]
assert any("streamed-sentinel" in t for t in all_text)

async def test_failed_command_raises_error(
async def test_failed_command_returns_exit_code(
self, client: RuntimeUseClient, make_execute_commands_options
):
with pytest.raises(AgentRuntimeError, match="command failed with exit code"):
await client.execute_commands(
commands=[CommandInterface(command="exit 1")],
options=make_execute_commands_options(timeout=10),
)
result = await client.execute_commands(
commands=[CommandInterface(command="exit 1")],
options=make_execute_commands_options(timeout=10),
)

assert isinstance(result, CommandExecutionResult)
assert len(result.results) == 1
assert result.results[0].command == "exit 1"
assert result.results[0].exit_code == 1

async def test_failed_command_skips_remaining(
self, client: RuntimeUseClient, make_execute_commands_options
):
result = await client.execute_commands(
commands=[
CommandInterface(command="echo first"),
CommandInterface(command="exit 2"),
CommandInterface(command="echo should-not-run"),
],
options=make_execute_commands_options(timeout=10),
)

assert len(result.results) == 2
assert result.results[0].command == "echo first"
assert result.results[0].exit_code == 0
assert result.results[1].command == "exit 2"
assert result.results[1].exit_code == 2

async def test_agent_handler_not_invoked(
self, client: RuntimeUseClient, make_execute_commands_options
Expand Down
37 changes: 37 additions & 0 deletions packages/runtimeuse-client-python/test/sandbox/test_e2b.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,40 @@ async def on_msg(msg: AssistantMessageInterface):
assert any("hello-from-e2b" in t for t in all_text)
finally:
sandbox.kill()

async def test_execute_commands_failure_returns_exit_code(self):
sandbox, ws_url = create_e2b_runtimeuse(agent="openai")
try:
client = RuntimeUseClient(ws_url=ws_url)
result = await client.execute_commands(
commands=[
CommandInterface(command="exit 1"),
],
options=ExecuteCommandsOptions(timeout=30),
)

assert isinstance(result, CommandExecutionResult)
assert len(result.results) == 1
assert result.results[0].exit_code == 1
finally:
sandbox.kill()

async def test_execute_commands_failure_skips_remaining(self):
sandbox, ws_url = create_e2b_runtimeuse(agent="openai")
try:
client = RuntimeUseClient(ws_url=ws_url)
result = await client.execute_commands(
commands=[
CommandInterface(command="echo first"),
CommandInterface(command="exit 1"),
CommandInterface(command="echo should-not-run"),
],
options=ExecuteCommandsOptions(timeout=30),
)

assert isinstance(result, CommandExecutionResult)
assert len(result.results) == 2
assert result.results[0].exit_code == 0
assert result.results[1].exit_code == 1
finally:
sandbox.kill()
51 changes: 48 additions & 3 deletions packages/runtimeuse-client-python/test/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -585,17 +585,62 @@ async def test_error_message_raises(
):
error_msg = {
"message_type": "error_message",
"error": "command failed with exit code: 1",
"error": "something went wrong",
"metadata": {},
}
transport, client = fake_transport([error_msg])

with pytest.raises(AgentRuntimeError, match="command failed"):
with pytest.raises(AgentRuntimeError, match="something went wrong"):
await client.execute_commands(
commands=[CommandInterface(command="exit 1")],
commands=[CommandInterface(command="echo hello")],
options=make_execute_commands_options(),
)

@pytest.mark.asyncio
async def test_non_zero_exit_code_returns_result(
self, fake_transport, make_execute_commands_options
):
result_msg = {
"message_type": "command_execution_result_message",
"results": [{"command": "exit 1", "exit_code": 1}],
}
transport, client = fake_transport([result_msg])

result = await client.execute_commands(
commands=[CommandInterface(command="exit 1")],
options=make_execute_commands_options(),
)

assert isinstance(result, CommandExecutionResult)
assert len(result.results) == 1
assert result.results[0].exit_code == 1

@pytest.mark.asyncio
async def test_failed_command_skips_remaining(
self, fake_transport, make_execute_commands_options
):
result_msg = {
"message_type": "command_execution_result_message",
"results": [
{"command": "echo first", "exit_code": 0},
{"command": "exit 1", "exit_code": 1},
],
}
transport, client = fake_transport([result_msg])

result = await client.execute_commands(
commands=[
CommandInterface(command="echo first"),
CommandInterface(command="exit 1"),
CommandInterface(command="echo skipped"),
],
options=make_execute_commands_options(),
)

assert len(result.results) == 2
assert result.results[0].exit_code == 0
assert result.results[1].exit_code == 1

@pytest.mark.asyncio
async def test_no_result_raises(
self, fake_transport, make_execute_commands_options
Expand Down
Loading