smooth-py 0.3.1.dev20251027__py3-none-any.whl → 0.3.1.dev20251028__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of smooth-py might be problematic. Click here for more details.
- smooth/__init__.py +64 -9
- {smooth_py-0.3.1.dev20251027.dist-info → smooth_py-0.3.1.dev20251028.dist-info}/METADATA +1 -1
- smooth_py-0.3.1.dev20251028.dist-info/RECORD +6 -0
- smooth_py-0.3.1.dev20251027.dist-info/RECORD +0 -6
- {smooth_py-0.3.1.dev20251027.dist-info → smooth_py-0.3.1.dev20251028.dist-info}/WHEEL +0 -0
smooth/__init__.py
CHANGED
|
@@ -89,6 +89,7 @@ class TaskResponse(BaseModel):
|
|
|
89
89
|
device: Literal["desktop", "mobile"] | None = Field(default=None, description="The device type used for the task.")
|
|
90
90
|
live_url: str | None = Field(default=None, description="The URL to view and interact with the task execution.")
|
|
91
91
|
recording_url: str | None = Field(default=None, description="The URL to view the task recording.")
|
|
92
|
+
downloads_url: str | None = Field(default=None, description="The URL of the archive containing the downloaded files.")
|
|
92
93
|
created_at: int | None = Field(default=None, description="The timestamp when the task was created.")
|
|
93
94
|
|
|
94
95
|
|
|
@@ -111,7 +112,7 @@ class TaskRequest(BaseModel):
|
|
|
111
112
|
files: list[str] | None = Field(default=None, description="A list of file ids to pass to the agent.")
|
|
112
113
|
agent: Literal["smooth", "smooth-lite"] = Field(default="smooth", description="The agent to use for the task.")
|
|
113
114
|
max_steps: int = Field(default=32, ge=2, le=128, description="Maximum number of steps the agent can take (min 2, max 128).")
|
|
114
|
-
device: Literal["desktop", "mobile"] = Field(default="
|
|
115
|
+
device: Literal["desktop", "mobile"] = Field(default="desktop", description="Device type for the task. Default is desktop.")
|
|
115
116
|
allowed_urls: list[str] | None = Field(
|
|
116
117
|
default=None,
|
|
117
118
|
description=(
|
|
@@ -148,6 +149,7 @@ class TaskRequest(BaseModel):
|
|
|
148
149
|
" - `password` (optional): Password to decrypt the certificate file."
|
|
149
150
|
),
|
|
150
151
|
)
|
|
152
|
+
use_adblock: bool | None = Field(default=True, description="Enable adblock for the browser session. Default is True.")
|
|
151
153
|
experimental_features: dict[str, Any] | None = Field(
|
|
152
154
|
default=None, description="Experimental features to enable for the task."
|
|
153
155
|
)
|
|
@@ -464,6 +466,28 @@ class TaskHandle:
|
|
|
464
466
|
time.sleep(1)
|
|
465
467
|
raise TimeoutError(f"Recording URL not available for task {self.id()}.")
|
|
466
468
|
|
|
469
|
+
def downloads_url(self, timeout: int | None = None) -> str:
|
|
470
|
+
"""Returns the downloads URL for the task."""
|
|
471
|
+
if self._task_response and self._task_response.downloads_url is not None:
|
|
472
|
+
return self._task_response.downloads_url
|
|
473
|
+
|
|
474
|
+
start_time = time.time()
|
|
475
|
+
while timeout is None or (time.time() - start_time) < timeout:
|
|
476
|
+
task_response = self._client._get_task(self.id(), query_params={"downloads": "true"})
|
|
477
|
+
self._task_response = task_response
|
|
478
|
+
if task_response.downloads_url is not None:
|
|
479
|
+
if not task_response.downloads_url:
|
|
480
|
+
raise ApiError(
|
|
481
|
+
status_code=404,
|
|
482
|
+
detail=(
|
|
483
|
+
f"Downloads URL not available for task {self.id()}."
|
|
484
|
+
" Make sure the task downloaded files during its execution."
|
|
485
|
+
)
|
|
486
|
+
)
|
|
487
|
+
return task_response.downloads_url
|
|
488
|
+
time.sleep(1)
|
|
489
|
+
raise TimeoutError(f"Downloads URL not available for task {self.id()}.")
|
|
490
|
+
|
|
467
491
|
|
|
468
492
|
class SmoothClient(BaseClient):
|
|
469
493
|
"""A synchronous client for the API."""
|
|
@@ -490,20 +514,21 @@ class SmoothClient(BaseClient):
|
|
|
490
514
|
def _submit_task(self, payload: TaskRequest) -> TaskResponse:
|
|
491
515
|
"""Submits a task to be run."""
|
|
492
516
|
try:
|
|
493
|
-
response = self._session.post(f"{self.base_url}/task", json=payload.model_dump(
|
|
517
|
+
response = self._session.post(f"{self.base_url}/task", json=payload.model_dump())
|
|
494
518
|
data = self._handle_response(response)
|
|
495
519
|
return TaskResponse(**data["r"])
|
|
496
520
|
except requests.exceptions.RequestException as e:
|
|
497
521
|
logger.error(f"Request failed: {e}")
|
|
498
522
|
raise ApiError(status_code=0, detail=f"Request failed: {str(e)}") from None
|
|
499
523
|
|
|
500
|
-
def _get_task(self, task_id: str) -> TaskResponse:
|
|
524
|
+
def _get_task(self, task_id: str, query_params: dict[str, Any] | None = None) -> TaskResponse:
|
|
501
525
|
"""Retrieves the status and result of a task."""
|
|
502
526
|
if not task_id:
|
|
503
527
|
raise ValueError("Task ID cannot be empty.")
|
|
504
528
|
|
|
505
529
|
try:
|
|
506
|
-
|
|
530
|
+
url = f"{self.base_url}/task/{task_id}"
|
|
531
|
+
response = self._session.get(url, params=query_params)
|
|
507
532
|
data = self._handle_response(response)
|
|
508
533
|
return TaskResponse(**data["r"])
|
|
509
534
|
except requests.exceptions.RequestException as e:
|
|
@@ -542,6 +567,7 @@ class SmoothClient(BaseClient):
|
|
|
542
567
|
proxy_username: str | None = None,
|
|
543
568
|
proxy_password: str | None = None,
|
|
544
569
|
certificates: list[Certificate] | None = None,
|
|
570
|
+
use_adblock: bool | None = True,
|
|
545
571
|
experimental_features: dict[str, Any] | None = None,
|
|
546
572
|
) -> TaskHandle:
|
|
547
573
|
"""Runs a task and returns a handle to the task.
|
|
@@ -572,6 +598,7 @@ class SmoothClient(BaseClient):
|
|
|
572
598
|
Each certificate is a dictionary with the following fields:
|
|
573
599
|
- `file` (required): p12 file object to be uploaded (e.g., open("cert.p12", "rb")).
|
|
574
600
|
- `password` (optional): Password to decrypt the certificate file, if password-protected.
|
|
601
|
+
use_adblock: Enable adblock for the browser session. Default is True.
|
|
575
602
|
experimental_features: Experimental features to enable for the task.
|
|
576
603
|
|
|
577
604
|
Returns:
|
|
@@ -598,6 +625,7 @@ class SmoothClient(BaseClient):
|
|
|
598
625
|
proxy_username=proxy_username,
|
|
599
626
|
proxy_password=proxy_password,
|
|
600
627
|
certificates=_process_certificates(certificates),
|
|
628
|
+
use_adblock=use_adblock,
|
|
601
629
|
experimental_features=experimental_features,
|
|
602
630
|
)
|
|
603
631
|
initial_response = self._submit_task(payload)
|
|
@@ -623,7 +651,7 @@ class SmoothClient(BaseClient):
|
|
|
623
651
|
try:
|
|
624
652
|
response = self._session.post(
|
|
625
653
|
f"{self.base_url}/browser/session",
|
|
626
|
-
json=BrowserSessionRequest(profile_id=profile_id or session_id, live_view=live_view).model_dump(
|
|
654
|
+
json=BrowserSessionRequest(profile_id=profile_id or session_id, live_view=live_view).model_dump(),
|
|
627
655
|
)
|
|
628
656
|
data = self._handle_response(response)
|
|
629
657
|
return BrowserSessionHandle(browser_session=BrowserSessionResponse(**data["r"]))
|
|
@@ -797,6 +825,29 @@ class AsyncTaskHandle:
|
|
|
797
825
|
|
|
798
826
|
raise TimeoutError(f"Recording URL not available for task {self.id()}.")
|
|
799
827
|
|
|
828
|
+
async def downloads_url(self, timeout: int | None = None) -> str:
|
|
829
|
+
"""Returns the downloads URL for the task."""
|
|
830
|
+
if self._task_response and self._task_response.downloads_url is not None:
|
|
831
|
+
return self._task_response.downloads_url
|
|
832
|
+
|
|
833
|
+
start_time = time.time()
|
|
834
|
+
while timeout is None or (time.time() - start_time) < timeout:
|
|
835
|
+
task_response = await self._client._get_task(self.id(), query_params={"downloads": "true"})
|
|
836
|
+
self._task_response = task_response
|
|
837
|
+
if task_response.downloads_url is not None:
|
|
838
|
+
if not task_response.downloads_url:
|
|
839
|
+
raise ApiError(
|
|
840
|
+
status_code=404,
|
|
841
|
+
detail=(
|
|
842
|
+
f"Downloads URL not available for task {self.id()}."
|
|
843
|
+
" Make sure the task downloaded files during its execution."
|
|
844
|
+
)
|
|
845
|
+
)
|
|
846
|
+
return task_response.downloads_url
|
|
847
|
+
await asyncio.sleep(1)
|
|
848
|
+
|
|
849
|
+
raise TimeoutError(f"Downloads URL not available for task {self.id()}.")
|
|
850
|
+
|
|
800
851
|
|
|
801
852
|
class SmoothAsyncClient(BaseClient):
|
|
802
853
|
"""An asynchronous client for the API."""
|
|
@@ -817,20 +868,21 @@ class SmoothAsyncClient(BaseClient):
|
|
|
817
868
|
async def _submit_task(self, payload: TaskRequest) -> TaskResponse:
|
|
818
869
|
"""Submits a task to be run asynchronously."""
|
|
819
870
|
try:
|
|
820
|
-
response = await self._client.post(f"{self.base_url}/task", json=payload.model_dump(
|
|
871
|
+
response = await self._client.post(f"{self.base_url}/task", json=payload.model_dump())
|
|
821
872
|
data = self._handle_response(response)
|
|
822
873
|
return TaskResponse(**data["r"])
|
|
823
874
|
except httpx.RequestError as e:
|
|
824
875
|
logger.error(f"Request failed: {e}")
|
|
825
876
|
raise ApiError(status_code=0, detail=f"Request failed: {str(e)}") from None
|
|
826
877
|
|
|
827
|
-
async def _get_task(self, task_id: str) -> TaskResponse:
|
|
878
|
+
async def _get_task(self, task_id: str, query_params: dict[str, Any] | None = None) -> TaskResponse:
|
|
828
879
|
"""Retrieves the status and result of a task asynchronously."""
|
|
829
880
|
if not task_id:
|
|
830
881
|
raise ValueError("Task ID cannot be empty.")
|
|
831
882
|
|
|
832
883
|
try:
|
|
833
|
-
|
|
884
|
+
url = f"{self.base_url}/task/{task_id}"
|
|
885
|
+
response = await self._client.get(url, params=query_params)
|
|
834
886
|
data = self._handle_response(response)
|
|
835
887
|
return TaskResponse(**data["r"])
|
|
836
888
|
except httpx.RequestError as e:
|
|
@@ -869,6 +921,7 @@ class SmoothAsyncClient(BaseClient):
|
|
|
869
921
|
proxy_username: str | None = None,
|
|
870
922
|
proxy_password: str | None = None,
|
|
871
923
|
certificates: list[Certificate] | None = None,
|
|
924
|
+
use_adblock: bool | None = True,
|
|
872
925
|
experimental_features: dict[str, Any] | None = None,
|
|
873
926
|
) -> AsyncTaskHandle:
|
|
874
927
|
"""Runs a task and returns a handle to the task asynchronously.
|
|
@@ -899,6 +952,7 @@ class SmoothAsyncClient(BaseClient):
|
|
|
899
952
|
Each certificate is a dictionary with the following fields:
|
|
900
953
|
- `file` (required): p12 file object to be uploaded (e.g., open("cert.p12", "rb")).
|
|
901
954
|
- `password` (optional): Password to decrypt the certificate file.
|
|
955
|
+
use_adblock: Enable adblock for the browser session. Default is True.
|
|
902
956
|
experimental_features: Experimental features to enable for the task.
|
|
903
957
|
|
|
904
958
|
Returns:
|
|
@@ -925,6 +979,7 @@ class SmoothAsyncClient(BaseClient):
|
|
|
925
979
|
proxy_username=proxy_username,
|
|
926
980
|
proxy_password=proxy_password,
|
|
927
981
|
certificates=_process_certificates(certificates),
|
|
982
|
+
use_adblock=use_adblock,
|
|
928
983
|
experimental_features=experimental_features,
|
|
929
984
|
)
|
|
930
985
|
|
|
@@ -950,7 +1005,7 @@ class SmoothAsyncClient(BaseClient):
|
|
|
950
1005
|
try:
|
|
951
1006
|
response = await self._client.post(
|
|
952
1007
|
f"{self.base_url}/browser/session",
|
|
953
|
-
json=BrowserSessionRequest(profile_id=profile_id or session_id, live_view=live_view).model_dump(
|
|
1008
|
+
json=BrowserSessionRequest(profile_id=profile_id or session_id, live_view=live_view).model_dump(),
|
|
954
1009
|
)
|
|
955
1010
|
data = self._handle_response(response)
|
|
956
1011
|
return BrowserSessionHandle(browser_session=BrowserSessionResponse(**data["r"]))
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
smooth/__init__.py,sha256=HpLvLf7rhN4w4CT06ZpW8KHE-GNP6x1LrS_NhfAetro,44637
|
|
2
|
+
smooth/mcp/__init__.py,sha256=0aJVFi2a8Ah3-5xtgyZ5UMbaaJsBWu2T8QLWoFQITk8,219
|
|
3
|
+
smooth/mcp/server.py,sha256=9SymTD4NOGTMN8P-LNGlvYNvv81yCIZfZeeuhEcAc6s,20068
|
|
4
|
+
smooth_py-0.3.1.dev20251028.dist-info/METADATA,sha256=xn8kj9HoWdYjNyJuGBqUTvJh4DjlwuejsStj58Reo3c,7529
|
|
5
|
+
smooth_py-0.3.1.dev20251028.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
6
|
+
smooth_py-0.3.1.dev20251028.dist-info/RECORD,,
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
smooth/__init__.py,sha256=5ebKGse4vLyqBRFRcCZX2qTB2LAkKf--LgBNeF8rmek,42091
|
|
2
|
-
smooth/mcp/__init__.py,sha256=0aJVFi2a8Ah3-5xtgyZ5UMbaaJsBWu2T8QLWoFQITk8,219
|
|
3
|
-
smooth/mcp/server.py,sha256=9SymTD4NOGTMN8P-LNGlvYNvv81yCIZfZeeuhEcAc6s,20068
|
|
4
|
-
smooth_py-0.3.1.dev20251027.dist-info/METADATA,sha256=BeMJNBO-bSmV94LjZQJ8SjaCtUUuh7Pq8c7HpJv-N7k,7529
|
|
5
|
-
smooth_py-0.3.1.dev20251027.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
6
|
-
smooth_py-0.3.1.dev20251027.dist-info/RECORD,,
|
|
File without changes
|