From 80d4b7749be842b06c2cbdd96201f8cca175bf66 Mon Sep 17 00:00:00 2001 From: Vlad Cimpeanu Date: Mon, 7 Sep 2026 18:02:42 +0300 Subject: [PATCH 1/2] fix(cli): only claim uipath new scaffolds for langchain agents langgraph_new_middleware intercepted `uipath new` unconditionally, making the base function scaffold unreachable whenever uipath-langchain was installed (UiPath/uipath-python#1543). Gate it on the project_type and agent_framework values forwarded by uipath >= 2.14.13 and pass through anything that is not an agent scaffold for the langchain framework. Bump version to 0.17.2 and raise the uipath floor to 2.14.13, which ships the shared ProjectType/AgentFramework enums. Co-Authored-By: Claude Fable 5 --- pyproject.toml | 4 +- src/uipath_langchain/_cli/cli_new.py | 19 +++++++- tests/cli/test_new.py | 70 ++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8837991fc..a7567db34 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,11 @@ [project] name = "uipath-langchain" -version = "0.17.5" +version = "0.17.6" description = "Python SDK that enables developers to build and deploy LangGraph agents to the UiPath Cloud Platform" readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" dependencies = [ - "uipath>=2.14.6, <2.15.0", + "uipath>=2.14.14, <2.15.0", "uipath-core>=0.5.29, <0.6.0", "uipath-platform>=0.2.24, <0.3.0", "uipath-runtime>=0.13.0, <0.14.0", diff --git a/src/uipath_langchain/_cli/cli_new.py b/src/uipath_langchain/_cli/cli_new.py index bd6e6c642..91b8b2142 100644 --- a/src/uipath_langchain/_cli/cli_new.py +++ b/src/uipath_langchain/_cli/cli_new.py @@ -4,6 +4,8 @@ import click from uipath._cli._utils._console import ConsoleLogger from uipath._cli.middlewares import MiddlewareResult +from uipath._cli.models.agent_frameworks import AgentFramework +from uipath._cli.models.project_types import ProjectType console = ConsoleLogger() @@ -47,8 +49,21 @@ def generate_pyproject(target_directory, project_name): f.write(toml_content) -def langgraph_new_middleware(name: str) -> MiddlewareResult: - """Middleware to create demo langchain agent""" +def langgraph_new_middleware( + name: str, + project_type: ProjectType = ProjectType.AGENT, + agent_framework: AgentFramework = AgentFramework.LANGCHAIN, +) -> MiddlewareResult: + """Middleware to create demo langchain agent. + + Only handles `--type agent` scaffolds for the langchain framework; anything + else falls through to the base `uipath new` scaffold (default: function + project) or to another framework's integration. + """ + # `!=` instead of `is not`: callers may pass plain strings, which + # StrEnum equality handles. + if project_type != ProjectType.AGENT or agent_framework != AgentFramework.LANGCHAIN: + return MiddlewareResult(should_continue=True) directory = os.getcwd() diff --git a/tests/cli/test_new.py b/tests/cli/test_new.py index eaf8258d8..9b987a464 100644 --- a/tests/cli/test_new.py +++ b/tests/cli/test_new.py @@ -5,6 +5,8 @@ import pytest from packaging.specifiers import SpecifierSet from packaging.version import Version +from uipath._cli.models.agent_frameworks import AgentFramework +from uipath._cli.models.project_types import ProjectType from uipath_langchain._cli.cli_new import ( UIPATH_LANGCHAIN_SCAFFOLD_MINOR, @@ -14,6 +16,74 @@ PIN_RE = re.compile(r'"uipath-langchain\[bedrock,vertex\]([^"]*)"') +class TestProjectTypeGate: + """The middleware only claims agent scaffolds; everything else passes through.""" + + def test_function_type_passes_through( + self, tmp_path, monkeypatch: pytest.MonkeyPatch + ) -> None: + """project_type='function' must defer to the base `uipath new` scaffold. + + Regression guard for uipath-python#1543: installing uipath-langchain + used to hijack `uipath new` unconditionally, making the base function + scaffold unreachable. + """ + monkeypatch.chdir(tmp_path) + result = langgraph_new_middleware("demo", project_type=ProjectType.FUNCTION) + assert result.should_continue is True + assert not os.path.exists("main.py") + assert not os.path.exists("langgraph.json") + assert not os.path.exists("pyproject.toml") + + def test_agent_type_scaffolds_agent( + self, tmp_path, monkeypatch: pytest.MonkeyPatch + ) -> None: + monkeypatch.chdir(tmp_path) + result = langgraph_new_middleware( + "demo", + project_type=ProjectType.AGENT, + agent_framework=AgentFramework.LANGCHAIN, + ) + assert result.should_continue is False + assert os.path.exists("main.py") + assert os.path.exists("langgraph.json") + + def test_other_agent_framework_passes_through( + self, tmp_path, monkeypatch: pytest.MonkeyPatch + ) -> None: + """Another framework's scaffold must be left to its own integration.""" + monkeypatch.chdir(tmp_path) + result = langgraph_new_middleware( + "demo", + project_type=ProjectType.AGENT, + agent_framework=AgentFramework.PYDANTIC_AI, + ) + assert result.should_continue is True + assert not os.path.exists("main.py") + assert not os.path.exists("langgraph.json") + assert not os.path.exists("pyproject.toml") + + def test_plain_string_arguments_still_gate_correctly( + self, tmp_path, monkeypatch: pytest.MonkeyPatch + ) -> None: + """Callers passing raw strings (older CLIs) must hit the same gate.""" + monkeypatch.chdir(tmp_path) + result = langgraph_new_middleware( + "demo", project_type="agent", agent_framework="pydantic-ai" + ) + assert result.should_continue is True + assert not os.path.exists("langgraph.json") + + def test_default_type_stays_agent_for_old_base_cli( + self, tmp_path, monkeypatch: pytest.MonkeyPatch + ) -> None: + """Older `uipath` versions call without project_type; keep scaffolding an agent.""" + monkeypatch.chdir(tmp_path) + result = langgraph_new_middleware("demo") + assert result.should_continue is False + assert os.path.exists("langgraph.json") + + class TestUipathLangchainScaffoldPin: """The scaffolded pin must admit the uipath-langchain release it ships with.""" From 0fbab936bdbac4e03a00cf7a861b3afe3f73373d Mon Sep 17 00:00:00 2001 From: Vlad Cimpeanu Date: Tue, 8 Sep 2026 16:18:01 +0300 Subject: [PATCH 2/2] chore: pin uipath to the 2.14.14 dev build from testpypi Temporary, to validate and publish a dev build of this PR before uipath 2.14.14 ships: pin uipath==2.14.14.dev1018867580 (built from UiPath/uipath-python#1886) and source it from the testpypi index. Restore the ">=2.14.14, <2.15.0" range and drop the source before merging. Co-Authored-By: Claude Fable 5 --- pyproject.toml | 7 ++++++- uv.lock | 28 ++++++++++++++-------------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a7567db34..bbac8b330 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,9 @@ description = "Python SDK that enables developers to build and deploy LangGraph readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" dependencies = [ - "uipath>=2.14.14, <2.15.0", + # TODO(release): restore ">=2.14.14, <2.15.0" and drop the testpypi + # source once uipath 2.14.14 (UiPath/uipath-python#1886) is on PyPI. + "uipath==2.14.14.dev1018867580", "uipath-core>=0.5.29, <0.6.0", "uipath-platform>=0.2.24, <0.3.0", "uipath-runtime>=0.13.0, <0.14.0", @@ -182,3 +184,6 @@ name = "testpypi" url = "https://test.pypi.org/simple/" publish-url = "https://test.pypi.org/legacy/" explicit = true + +[tool.uv.sources] +uipath = { index = "testpypi" } diff --git a/uv.lock b/uv.lock index 9a4330de4..4d717c7f4 100644 --- a/uv.lock +++ b/uv.lock @@ -172,9 +172,9 @@ name = "aiologic" version = "0.17.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "sniffio" }, - { name = "typing-extensions" }, - { name = "wrapt" }, + { name = "sniffio", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "wrapt", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f1/7a/d51f2fde1e8ae8a83431f8e97b7a71e9358cdb1d4d2ce6be387fa44d68de/aiologic-0.17.1.tar.gz", hash = "sha256:2e1b93b9e88ced318c2a63ad7b382688f40cbfe40e3d42258d49dc9c5aea179d", size = 252354, upload-time = "2026-06-27T20:41:33.25Z" } wheels = [ @@ -991,8 +991,8 @@ name = "culsans" version = "0.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "aiologic" }, - { name = "typing-extensions" }, + { name = "aiologic", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d9/e3/49afa1bc180e0d28008ec6bcdf82a4072d1c7a41032b5b759b60814ca4b0/culsans-0.11.0.tar.gz", hash = "sha256:0b43d0d05dce6106293d114c86e3fb4bfc63088cfe8ff08ed3fe36891447fe33", size = 107546, upload-time = "2025-12-31T23:15:38.196Z" } wheels = [ @@ -4692,8 +4692,8 @@ wheels = [ [[package]] name = "uipath" -version = "2.14.6" -source = { registry = "https://pypi.org/simple" } +version = "2.14.14.dev1018867580" +source = { registry = "https://test.pypi.org/simple/" } dependencies = [ { name = "applicationinsights" }, { name = "click" }, @@ -4716,9 +4716,9 @@ dependencies = [ { name = "uipath-platform" }, { name = "uipath-runtime" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/43/f0/ab87d6776423d63320dc359cbb88cc1e57d5792fdd63c0f322ff55c110a7/uipath-2.14.6.tar.gz", hash = "sha256:c44fda80db5fae79b7e92aa2c0f34ba374eeea20d9efac7857568643ebffd456", size = 4543177, upload-time = "2026-08-19T13:13:10.581Z" } +sdist = { url = "https://test-files.pythonhosted.org/packages/bd/a6/7bf90fad07b87cf648280117d1b625c38057f3ea00e1d4a1ec31b88f698a/uipath-2.14.14.dev1018867580.tar.gz", hash = "sha256:fa0a493cb883f64d3269a62a659e696b4686ef58531d8f2e6cfb45df5b04af59", size = 4555192, upload-time = "2026-09-09T11:23:49.02Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/21/fceec66117831747a5c547d00b02030a8b97bf497e61619a2d8c3ea089c3/uipath-2.14.6-py3-none-any.whl", hash = "sha256:8ae40208aa4c8d987eba49bb5cff9fdaf4ee24f4db984e99b66af6948708bbcc", size = 435763, upload-time = "2026-08-19T13:13:08.627Z" }, + { url = "https://test-files.pythonhosted.org/packages/13/36/c9a1bee3ea559d356ccb549c218e13ea80bd0b51588789e69e1401aba2da/uipath-2.14.14.dev1018867580-py3-none-any.whl", hash = "sha256:15a1aecf6981c3e829f1b5f93172c78fcedb033726d949c3a2d9e3ea53e8ee00", size = 442107, upload-time = "2026-09-09T11:23:43.875Z" }, ] [[package]] @@ -4737,7 +4737,7 @@ wheels = [ [[package]] name = "uipath-langchain" -version = "0.17.5" +version = "0.17.6" source = { editable = "." } dependencies = [ { name = "a2a-sdk" }, @@ -4826,7 +4826,7 @@ requires-dist = [ { name = "pydantic-settings", specifier = ">=2.6.0" }, { name = "python-dotenv", specifier = ">=1.0.1" }, { name = "rdflib", specifier = ">=7.0.0,<8.0.0" }, - { name = "uipath", specifier = ">=2.14.6,<2.15.0" }, + { name = "uipath", specifier = "==2.14.14.dev1018867580", index = "https://test.pypi.org/simple/" }, { name = "uipath-core", specifier = ">=0.5.29,<0.6.0" }, { name = "uipath-langchain-client", extras = ["all"], marker = "extra == 'all'", specifier = ">=1.18.3,<1.19.0" }, { name = "uipath-langchain-client", extras = ["anthropic"], marker = "extra == 'anthropic'", specifier = ">=1.18.3,<1.19.0" }, @@ -4927,7 +4927,7 @@ wheels = [ [[package]] name = "uipath-platform" -version = "0.2.24" +version = "0.2.27" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -4938,9 +4938,9 @@ dependencies = [ { name = "truststore" }, { name = "uipath-core" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4d/d2/d2d79bd778e029ea32590be2aeab0b32f5f62ffab38d1a773bc13fb326f8/uipath_platform-0.2.24.tar.gz", hash = "sha256:6f1b8673d96c048142e6421a13b67002ca756ed80083e2c767ded4e0eccefd52", size = 447671, upload-time = "2026-09-03T12:21:32.472Z" } +sdist = { url = "https://files.pythonhosted.org/packages/35/37/92a26e6f793d063a38905bfc6ccdff3c30cd8584248be7522cc19c88ae1b/uipath_platform-0.2.27.tar.gz", hash = "sha256:165ad1e9502c691c5dcf80f79330e3ee2dc20f8b871c6801297066e049cccd1c", size = 448780, upload-time = "2026-09-09T08:02:46.136Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/40/f007b2819a5613047eff60f1f3b6ff8b108276abe2b7428cc0884d8c52d7/uipath_platform-0.2.24-py3-none-any.whl", hash = "sha256:1144874dba7e812ee2092580f94abba27c18f9552ca3a331992a571bfecdd561", size = 291730, upload-time = "2026-09-03T12:21:30.9Z" }, + { url = "https://files.pythonhosted.org/packages/b0/01/1f39ea45a1dd9348d3f20696068aee0bb429d122b3a58303c4a30a422f8b/uipath_platform-0.2.27-py3-none-any.whl", hash = "sha256:b900cbb5c2f629b9e39e91928d2d61b0ce381d939c971988ba75da787afc36ea", size = 292267, upload-time = "2026-09-09T08:02:44.319Z" }, ] [[package]]