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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

All notable changes to `uipath_llm_client` (core package) will be documented in this file.

## [1.2.0] - 2026-02-18

### Stable release

### Fix
- Fixed agenhub auth when token already exists

## [1.1.1] - 2026-02-12

### Fix
Expand Down
4 changes: 4 additions & 0 deletions packages/uipath_langchain_client/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

All notable changes to `uipath_langchain_client` will be documented in this file.

## [1.2.0] - 2026-02-18

### Stable release

## [1.1.9] - 2026-02-13

### Docs
Expand Down
2 changes: 1 addition & 1 deletion packages/uipath_langchain_client/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"langchain>=1.2.7",
"uipath-llm-client>=1.1.1",
"uipath-llm-client>=1.2.0",
]

[project.optional-dependencies]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__title__ = "UiPath LangChain Client"
__description__ = "A Python client for interacting with UiPath's LLM services via LangChain."
__version__ = "1.1.9"
__version__ = "1.2.0"
2 changes: 1 addition & 1 deletion src/uipath_llm_client/__version__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__titile__ = "UiPath LLM Client"
__description__ = "A Python client for interacting with UiPath's LLM services."
__version__ = "1.1.1"
__version__ = "1.2.0"
11 changes: 5 additions & 6 deletions src/uipath_llm_client/settings/agenthub/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,18 @@ def __init__(
settings: AgentHub settings containing authentication credentials.
"""
self.settings = settings
self.access_token = self._get_access_token()

def _get_access_token(self) -> str:
def get_access_token(self, refresh: bool = False) -> str:
"""Retrieve or refresh the access token."""
self.settings.authenticate()
assert self.settings.access_token is not None
if refresh:
self.settings.authenticate(force=True)
return self.settings.access_token.get_secret_value()

def auth_flow(self, request: Request) -> Generator[Request, Response, None]:
"""HTTPX auth flow that handles token refresh on authentication failures."""
request.headers["Authorization"] = f"Bearer {self.access_token}"
request.headers["Authorization"] = f"Bearer {self.get_access_token()}"
response = yield request
if response.status_code == 401:
self.access_token = self._get_access_token()
request.headers["Authorization"] = f"Bearer {self.access_token}"
request.headers["Authorization"] = f"Bearer {self.get_access_token(refresh=True)}"
yield request
45 changes: 23 additions & 22 deletions src/uipath_llm_client/settings/agenthub/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def validate_environment(self) -> Self:
self.authenticate()
return self

def check_credentials(self) -> bool:
def credentials_available(self) -> bool:
"""Check if all required credentials are present."""
return (
self.access_token is not None
Expand All @@ -68,28 +68,29 @@ def check_credentials(self) -> bool:
and self.organization_id is not None
)

def authenticate(self) -> None:
def load_credentials(self) -> None:
"""Load credentials from environment variables."""
load_dotenv(override=True)
self.access_token = SecretStr(os.getenv("UIPATH_ACCESS_TOKEN", ""))
self.base_url = os.getenv("UIPATH_URL")
self.tenant_id = os.getenv("UIPATH_TENANT_ID")
self.organization_id = os.getenv("UIPATH_ORGANIZATION_ID")

def authenticate(self, force: bool = False) -> None:
"""Authenticate with UiPath using the configured credentials."""
if not self.check_credentials():
auth_service = AuthService(
environment=self.environment,
force=True,
client_id=self.client_id.get_secret_value() if self.client_id is not None else None,
client_secret=self.client_secret.get_secret_value()
if self.client_secret is not None
else None,
base_url=self.base_url,
tenant=self.tenant_id,
scope=self.client_scope,
)
auth_service.authenticate()
load_dotenv(override=True)
self.access_token = SecretStr(os.getenv("UIPATH_ACCESS_TOKEN", ""))
self.base_url = os.getenv("UIPATH_URL")
self.tenant_id = os.getenv("UIPATH_TENANT_ID")
self.organization_id = os.getenv("UIPATH_ORGANIZATION_ID")
if not self.check_credentials():
raise ValueError("Could not authenticate with UiPath")
auth_service = AuthService(
environment=self.environment,
force=force or not self.credentials_available(),
client_id=self.client_id.get_secret_value() if self.client_id is not None else None,
client_secret=self.client_secret.get_secret_value()
if self.client_secret is not None
else None,
base_url=self.base_url,
tenant=self.tenant_id,
scope=self.client_scope,
)
auth_service.authenticate()
self.load_credentials()

@override
def build_base_url(
Expand Down
16 changes: 7 additions & 9 deletions tests/core/test_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,12 +467,12 @@ def test_build_auth_pipeline_returns_auth(self, agenthub_env_vars):
auth = settings.build_auth_pipeline()
assert isinstance(auth, Auth)

def test_check_credentials(self, agenthub_env_vars):
"""Test check_credentials returns True when all credentials present."""
def test_credentials_available(self, agenthub_env_vars):
"""Test credentials_available returns True when all credentials present."""
with patch.dict(os.environ, agenthub_env_vars, clear=True):
with patch("uipath_llm_client.settings.agenthub.settings.AuthService"):
settings = AgentHubSettings()
assert settings.check_credentials() is True
assert settings.credentials_available() is True


# ============================================================================
Expand Down Expand Up @@ -505,8 +505,8 @@ def test_auth_flow_refreshes_on_401(self, agenthub_env_vars):
with patch("uipath_llm_client.settings.agenthub.settings.AuthService"):
settings = AgentHubSettings()

# Mock _get_access_token to return a new token on refresh
with patch.object(AgentHubAuth, "_get_access_token", return_value="initial-token"):
# Mock get_access_token to return a new token on refresh
with patch.object(AgentHubAuth, "get_access_token", return_value="initial-token"):
auth = AgentHubAuth(settings=settings)

request = Request("GET", "https://example.com")
Expand All @@ -520,10 +520,8 @@ def test_auth_flow_refreshes_on_401(self, agenthub_env_vars):
mock_response = MagicMock(spec=Response)
mock_response.status_code = 401

# Mock the _get_access_token method to return a new token
with patch.object(
AgentHubAuth, "_get_access_token", return_value="refreshed-token"
):
# Mock the get_access_token method to return a new token
with patch.object(AgentHubAuth, "get_access_token", return_value="refreshed-token"):
try:
retry_request = flow.send(mock_response)
assert retry_request.headers["Authorization"] == "Bearer refreshed-token"
Expand Down
Loading