smooth-py 0.2.2.post0__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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: smooth-py
3
- Version: 0.2.2.post0
3
+ Version: 0.2.3
4
4
  Summary:
5
5
  Author: Luca Pinchetti
6
6
  Author-email: luca@circlemind.co
@@ -11,7 +11,7 @@ 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.0.0,<3.0.0) ; extra == "mcp"
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
17
  Requires-Dist: requests (>=2.32.5,<3.0.0)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "smooth-py"
3
- version = "0.2.2.post0"
3
+ version = "0.2.3"
4
4
  description = ""
5
5
  authors = [
6
6
  {name = "Luca Pinchetti",email = "luca@circlemind.co"}
@@ -15,7 +15,7 @@ dependencies = [
15
15
 
16
16
  [project.optional-dependencies]
17
17
  mcp = [
18
- "fastmcp (>=2.0.0,<3.0.0)"
18
+ "fastmcp (>=2.12.0,<3.0.0)"
19
19
  ]
20
20
 
21
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
@@ -58,9 +59,9 @@ class TaskRequest(BaseModel):
58
59
  metadata: dict[str, str | int | float | bool] | None = Field(
59
60
  default=None, description="A dictionary containing variables or parameters that will be passed to the agent."
60
61
  )
61
- files: dict[str, str] | None = Field(
62
- default=None, description="A dictionary of file names to their url or base64-encoded content to be used by the agent"
63
- ),
62
+ files: list[str] | None = Field(
63
+ default=None, description="A dictionary of file ids to pass to the agent."
64
+ )
64
65
  agent: Literal["smooth"] = Field(default="smooth", description="The agent to use for the task.")
65
66
  max_steps: int = Field(default=32, ge=2, le=128, description="Maximum number of steps the agent can take (min 2, max 128).")
66
67
  device: Literal["desktop", "mobile"] = Field(default="mobile", description="Device type for the task. Default is mobile.")
@@ -103,6 +104,11 @@ class BrowserSessionsResponse(BaseModel):
103
104
  session_ids: list[str] = Field(description="The IDs of the browser sessions.")
104
105
 
105
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
+
106
112
  # --- Exception Handling ---
107
113
 
108
114
 
@@ -145,7 +151,6 @@ class BaseClient:
145
151
  self.base_url = f"{base_url.rstrip('/')}/{api_version}"
146
152
  self.headers = {
147
153
  "apikey": self.api_key,
148
- "Content-Type": "application/json",
149
154
  "User-Agent": "smooth-python-sdk/0.2.0",
150
155
  }
151
156
 
@@ -284,6 +289,19 @@ class SmoothClient(BaseClient):
284
289
  logger.error(f"Request failed: {e}")
285
290
  raise ApiError(status_code=0, detail=f"Request failed: {str(e)}") from None
286
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
+
287
305
  def _get_task(self, task_id: str) -> TaskResponse:
288
306
  """Retrieves the status and result of a task."""
289
307
  if not task_id:
@@ -303,7 +321,7 @@ class SmoothClient(BaseClient):
303
321
  response_model: dict[str, Any] | Type[BaseModel] | None = None,
304
322
  url: str | None = None,
305
323
  metadata: dict[str, str | int | float | bool] | None = None,
306
- files: dict[str, str] | None = None,
324
+ files: list[str] | None = None,
307
325
  agent: Literal["smooth"] = "smooth",
308
326
  max_steps: int = 32,
309
327
  device: Literal["desktop", "mobile"] = "mobile",
@@ -324,7 +342,7 @@ class SmoothClient(BaseClient):
324
342
  response_model: If provided, the schema describing the desired output structure.
325
343
  url: The starting URL for the task. If not provided, the agent will infer it from the task.
326
344
  metadata: A dictionary containing variables or parameters that will be passed to the agent.
327
- files: A dictionary of file names to their url or base64-encoded content to be used by the agent.
345
+ files: A dictionary of file names to their ids. These files will be passed to the agent.
328
346
  agent: The agent to use for the task.
329
347
  max_steps: Maximum number of steps the agent can take (max 64).
330
348
  device: Device type for the task. Default is mobile.
@@ -407,10 +425,26 @@ class SmoothClient(BaseClient):
407
425
  try:
408
426
  response = self._session.delete(f"{self.base_url}/browser/session/{session_id}")
409
427
  self._handle_response(response)
410
- except httpx.RequestError as e:
428
+ except requests.exceptions.RequestException as e:
411
429
  logger.error(f"Request failed: {e}")
412
430
  raise ApiError(status_code=0, detail=f"Request failed: {str(e)}") from None
413
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
+
414
448
 
415
449
  # --- Asynchronous Client ---
416
450
 
@@ -505,6 +539,19 @@ class SmoothAsyncClient(BaseClient):
505
539
  logger.error(f"Request failed: {e}")
506
540
  raise ApiError(status_code=0, detail=f"Request failed: {str(e)}") from None
507
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
+
508
555
  async def _get_task(self, task_id: str) -> TaskResponse:
509
556
  """Retrieves the status and result of a task asynchronously."""
510
557
  if not task_id:
@@ -524,7 +571,7 @@ class SmoothAsyncClient(BaseClient):
524
571
  response_model: dict[str, Any] | Type[BaseModel] | None = None,
525
572
  url: str | None = None,
526
573
  metadata: dict[str, str | int | float | bool] | None = None,
527
- files: dict[str, str] | None = None,
574
+ files: list[str] | None = None,
528
575
  agent: Literal["smooth"] = "smooth",
529
576
  max_steps: int = 32,
530
577
  device: Literal["desktop", "mobile"] = "mobile",
@@ -634,6 +681,22 @@ class SmoothAsyncClient(BaseClient):
634
681
  logger.error(f"Request failed: {e}")
635
682
  raise ApiError(status_code=0, detail=f"Request failed: {str(e)}") from None
636
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
+
637
700
  async def close(self):
638
701
  """Closes the async client session."""
639
702
  await self._client.aclose()
@@ -651,6 +714,7 @@ __all__ = [
651
714
  "BrowserSessionRequest",
652
715
  "BrowserSessionResponse",
653
716
  "BrowserSessionsResponse",
717
+ "UploadFileResponse",
654
718
  "ApiError",
655
719
  "TimeoutError",
656
720
  ]
File without changes