diff --git a/packages/uipath/docs/cli/index.md b/packages/uipath/docs/cli/index.md index e74a4d83e..0fdddfe13 100644 --- a/packages/uipath/docs/cli/index.md +++ b/packages/uipath/docs/cli/index.md @@ -67,6 +67,66 @@ Treat `--client-secret` as a credential. In CI, prefer reading it from a secret --- +::: mkdocs-click + :module: uipath._cli + :command: new + :depth: 1 + :style: table + +Scaffolds a project in the current directory. `--type` selects what gets created: + +- **`auto`** (default) — an installed agent framework integration (e.g. `uipath-langchain`) claims the scaffold and creates a coded agent project; with none installed, a coded function project is created. +- **`function`** — always creates a coded function project, regardless of installed integrations. +- **`agent`** — creates a coded agent project with the installed framework integration, and fails if none is installed rather than quietly creating a function project. + +| Installed framework integrations | `uipath new x` (auto) | `uipath new x --type agent` | +|----------------------------------|------------------------|------------------------------| +| none | coded function project | error: install an agent framework | +| one | that framework's coded agent project | that framework's coded agent project | +| several | warns, scaffolds with the first discovered | warns, scaffolds with the first discovered | + +With one framework installed there is nothing to choose, so `uipath new` uses it. With several, it warns and scaffolds with the first one discovered — pass `--agent-framework` to choose deliberately: + +```shell +uipath new my-agent --type agent --agent-framework uipath-langchain +``` + +`--agent-framework` takes the integration package name, is only valid together with `--type agent`, and scaffolds with that framework alone. The names it accepts are the packages installed in your environment — the same ones the error above lists — so `uipath` keeps no list of its own. + +Scaffold a coded function: + + + +```shell +> uipath new my-function --type function +⠋ Creating new project my-function in current directory ... +✓ Created 'main.py' file. +✓ Created 'pyproject.toml' file. +✓ Created 'uipath.json' file. +💡 Initialize project: uipath init +💡 Run project: uipath run main '{"message": "Hello World!"}' +``` + +Scaffold a coded agent — requires the framework's integration package in the environment: + + + +```shell +> uv add uipath-langchain +Resolved 42 packages in 1.2s +Installed 42 packages in 0.8s + +> uipath new my-agent --type agent +⠋ Creating new agent my-agent in current directory ... +✓ Created 'main.py' file. +✓ Created 'langgraph.json' file. +✓ Created 'pyproject.toml' file. +💡 Initialize project: uipath init +💡 Run agent: uipath run agent '{"topic": "UiPath"}' +``` + +--- + ::: mkdocs-click :module: uipath._cli :command: init diff --git a/packages/uipath/docs/core/agents.md b/packages/uipath/docs/core/agents.md index 5621d864e..add97b8ed 100644 --- a/packages/uipath/docs/core/agents.md +++ b/packages/uipath/docs/core/agents.md @@ -25,6 +25,9 @@ The `uipath` package is always required. Add one framework extension on top: | PydanticAI | `uipath-pydantic-ai` | Type-safe agents with Pydantic models | | Google ADK | `uipath-google-adk` | Gemini models, Google ecosystem | | UiPath Agent Framework | `uipath-agent-framework` | UiPath-native agent primitives | +| Claude Agent SDK | `uipath-claude-sdk` | Claude models, Anthropic-native agent loop | + +Installing one of these packages is what makes `uipath new` scaffold an agent. With more than one installed, name the one you want with `--agent-framework`, using the package exactly as it appears above. --- @@ -85,6 +88,20 @@ The example below uses LangChain. Swap `uipath-langchain` for the framework of y //// +/// info | Guaranteeing an agent project +`uipath new` defaults to `--type auto`: the installed framework integration claims the scaffold, which is why the commands above create an agent project. Pass `--type agent` to make that a requirement — it fails with instructions instead of creating a function project when no framework is installed: + +```shell +uipath new agent --type agent +``` + +With several framework integrations installed in the same environment, `uipath new` warns and uses the first one discovered; name the one you want instead with: + +```shell +uipath new agent --type agent --agent-framework uipath-langchain +``` +/// + --- ## Project Structure diff --git a/packages/uipath/docs/core/functions.md b/packages/uipath/docs/core/functions.md index 6c073f68f..598f0df3b 100644 --- a/packages/uipath/docs/core/functions.md +++ b/packages/uipath/docs/core/functions.md @@ -70,6 +70,14 @@ Use coded functions for deterministic compute steps: document extraction, ERP wr //// +/// info | Guaranteeing a function project +`uipath new` defaults to `--type auto`: when an agent framework integration (e.g. `uipath-langchain`) is installed in the environment, it scaffolds a coded agent instead of a function. Pass `--type function` to always get a coded function project: + +```shell +uipath new my-function --type function +``` +/// + --- ## Project Structure diff --git a/packages/uipath/docs/core/studio_web.md b/packages/uipath/docs/core/studio_web.md index 09286dd73..d44a565c4 100644 --- a/packages/uipath/docs/core/studio_web.md +++ b/packages/uipath/docs/core/studio_web.md @@ -171,6 +171,7 @@ First, install the SDK package for the framework you want to use: # uipath-pydantic-ai - PydanticAI # uipath-google-adk - Google ADK # uipath-agent-framework - UiPath Agent Framework +# uipath-claude-sdk - Claude Agent SDK > uv add uipath-langchain Resolved 42 packages in 1.2s Installed 42 packages in 0.8s @@ -190,6 +191,7 @@ Installed 42 packages in 0.8s # uipath-pydantic-ai - PydanticAI # uipath-google-adk - Google ADK # uipath-agent-framework - UiPath Agent Framework +# uipath-claude-sdk - Claude Agent SDK > pip install uipath-langchain Successfully installed uipath-langchain ``` @@ -221,9 +223,13 @@ Selected tenant: Tenant1 That's it, your agent should now be visible in Studio Web. +/// info +`uipath new` defaults to `--type auto`, which lets the installed framework integration claim the scaffold. Pass `--type agent` to require an agent project — it fails rather than creating a function project when no framework is installed. With several integrations installed, `uipath new` warns and uses the first one discovered; pick explicitly with `uipath new agent --type agent --agent-framework uipath-langchain`. +/// + #### Coded Function -A coded function doesn't require an additional framework package. Authenticate, scaffold the project, and initialize it: +A coded function doesn't require an additional framework package. Authenticate, scaffold the project, and initialize it (`--type function` guarantees a function project even when a framework integration is installed): @@ -238,7 +244,7 @@ Select tenant number: 0 Selected tenant: Tenant1 ✓ Authentication successful. -> uipath new my-function +> uipath new my-function --type function ✓ Created 'main.py' file. ✓ Created 'pyproject.toml' file. ✓ Created 'uipath.json' file. diff --git a/packages/uipath/pyproject.toml b/packages/uipath/pyproject.toml index 7eea2b2c8..e778925c8 100644 --- a/packages/uipath/pyproject.toml +++ b/packages/uipath/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath" -version = "2.14.22" +version = "2.14.23" description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools." readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" diff --git a/packages/uipath/src/uipath/_cli/_utils/_constants.py b/packages/uipath/src/uipath/_cli/_utils/_constants.py index 88dac9a96..39a286725 100644 --- a/packages/uipath/src/uipath/_cli/_utils/_constants.py +++ b/packages/uipath/src/uipath/_cli/_utils/_constants.py @@ -61,3 +61,7 @@ def is_binary_file(file_extension: str) -> bool: """Determine if a file should be treated as binary.""" return file_extension.lower() in BINARY_EXTENSIONS + + +# Supported agent frameworks and their packages, for `uipath new --type agent` +AGENT_FRAMEWORKS_DOCS_URL = "https://uipath.github.io/uipath-python/core/agents/" diff --git a/packages/uipath/src/uipath/_cli/cli_new.py b/packages/uipath/src/uipath/_cli/cli_new.py index 6d021a867..0d6aa5847 100644 --- a/packages/uipath/src/uipath/_cli/cli_new.py +++ b/packages/uipath/src/uipath/_cli/cli_new.py @@ -1,3 +1,4 @@ +import importlib.metadata import json import os import shutil @@ -9,8 +10,11 @@ from ._telemetry import track_command from ._utils._console import ConsoleLogger +from ._utils._constants import AGENT_FRAMEWORKS_DOCS_URL from ._utils._project_files import resolve_existing_project_id -from .middlewares import Middlewares +from .middlewares import MiddlewareResult, Middlewares +from .models.agent_frameworks import AgentFramework +from .models.project_types import ProjectType console = ConsoleLogger() @@ -57,10 +61,91 @@ def generate_uipath_json(target_directory): json.dump(uipath_config, f, indent=2) +def installed_agent_frameworks() -> list[AgentFramework]: + """Agent frameworks that can scaffold a project, in discovery order.""" + packages: dict[str, str] = {} + for entry_point in importlib.metadata.entry_points(group="uipath.middlewares"): + if entry_point.dist is not None: + packages.setdefault(entry_point.module.split(".")[0], entry_point.dist.name) + + frameworks = [] + for middleware in Middlewares.get("new"): + package = packages.get(middleware.__module__.split(".")[0]) + if package is not None: + frameworks.append(AgentFramework(package=package, scaffold=middleware)) + return frameworks + + +def _select_agent_framework( + frameworks: list[AgentFramework], requested: str +) -> AgentFramework: + """Resolve `--agent-framework` against what is installed.""" + for framework in frameworks: + if requested == framework.package: + return framework + + installed = ( + "Installed: " + + ", ".join(sorted(framework.package for framework in frameworks)) + + "." + if frameworks + else "No agent framework is installed." + ) + console.error( + f"No installed agent framework matches '{requested}'.\n" + f"{installed}\n" + f"See {AGENT_FRAMEWORKS_DOCS_URL} for the supported frameworks and " + "their packages." + ) + + +def _scaffold_agent(name: str, agent_framework: str | None) -> MiddlewareResult: + """Offer the scaffold to the installed agent frameworks.""" + Middlewares.load_plugins() + installed = installed_agent_frameworks() + + if agent_framework: + # Dispatch to the chosen framework alone, so that another one cannot + # claim the scaffold ahead of it. + return _select_agent_framework(installed, agent_framework).scaffold(name) + + if len(installed) > 1: + first = installed[0] + console.warning( + "Multiple agent frameworks are installed: " + + ", ".join(framework.package for framework in installed) + + f".\nScaffolding with the first one discovered: '{first.package}'. " + f"To pick a different one, run `uipath new {name} --type agent " + "--agent-framework `." + ) + return first.scaffold(name) + + return Middlewares.next("new", name) + + @click.command() @click.argument("name", type=str, default="") +@click.option( + "--type", + "project_type", + type=click.Choice([t.value for t in ProjectType]), + default=ProjectType.AUTO.value, + show_default=True, + help="Project type to scaffold. 'auto' scaffolds an agent when an agent " + "framework package (e.g. uipath-langchain) is installed and a function " + "otherwise; 'function' always scaffolds a function; 'agent' scaffolds an " + "agent and fails when no agent framework is installed.", +) +@click.option( + "--agent-framework", + "agent_framework", + default=None, + help="Agent framework to scaffold with, named by its package (e.g. " + "`uipath-langchain`). Only valid together with `--type agent`; picks " + "which framework scaffolds when several are installed.", +) @track_command("new") -def new(name: str): +def new(name: str, project_type: str, agent_framework: str | None): """Generate a quick-start project.""" directory = os.getcwd() @@ -69,18 +154,39 @@ def new(name: str): "Please specify a name for your project:\n`uipath new hello-world`" ) - result = Middlewares.next("new", name) + scaffold_type = ProjectType(project_type) - if result.error_message: + if agent_framework and scaffold_type is not ProjectType.AGENT: console.error( - result.error_message, include_traceback=result.should_include_stacktrace + "`--agent-framework` can only be used together with `--type agent`." ) - if result.info_message: - console.info(result.info_message) - - if not result.should_continue: - return + # Agent frameworks scaffold through the `new` middleware chain. A function + # project never consults them. + if scaffold_type is not ProjectType.FUNCTION: + result = _scaffold_agent(name, agent_framework) + + if result.error_message: + console.error( + result.error_message, include_traceback=result.should_include_stacktrace + ) + + if result.info_message: + console.info(result.info_message) + + if not result.should_continue: + return # an agent framework scaffolded the project + + if scaffold_type is ProjectType.AGENT: + console.error( + "No agent framework is installed, so there is nothing to " + "scaffold an agent with.\n" + "Install the framework you want to use and run this command " + f"again — see {AGENT_FRAMEWORKS_DOCS_URL} for the supported " + "frameworks and their packages.\n" + f"Or run `uipath new {name} --type function` to create a " + "function project." + ) with console.spinner(f"Creating new project {name} in current directory ..."): generate_script(directory) diff --git a/packages/uipath/src/uipath/_cli/models/agent_frameworks.py b/packages/uipath/src/uipath/_cli/models/agent_frameworks.py new file mode 100644 index 000000000..876b5100b --- /dev/null +++ b/packages/uipath/src/uipath/_cli/models/agent_frameworks.py @@ -0,0 +1,16 @@ +"""Agent frameworks that scaffold projects for `uipath new`.""" + +from dataclasses import dataclass + +from ..middlewares import MiddlewareFunc + + +@dataclass(frozen=True) +class AgentFramework: + """An agent framework installed in this environment.""" + + package: str + """Package that provides it, and the value `--agent-framework` takes.""" + + scaffold: MiddlewareFunc + """Its `new` middleware.""" diff --git a/packages/uipath/src/uipath/_cli/models/project_types.py b/packages/uipath/src/uipath/_cli/models/project_types.py new file mode 100644 index 000000000..c8ab05ae1 --- /dev/null +++ b/packages/uipath/src/uipath/_cli/models/project_types.py @@ -0,0 +1,16 @@ +"""Project types scaffolded by `uipath new`.""" + +from enum import StrEnum + + +class ProjectType(StrEnum): + """What `uipath new` scaffolds. + + AUTO (the default) lets an installed agent framework claim the scaffold + and falls back to a function project; FUNCTION and AGENT request one + explicitly. + """ + + AUTO = "auto" + FUNCTION = "function" + AGENT = "agent" diff --git a/packages/uipath/tests/cli/test_new.py b/packages/uipath/tests/cli/test_new.py index d2d278a62..08fe9ba37 100644 --- a/packages/uipath/tests/cli/test_new.py +++ b/packages/uipath/tests/cli/test_new.py @@ -3,14 +3,32 @@ import re import uuid from importlib.metadata import version -from unittest.mock import patch +from types import SimpleNamespace +from typing import cast +from unittest.mock import MagicMock, patch from click.testing import CliRunner from packaging.specifiers import SpecifierSet from packaging.version import Version from uipath._cli import cli +from uipath._cli._utils._constants import AGENT_FRAMEWORKS_DOCS_URL +from uipath._cli.cli_new import installed_agent_frameworks from uipath._cli.middlewares import MiddlewareResult +from uipath._cli.models.agent_frameworks import AgentFramework + + +def _framework(package: str) -> AgentFramework: + """An installed agent framework whose scaffold claims the project.""" + return AgentFramework( + package=package, + scaffold=MagicMock(return_value=MiddlewareResult(should_continue=False)), + ) + + +def _scaffold_of(framework: AgentFramework) -> MagicMock: + """The mock behind a `_framework()` scaffold, to assert calls on.""" + return cast(MagicMock, framework.scaffold) class TestNew: @@ -84,6 +102,284 @@ def test_new_project_middleware_interaction( assert result.exit_code == 0 assert os.path.exists("main.py") + def test_new_default_type_consults_the_agent_frameworks( + self, runner: CliRunner, temp_dir: str + ) -> None: + """Without --type, an installed agent framework gets first refusal. + + The chain is called with the project name only: the base CLI decides + whether frameworks are consulted at all, so they need to know nothing + about project types. + """ + with runner.isolated_filesystem(temp_dir=temp_dir): + with patch("uipath._cli.cli_new.Middlewares.next") as mock_middleware: + mock_middleware.return_value = MiddlewareResult(should_continue=True) + + result = runner.invoke(cli, ["new", "my_project"]) + assert result.exit_code == 0 + mock_middleware.assert_called_once_with("new", "my_project") + # Nothing claimed it, so the base function scaffold runs. + assert os.path.exists("uipath.json") + + def test_new_type_auto_claimed_by_a_framework( + self, runner: CliRunner, temp_dir: str + ) -> None: + """--type auto lets an agent framework claim the scaffold.""" + with runner.isolated_filesystem(temp_dir=temp_dir): + with patch("uipath._cli.cli_new.Middlewares.next") as mock_middleware: + mock_middleware.return_value = MiddlewareResult(should_continue=False) + + result = runner.invoke(cli, ["new", "my_agent", "--type", "auto"]) + assert result.exit_code == 0 + mock_middleware.assert_called_once_with("new", "my_agent") + # Claimed by a framework: no base function scaffold. + assert not os.path.exists("uipath.json") + + def test_new_type_function_never_consults_agent_frameworks( + self, runner: CliRunner, temp_dir: str + ) -> None: + """--type function must always produce a function project. + + The middleware chain is skipped entirely, so an installed agent + framework cannot intercept the scaffold no matter what it claims. + """ + with runner.isolated_filesystem(temp_dir=temp_dir): + with patch("uipath._cli.cli_new.Middlewares.next") as mock_middleware: + mock_middleware.return_value = MiddlewareResult(should_continue=False) + + result = runner.invoke(cli, ["new", "my_project", "--type", "function"]) + assert result.exit_code == 0 + mock_middleware.assert_not_called() + with open("uipath.json") as f: + config = json.load(f) + assert config["functions"] == {"main": "main.py:main"} + + def test_new_type_agent_scaffolds_through_the_framework( + self, runner: CliRunner, temp_dir: str + ) -> None: + """--type agent with a framework installed hands over to it.""" + with runner.isolated_filesystem(temp_dir=temp_dir): + with patch("uipath._cli.cli_new.Middlewares.next") as mock_middleware: + mock_middleware.return_value = MiddlewareResult(should_continue=False) + + result = runner.invoke(cli, ["new", "my_agent", "--type", "agent"]) + assert result.exit_code == 0 + mock_middleware.assert_called_once_with("new", "my_agent") + assert not os.path.exists("uipath.json") + + def test_new_type_agent_without_a_framework_errors( + self, runner: CliRunner, temp_dir: str + ) -> None: + """--type agent is a guarantee: never silently a function project.""" + with runner.isolated_filesystem(temp_dir=temp_dir): + with patch("uipath._cli.cli_new.Middlewares.next") as mock_middleware: + mock_middleware.return_value = MiddlewareResult(should_continue=True) + + result = runner.invoke(cli, ["new", "my_agent", "--type", "agent"]) + assert result.exit_code == 1 + assert "No agent framework is installed" in result.output + assert AGENT_FRAMEWORKS_DOCS_URL in result.output + assert "--type function" in result.output + assert not os.path.exists("main.py") + assert not os.path.exists("uipath.json") + + def test_new_multiple_frameworks_warn_and_use_the_first_discovered( + self, runner: CliRunner, temp_dir: str + ) -> None: + """Several frameworks installed: warn, then scaffold with the first. + + Erroring here would break environments that scaffolded fine before + `--type` existed, so the old behaviour stays the default and the + warning points at `--agent-framework` for choosing explicitly. + """ + with runner.isolated_filesystem(temp_dir=temp_dir): + for extra_args in ([], ["--type", "auto"], ["--type", "agent"]): + first = _framework("uipath-llamaindex") + other = _framework("uipath-langchain") + with ( + patch("uipath._cli.cli_new.Middlewares.next") as mock_middleware, + patch( + "uipath._cli.cli_new.installed_agent_frameworks", + return_value=[first, other], + ), + ): + result = runner.invoke(cli, ["new", "my_agent"] + extra_args) + assert result.exit_code == 0 + assert "Multiple agent frameworks are installed" in result.output + # listed in discovery order, so the first named is the pick + assert "uipath-llamaindex, uipath-langchain" in result.output + assert "first one discovered: 'uipath-llamaindex'" in result.output + assert "--agent-framework" in result.output + _scaffold_of(first).assert_called_once_with("my_agent") + _scaffold_of(other).assert_not_called() + mock_middleware.assert_not_called() + # claimed by the framework: no base function scaffold + assert not os.path.exists("uipath.json") + + def test_agent_framework_scaffolds_with_the_chosen_one_only( + self, runner: CliRunner, temp_dir: str + ) -> None: + """--agent-framework dispatches to that framework and nothing else.""" + with runner.isolated_filesystem(temp_dir=temp_dir): + chosen = _framework("uipath-llamaindex") + other = _framework("uipath-langchain") + with ( + patch("uipath._cli.cli_new.Middlewares.next") as mock_middleware, + patch( + "uipath._cli.cli_new.installed_agent_frameworks", + return_value=[other, chosen], + ), + ): + result = runner.invoke( + cli, + [ + "new", + "my_agent", + "--type", + "agent", + "--agent-framework", + "uipath-llamaindex", + ], + ) + assert result.exit_code == 0 + _scaffold_of(chosen).assert_called_once_with("my_agent") + _scaffold_of(other).assert_not_called() + # The chain would have let the first registered one claim it. + mock_middleware.assert_not_called() + assert not os.path.exists("uipath.json") + + def test_agent_framework_takes_the_package_name_only( + self, runner: CliRunner, temp_dir: str + ) -> None: + """One spelling: the package, exactly as the error messages list it.""" + with runner.isolated_filesystem(temp_dir=temp_dir): + chosen = _framework("uipath-langchain") + for requested, expected_exit in (("uipath-langchain", 0), ("langchain", 1)): + with patch( + "uipath._cli.cli_new.installed_agent_frameworks", + return_value=[chosen, _framework("uipath-llamaindex")], + ): + result = runner.invoke( + cli, + [ + "new", + "my_agent", + "--type", + "agent", + "--agent-framework", + requested, + ], + ) + assert result.exit_code == expected_exit + _scaffold_of(chosen).assert_called_once_with("my_agent") + + def test_agent_framework_not_installed_errors( + self, runner: CliRunner, temp_dir: str + ) -> None: + """Asking for a framework that isn't installed names the ones that are.""" + with runner.isolated_filesystem(temp_dir=temp_dir): + installed = _framework("uipath-langchain") + with patch( + "uipath._cli.cli_new.installed_agent_frameworks", + return_value=[installed], + ): + result = runner.invoke( + cli, + [ + "new", + "my_agent", + "--type", + "agent", + "--agent-framework", + "uipath-crewai", + ], + ) + assert result.exit_code == 1 + assert ( + "No installed agent framework matches 'uipath-crewai'" + in result.output + ) + assert "Installed: uipath-langchain." in result.output + assert AGENT_FRAMEWORKS_DOCS_URL in result.output + _scaffold_of(installed).assert_not_called() + assert not os.path.exists("main.py") + + def test_agent_framework_with_none_installed_errors( + self, runner: CliRunner, temp_dir: str + ) -> None: + with runner.isolated_filesystem(temp_dir=temp_dir): + with patch( + "uipath._cli.cli_new.installed_agent_frameworks", return_value=[] + ): + result = runner.invoke( + cli, + [ + "new", + "my_agent", + "--type", + "agent", + "--agent-framework", + "uipath-langchain", + ], + ) + assert result.exit_code == 1 + assert "No agent framework is installed." in result.output + assert not os.path.exists("main.py") + + def test_agent_framework_requires_type_agent( + self, runner: CliRunner, temp_dir: str + ) -> None: + """The flag names an agent framework, so it only makes sense for agents.""" + with runner.isolated_filesystem(temp_dir=temp_dir): + for extra_args in ([], ["--type", "auto"], ["--type", "function"]): + result = runner.invoke( + cli, + ["new", "my_project", "--agent-framework", "uipath-langchain"] + + extra_args, + ) + assert result.exit_code == 1 + assert ( + "`--agent-framework` can only be used together with " + "`--type agent`" in result.output + ) + assert not os.path.exists("main.py") + + def test_installed_agent_frameworks_are_named_after_their_package(self) -> None: + """Frameworks come from the registered middlewares, not a static list.""" + + def fake_middleware(name: str) -> MiddlewareResult: # pragma: no cover + return MiddlewareResult(should_continue=True) + + fake_middleware.__module__ = "uipath_langchain._cli.cli_new" + entry_point = SimpleNamespace( + module="uipath_langchain.middlewares", + dist=SimpleNamespace(name="uipath-langchain"), + ) + + with patch( + "uipath._cli.cli_new.Middlewares.get", return_value=[fake_middleware] + ): + with patch( + "uipath._cli.cli_new.importlib.metadata.entry_points", + return_value=[entry_point], + ): + (framework,) = installed_agent_frameworks() + assert framework.package == "uipath-langchain" + assert framework.scaffold is fake_middleware + # Registered in-process instead of through an entry point: there is + # no package to name it with, so it is not one to choose between. + with patch( + "uipath._cli.cli_new.importlib.metadata.entry_points", return_value=[] + ): + assert installed_agent_frameworks() == [] + + def test_new_invalid_type_rejected(self, runner: CliRunner, temp_dir: str) -> None: + """Unknown --type values are rejected by click.""" + with runner.isolated_filesystem(temp_dir=temp_dir): + result = runner.invoke(cli, ["new", "my_project", "--type", "workflow"]) + assert result.exit_code == 2 + assert not os.path.exists("main.py") + def test_new_project_error_handling(self, runner: CliRunner, temp_dir: str) -> None: """Test error handling in new command.""" with runner.isolated_filesystem(temp_dir=temp_dir): diff --git a/packages/uipath/uv.lock b/packages/uipath/uv.lock index c9851f030..cd8ee6671 100644 --- a/packages/uipath/uv.lock +++ b/packages/uipath/uv.lock @@ -2599,7 +2599,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.14.22" +version = "2.14.23" source = { editable = "." } dependencies = [ { name = "applicationinsights" },