smooth-py 0.2.1.post0.dev20250911__tar.gz → 0.2.3__tar.gz
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_py-0.2.1.post0.dev20250911 → smooth_py-0.2.3}/PKG-INFO +4 -3
- {smooth_py-0.2.1.post0.dev20250911 → smooth_py-0.2.3}/pyproject.toml +5 -4
- {smooth_py-0.2.1.post0.dev20250911 → smooth_py-0.2.3}/src/smooth/__init__.py +81 -6
- {smooth_py-0.2.1.post0.dev20250911 → smooth_py-0.2.3}/README.md +0 -0
- {smooth_py-0.2.1.post0.dev20250911 → smooth_py-0.2.3}/src/smooth/mcp/__init__.py +0 -0
- {smooth_py-0.2.1.post0.dev20250911 → smooth_py-0.2.3}/src/smooth/mcp/server.py +0 -0
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: smooth-py
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary:
|
|
5
5
|
Author: Luca Pinchetti
|
|
6
6
|
Author-email: luca@circlemind.co
|
|
7
|
-
Requires-Python: >=3.10
|
|
7
|
+
Requires-Python: >=3.10,<4.0
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: Programming Language :: Python :: 3.10
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.11
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.13
|
|
13
13
|
Provides-Extra: mcp
|
|
14
|
-
Requires-Dist: fastmcp (>=2.
|
|
14
|
+
Requires-Dist: fastmcp (>=2.12.0,<3.0.0) ; extra == "mcp"
|
|
15
15
|
Requires-Dist: httpx (>=0.28.1,<0.29.0)
|
|
16
16
|
Requires-Dist: pydantic (>=2.11.7,<3.0.0)
|
|
17
|
+
Requires-Dist: requests (>=2.32.5,<3.0.0)
|
|
17
18
|
Description-Content-Type: text/markdown
|
|
18
19
|
|
|
19
20
|
# Smooth Python SDK
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "smooth-py"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.3"
|
|
4
4
|
description = ""
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "Luca Pinchetti",email = "luca@circlemind.co"}
|
|
7
7
|
]
|
|
8
8
|
readme = "README.md"
|
|
9
|
-
requires-python = ">=3.10"
|
|
9
|
+
requires-python = ">=3.10,<4.0"
|
|
10
10
|
dependencies = [
|
|
11
11
|
"pydantic (>=2.11.7,<3.0.0)",
|
|
12
|
-
"httpx (>=0.28.1,<0.29.0)"
|
|
12
|
+
"httpx (>=0.28.1,<0.29.0)",
|
|
13
|
+
"requests (>=2.32.5,<3.0.0)"
|
|
13
14
|
]
|
|
14
15
|
|
|
15
16
|
[project.optional-dependencies]
|
|
16
17
|
mcp = [
|
|
17
|
-
"fastmcp (>=2.
|
|
18
|
+
"fastmcp (>=2.12.0,<3.0.0)"
|
|
18
19
|
]
|
|
19
20
|
|
|
20
21
|
[tool.poetry]
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Smooth python SDK."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import io
|
|
4
5
|
import logging
|
|
5
6
|
import os
|
|
6
7
|
import time
|
|
@@ -53,9 +54,14 @@ class TaskRequest(BaseModel):
|
|
|
53
54
|
)
|
|
54
55
|
url: str | None = Field(
|
|
55
56
|
default=None,
|
|
56
|
-
description="
|
|
57
|
+
description="The starting URL for the task. If not provided, the agent will infer it from the task.",
|
|
58
|
+
)
|
|
59
|
+
metadata: dict[str, str | int | float | bool] | None = Field(
|
|
60
|
+
default=None, description="A dictionary containing variables or parameters that will be passed to the agent."
|
|
61
|
+
)
|
|
62
|
+
files: list[str] | None = Field(
|
|
63
|
+
default=None, description="A dictionary of file ids to pass to the agent."
|
|
57
64
|
)
|
|
58
|
-
metadata: dict[str, str | int | float | bool] | None = Field(default=None, description="Optional metadata for the task.")
|
|
59
65
|
agent: Literal["smooth"] = Field(default="smooth", description="The agent to use for the task.")
|
|
60
66
|
max_steps: int = Field(default=32, ge=2, le=128, description="Maximum number of steps the agent can take (min 2, max 128).")
|
|
61
67
|
device: Literal["desktop", "mobile"] = Field(default="mobile", description="Device type for the task. Default is mobile.")
|
|
@@ -98,6 +104,11 @@ class BrowserSessionsResponse(BaseModel):
|
|
|
98
104
|
session_ids: list[str] = Field(description="The IDs of the browser sessions.")
|
|
99
105
|
|
|
100
106
|
|
|
107
|
+
class UploadFileResponse(BaseModel):
|
|
108
|
+
"""Response model for listing browser sessions."""
|
|
109
|
+
id: str = Field(description="The ID assigned to the uploaded file.")
|
|
110
|
+
|
|
111
|
+
|
|
101
112
|
# --- Exception Handling ---
|
|
102
113
|
|
|
103
114
|
|
|
@@ -140,7 +151,6 @@ class BaseClient:
|
|
|
140
151
|
self.base_url = f"{base_url.rstrip('/')}/{api_version}"
|
|
141
152
|
self.headers = {
|
|
142
153
|
"apikey": self.api_key,
|
|
143
|
-
"Content-Type": "application/json",
|
|
144
154
|
"User-Agent": "smooth-python-sdk/0.2.0",
|
|
145
155
|
}
|
|
146
156
|
|
|
@@ -279,6 +289,19 @@ class SmoothClient(BaseClient):
|
|
|
279
289
|
logger.error(f"Request failed: {e}")
|
|
280
290
|
raise ApiError(status_code=0, detail=f"Request failed: {str(e)}") from None
|
|
281
291
|
|
|
292
|
+
def _upload_file(self, file: io.IOBase, name: str) -> UploadFileResponse:
|
|
293
|
+
"""Uploads a file and returns the assigned file id."""
|
|
294
|
+
try:
|
|
295
|
+
files = {
|
|
296
|
+
"file": (name, file)
|
|
297
|
+
}
|
|
298
|
+
response = self._session.post(f"{self.base_url}/upload_file", files=files)
|
|
299
|
+
data = self._handle_response(response)
|
|
300
|
+
return UploadFileResponse(**data["r"])
|
|
301
|
+
except requests.exceptions.RequestException as e:
|
|
302
|
+
logger.error(f"Request failed: {e}")
|
|
303
|
+
raise ApiError(status_code=0, detail=f"Request failed: {str(e)}") from None
|
|
304
|
+
|
|
282
305
|
def _get_task(self, task_id: str) -> TaskResponse:
|
|
283
306
|
"""Retrieves the status and result of a task."""
|
|
284
307
|
if not task_id:
|
|
@@ -298,6 +321,7 @@ class SmoothClient(BaseClient):
|
|
|
298
321
|
response_model: dict[str, Any] | Type[BaseModel] | None = None,
|
|
299
322
|
url: str | None = None,
|
|
300
323
|
metadata: dict[str, str | int | float | bool] | None = None,
|
|
324
|
+
files: list[str] | None = None,
|
|
301
325
|
agent: Literal["smooth"] = "smooth",
|
|
302
326
|
max_steps: int = 32,
|
|
303
327
|
device: Literal["desktop", "mobile"] = "mobile",
|
|
@@ -317,7 +341,8 @@ class SmoothClient(BaseClient):
|
|
|
317
341
|
task: The task to run.
|
|
318
342
|
response_model: If provided, the schema describing the desired output structure.
|
|
319
343
|
url: The starting URL for the task. If not provided, the agent will infer it from the task.
|
|
320
|
-
metadata:
|
|
344
|
+
metadata: A dictionary containing variables or parameters that will be passed to the agent.
|
|
345
|
+
files: A dictionary of file names to their ids. These files will be passed to the agent.
|
|
321
346
|
agent: The agent to use for the task.
|
|
322
347
|
max_steps: Maximum number of steps the agent can take (max 64).
|
|
323
348
|
device: Device type for the task. Default is mobile.
|
|
@@ -339,6 +364,7 @@ class SmoothClient(BaseClient):
|
|
|
339
364
|
response_model=response_model.model_json_schema() if issubclass(response_model, BaseModel) else response_model,
|
|
340
365
|
url=url,
|
|
341
366
|
metadata=metadata,
|
|
367
|
+
files=files,
|
|
342
368
|
agent=agent,
|
|
343
369
|
max_steps=max_steps,
|
|
344
370
|
device=device,
|
|
@@ -399,10 +425,26 @@ class SmoothClient(BaseClient):
|
|
|
399
425
|
try:
|
|
400
426
|
response = self._session.delete(f"{self.base_url}/browser/session/{session_id}")
|
|
401
427
|
self._handle_response(response)
|
|
402
|
-
except
|
|
428
|
+
except requests.exceptions.RequestException as e:
|
|
403
429
|
logger.error(f"Request failed: {e}")
|
|
404
430
|
raise ApiError(status_code=0, detail=f"Request failed: {str(e)}") from None
|
|
405
431
|
|
|
432
|
+
def upload_file(self, file: io.IOBase, name: str) -> UploadFileResponse:
|
|
433
|
+
"""Upload a file and return the file ID.
|
|
434
|
+
|
|
435
|
+
Args:
|
|
436
|
+
file: File object to be uploaded.
|
|
437
|
+
name: The name to assign to the uploaded file.
|
|
438
|
+
|
|
439
|
+
Returns:
|
|
440
|
+
The file ID assigned to the uploaded file.
|
|
441
|
+
|
|
442
|
+
Raises:
|
|
443
|
+
ValueError: If the file doesn't exist or can't be read.
|
|
444
|
+
ApiError: If the API request fails.
|
|
445
|
+
"""
|
|
446
|
+
return self._upload_file(file=file, name=name)
|
|
447
|
+
|
|
406
448
|
|
|
407
449
|
# --- Asynchronous Client ---
|
|
408
450
|
|
|
@@ -497,6 +539,19 @@ class SmoothAsyncClient(BaseClient):
|
|
|
497
539
|
logger.error(f"Request failed: {e}")
|
|
498
540
|
raise ApiError(status_code=0, detail=f"Request failed: {str(e)}") from None
|
|
499
541
|
|
|
542
|
+
async def _upload_file(self, file: io.IOBase, name: str) -> UploadFileResponse:
|
|
543
|
+
"""Uploads a file and returns the assigned file id."""
|
|
544
|
+
try:
|
|
545
|
+
files = {
|
|
546
|
+
"file": (name, file)
|
|
547
|
+
}
|
|
548
|
+
response = await self._client.post(f"{self.base_url}/upload_file", files=files)
|
|
549
|
+
data = self._handle_response(response)
|
|
550
|
+
return UploadFileResponse(**data["r"])
|
|
551
|
+
except httpx.RequestError as e:
|
|
552
|
+
logger.error(f"Request failed: {e}")
|
|
553
|
+
raise ApiError(status_code=0, detail=f"Request failed: {str(e)}") from None
|
|
554
|
+
|
|
500
555
|
async def _get_task(self, task_id: str) -> TaskResponse:
|
|
501
556
|
"""Retrieves the status and result of a task asynchronously."""
|
|
502
557
|
if not task_id:
|
|
@@ -516,6 +571,7 @@ class SmoothAsyncClient(BaseClient):
|
|
|
516
571
|
response_model: dict[str, Any] | Type[BaseModel] | None = None,
|
|
517
572
|
url: str | None = None,
|
|
518
573
|
metadata: dict[str, str | int | float | bool] | None = None,
|
|
574
|
+
files: list[str] | None = None,
|
|
519
575
|
agent: Literal["smooth"] = "smooth",
|
|
520
576
|
max_steps: int = 32,
|
|
521
577
|
device: Literal["desktop", "mobile"] = "mobile",
|
|
@@ -535,7 +591,8 @@ class SmoothAsyncClient(BaseClient):
|
|
|
535
591
|
task: The task to run.
|
|
536
592
|
response_model: If provided, the schema describing the desired output structure.
|
|
537
593
|
url: The starting URL for the task. If not provided, the agent will infer it from the task.
|
|
538
|
-
metadata:
|
|
594
|
+
metadata: A dictionary containing variables or parameters that will be passed to the agent.
|
|
595
|
+
files: A dictionary of file names to their url or base64-encoded content to be used by the agent.
|
|
539
596
|
agent: The agent to use for the task.
|
|
540
597
|
max_steps: Maximum number of steps the agent can take (max 64).
|
|
541
598
|
device: Device type for the task. Default is mobile.
|
|
@@ -559,6 +616,7 @@ class SmoothAsyncClient(BaseClient):
|
|
|
559
616
|
response_model=response_model.model_json_schema() if issubclass(response_model, BaseModel) else response_model,
|
|
560
617
|
url=url,
|
|
561
618
|
metadata=metadata,
|
|
619
|
+
files=files,
|
|
562
620
|
agent=agent,
|
|
563
621
|
max_steps=max_steps,
|
|
564
622
|
device=device,
|
|
@@ -623,6 +681,22 @@ class SmoothAsyncClient(BaseClient):
|
|
|
623
681
|
logger.error(f"Request failed: {e}")
|
|
624
682
|
raise ApiError(status_code=0, detail=f"Request failed: {str(e)}") from None
|
|
625
683
|
|
|
684
|
+
async def upload_file(self, file: io.IOBase, name: str) -> UploadFileResponse:
|
|
685
|
+
"""Upload a file and return the file ID.
|
|
686
|
+
|
|
687
|
+
Args:
|
|
688
|
+
file: File object to be uploaded.
|
|
689
|
+
name: The name to assign to the uploaded file.
|
|
690
|
+
|
|
691
|
+
Returns:
|
|
692
|
+
The file ID assigned to the uploaded file.
|
|
693
|
+
|
|
694
|
+
Raises:
|
|
695
|
+
ValueError: If the file doesn't exist or can't be read.
|
|
696
|
+
ApiError: If the API request fails.
|
|
697
|
+
"""
|
|
698
|
+
return await self._upload_file(file=file, name=name)
|
|
699
|
+
|
|
626
700
|
async def close(self):
|
|
627
701
|
"""Closes the async client session."""
|
|
628
702
|
await self._client.aclose()
|
|
@@ -640,6 +714,7 @@ __all__ = [
|
|
|
640
714
|
"BrowserSessionRequest",
|
|
641
715
|
"BrowserSessionResponse",
|
|
642
716
|
"BrowserSessionsResponse",
|
|
717
|
+
"UploadFileResponse",
|
|
643
718
|
"ApiError",
|
|
644
719
|
"TimeoutError",
|
|
645
720
|
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|