Skip to content
Draft
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
27 changes: 27 additions & 0 deletions packages/notte-sdk/src/notte_sdk/endpoints/profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
ExecutionResponse,
ProfileCreateRequest,
ProfileCreateRequestDict,
ProfileDuplicateRequest,
ProfileDuplicateRequestDict,
ProfileListRequest,
ProfileListRequestDict,
ProfileResponse,
Expand All @@ -28,6 +30,7 @@ class ProfilesClient(BaseClient):
CREATE_PROFILE = "create"
GET_PROFILE = "{profile_id}"
DELETE_PROFILE = "{profile_id}"
DUPLICATE_PROFILE = "{profile_id}/duplicate"
LIST_PROFILES = ""

def __init__(
Expand Down Expand Up @@ -80,6 +83,15 @@ def _delete_profile_endpoint(profile_id: str) -> NotteEndpoint[ExecutionResponse
method="DELETE",
)

@staticmethod
def _duplicate_profile_endpoint(profile_id: str) -> NotteEndpoint[ProfileResponse]:
"""Returns a NotteEndpoint configured for duplicating a profile."""
return NotteEndpoint(
path=ProfilesClient.DUPLICATE_PROFILE.format(profile_id=profile_id),
response=ProfileResponse,
method="POST",
)

@staticmethod
def _list_profiles_endpoint() -> NotteEndpoint[ProfileResponse]:
"""Returns a NotteEndpoint configured for listing profiles."""
Expand Down Expand Up @@ -139,6 +151,21 @@ def delete(self, profile_id: str) -> bool:
result = self.request(ProfilesClient._delete_profile_endpoint(profile_id))
return result.success

@track_usage("cloud.profile.duplicate")
def duplicate(self, profile_id: str, **data: Unpack[ProfileDuplicateRequestDict]) -> ProfileResponse:
"""Duplicate a profile and its last persisted browser state.

Args:
profile_id: Source profile ID
**data: Optional destination name. The source name is reused when omitted.

Returns:
ProfileResponse: The newly created profile
"""
request = ProfileDuplicateRequest.model_validate(data)
endpoint = ProfilesClient._duplicate_profile_endpoint(profile_id).with_request(request)
return self.request(endpoint)

@track_usage("cloud.profile.list")
def list(self, **data: Unpack[ProfileListRequestDict]) -> Sequence[ProfileResponse]:
"""List all profiles for the authenticated user.
Expand Down
13 changes: 13 additions & 0 deletions packages/notte-sdk/src/notte_sdk/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1380,6 +1380,19 @@ class ProfileCreateRequest(SdkRequest):
name: Annotated[str | None, Field(description="Name of the profile")] = None


class ProfileDuplicateRequestDict(TypedDict, total=False):
"""Request dictionary for duplicating a profile."""

name: str


class ProfileDuplicateRequest(SdkRequest):
name: Annotated[
str | None,
Field(description="Optional name for the duplicate; defaults to the source profile name"),
] = None


class ProfileResponse(SdkResponse):
profile_id: Annotated[str, Field(description="Profile ID (format: notte-profile-{16 hex chars})")]
name: Annotated[str | None, Field(description="Profile name")]
Expand Down
32 changes: 32 additions & 0 deletions tests/sdk/test_profile_endpoint_paths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import datetime as dt
from unittest.mock import patch

from notte_sdk import NotteClient
from notte_sdk.endpoints.profiles import ProfilesClient
from notte_sdk.types import ProfileDuplicateRequest, ProfileResponse


def test_profile_duplicate_endpoint_uses_source_id() -> None:
endpoint = ProfilesClient._duplicate_profile_endpoint("notte-profile-source")

assert endpoint.method == "POST"
assert endpoint.path == "notte-profile-source/duplicate"
assert endpoint.response is ProfileResponse


def test_profile_duplicate_sends_optional_destination_name() -> None:
client = NotteClient(api_key="test-api-key", server_url="https://api.notte.cc")
expected = ProfileResponse(
profile_id="notte-profile-copy",
name="Copied login",
created_at=dt.datetime(2026, 7, 27, tzinfo=dt.UTC),
updated_at=dt.datetime(2026, 7, 27, tzinfo=dt.UTC),
)

with patch.object(client.profiles, "request", return_value=expected) as request:
result = client.profiles.duplicate("notte-profile-source", name="Copied login")

assert result is expected
endpoint = request.call_args.args[0]
assert endpoint.path == "notte-profile-source/duplicate"
assert endpoint.request == ProfileDuplicateRequest(name="Copied login")
Loading