Skip to content

Commit 000fa6e

Browse files
feat: add models-list command
1 parent 64399bf commit 000fa6e

5 files changed

Lines changed: 162 additions & 2 deletions

File tree

packages/uipath/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath"
3-
version = "2.10.55"
3+
version = "2.10.56"
44
description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools."
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

packages/uipath/src/uipath/_cli/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"server": "cli_server",
4646
"register": "cli_register",
4747
"debug": "cli_debug",
48+
"list-models": "cli_list_models",
4849
"assets": "services.cli_assets",
4950
"buckets": "services.cli_buckets",
5051
"context-grounding": "services.cli_context_grounding",
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import click
2+
from rich.console import Console
3+
from rich.table import Table
4+
5+
from ._utils._service_base import ServiceCommandBase, service_command
6+
7+
8+
@click.command(name="list-models")
9+
@service_command
10+
async def list_models(ctx):
11+
"""List available LLM models in a table for human discovery."""
12+
client = ServiceCommandBase.get_client(ctx)
13+
models = await client.agenthub.get_available_llm_models_async()
14+
15+
table = Table(title="Available LLM Models", show_lines=False)
16+
table.add_column("Model", style="cyan", no_wrap=True)
17+
table.add_column("Vendor", style="magenta")
18+
19+
for model in sorted(models, key=lambda m: (m.vendor or "", m.model_name)):
20+
table.add_row(model.model_name, model.vendor or "")
21+
22+
Console().print(table)
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
"""Integration tests for the `uipath list-models` CLI command.
2+
3+
The command renders a rich table for human discovery — no machine-readable
4+
output mode. Tests verify that each model appears in the rendered output,
5+
the command is registered, and auth/error paths produce friendly messages.
6+
"""
7+
8+
from unittest.mock import AsyncMock, MagicMock, patch
9+
10+
import pytest
11+
from click.testing import CliRunner
12+
13+
from uipath._cli import cli
14+
from uipath.platform.agenthub import LlmModel
15+
16+
17+
@pytest.fixture
18+
def runner():
19+
"""Provide a Click CLI test runner."""
20+
return CliRunner()
21+
22+
23+
@pytest.fixture
24+
def mock_client():
25+
"""Provide a mocked UiPath client with an async agenthub service."""
26+
with patch("uipath.platform._uipath.UiPath") as mock:
27+
client_instance = MagicMock()
28+
mock.return_value = client_instance
29+
30+
client_instance.agenthub = MagicMock()
31+
client_instance.agenthub.get_available_llm_models_async = AsyncMock()
32+
33+
yield client_instance
34+
35+
36+
def _make_models() -> list[LlmModel]:
37+
"""Build a small list of LlmModel instances spanning multiple vendors."""
38+
return [
39+
LlmModel(model_name="gpt-4o-mini", vendor="OpenAi"),
40+
LlmModel(model_name="claude-sonnet-4-5", vendor="Anthropic"),
41+
LlmModel(model_name="gemini-2.5-flash", vendor="VertexAi"),
42+
]
43+
44+
45+
def test_list_models_renders_each_model(runner, mock_client, mock_env_vars):
46+
"""Each model and vendor appears in the rendered table."""
47+
mock_client.agenthub.get_available_llm_models_async.return_value = _make_models()
48+
49+
result = runner.invoke(cli, ["list-models"])
50+
51+
assert result.exit_code == 0
52+
for model in _make_models():
53+
assert model.model_name in result.output
54+
assert (model.vendor or "") in result.output
55+
mock_client.agenthub.get_available_llm_models_async.assert_awaited_once()
56+
57+
58+
def test_list_models_includes_table_title(runner, mock_client, mock_env_vars):
59+
"""The rich table renders its title for orientation."""
60+
mock_client.agenthub.get_available_llm_models_async.return_value = _make_models()
61+
62+
result = runner.invoke(cli, ["list-models"])
63+
64+
assert result.exit_code == 0
65+
assert "Available LLM Models" in result.output
66+
67+
68+
def test_list_models_handles_missing_vendor(runner, mock_client, mock_env_vars):
69+
"""A model with no vendor still renders cleanly."""
70+
mock_client.agenthub.get_available_llm_models_async.return_value = [
71+
LlmModel(model_name="custom-model", vendor=None),
72+
]
73+
74+
result = runner.invoke(cli, ["list-models"])
75+
76+
assert result.exit_code == 0
77+
assert "custom-model" in result.output
78+
79+
80+
def test_list_models_empty(runner, mock_client, mock_env_vars):
81+
"""An empty model list renders the table without any rows or errors."""
82+
mock_client.agenthub.get_available_llm_models_async.return_value = []
83+
84+
result = runner.invoke(cli, ["list-models"])
85+
86+
assert result.exit_code == 0
87+
assert "Available LLM Models" in result.output
88+
89+
90+
def test_list_models_service_error(runner, mock_client, mock_env_vars):
91+
"""Exceptions from the service are surfaced as click errors."""
92+
mock_client.agenthub.get_available_llm_models_async.side_effect = RuntimeError(
93+
"boom"
94+
)
95+
96+
result = runner.invoke(cli, ["list-models"])
97+
98+
assert result.exit_code != 0
99+
assert "boom" in result.output
100+
101+
102+
def test_list_models_missing_url(runner, monkeypatch):
103+
"""Missing UIPATH_URL surfaces an auth-configuration error."""
104+
monkeypatch.delenv("UIPATH_URL", raising=False)
105+
monkeypatch.setenv("UIPATH_ACCESS_TOKEN", "mock_token")
106+
107+
result = runner.invoke(cli, ["list-models"])
108+
109+
assert result.exit_code != 0
110+
assert "UIPATH_URL not configured" in result.output
111+
112+
113+
def test_list_models_missing_token(runner, monkeypatch):
114+
"""Missing UIPATH_ACCESS_TOKEN surfaces an auth-configuration error."""
115+
monkeypatch.setenv("UIPATH_URL", "https://cloud.uipath.com/org/tenant")
116+
monkeypatch.delenv("UIPATH_ACCESS_TOKEN", raising=False)
117+
118+
result = runner.invoke(cli, ["list-models"])
119+
120+
assert result.exit_code != 0
121+
assert "Authentication required" in result.output
122+
123+
124+
def test_list_models_help_text(runner):
125+
"""--help surfaces the command description."""
126+
result = runner.invoke(cli, ["list-models", "--help"])
127+
128+
assert result.exit_code == 0
129+
assert "List available LLM models" in result.output
130+
131+
132+
def test_list_models_registered_in_cli(runner):
133+
"""The command is wired into the top-level CLI group."""
134+
result = runner.invoke(cli, ["--help"])
135+
136+
assert result.exit_code == 0
137+
assert "list-models" in result.output

packages/uipath/uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)