tinyfish 0.2.2__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.
@@ -0,0 +1,182 @@
1
+ """Agent automation request/response types."""
2
+
3
+ from datetime import datetime
4
+ from enum import StrEnum
5
+
6
+ from pydantic import BaseModel, ConfigDict, Field
7
+
8
+ from tinyfish.runs.types import RunError, RunStatus
9
+
10
+ # ============================================================================
11
+ # Shared Types
12
+ # ============================================================================
13
+
14
+
15
+ class BrowserProfile(StrEnum):
16
+ """Browser profile for execution."""
17
+
18
+ LITE = "lite"
19
+ STEALTH = "stealth"
20
+
21
+
22
+ class ProxyCountryCode(StrEnum):
23
+ """ISO 3166-1 alpha-2 country code for proxy location."""
24
+
25
+ US = "US"
26
+ GB = "GB"
27
+ CA = "CA"
28
+ DE = "DE"
29
+ FR = "FR"
30
+ JP = "JP"
31
+ AU = "AU"
32
+
33
+
34
+ class ProxyConfig(BaseModel):
35
+ """Proxy configuration for browser automation."""
36
+
37
+ enabled: bool
38
+ """Enable proxy for this automation run."""
39
+ country_code: ProxyCountryCode | None = None
40
+ """ISO 3166-1 alpha-2 country code for proxy location."""
41
+
42
+
43
+ class EventType(StrEnum):
44
+ """SSE event type discriminator."""
45
+
46
+ STARTED = "STARTED"
47
+ STREAMING_URL = "STREAMING_URL"
48
+ PROGRESS = "PROGRESS"
49
+ HEARTBEAT = "HEARTBEAT"
50
+ COMPLETE = "COMPLETE"
51
+
52
+
53
+ # ============================================================================
54
+ # Response Types - Synchronous Run
55
+ # ============================================================================
56
+
57
+
58
+ class AgentRunResponse(BaseModel):
59
+ """
60
+ Response from synchronous automation execution.
61
+
62
+ Check status to determine success/failure:
63
+ - On success: result is populated, error is None
64
+ - On failure: result is None, error contains message
65
+
66
+ Example:
67
+ ```python
68
+ response = client.agent.run(
69
+ goal="Find the price of iPhone 15",
70
+ url="https://www.apple.com"
71
+ )
72
+ if response.status == "COMPLETED":
73
+ print(response.result)
74
+ else:
75
+ print(f"Failed: {response.error.message}")
76
+ ```
77
+ """
78
+
79
+ status: RunStatus = Field(..., description="Final status of the automation run")
80
+ run_id: str | None = Field(None, description="Unique identifier for the automation run")
81
+ result: dict[str, object] | None = Field(
82
+ None, description="Structured JSON result extracted from the automation. None if the run failed."
83
+ )
84
+ error: RunError | None = Field(None, description="Error details. None if the run succeeded.")
85
+ num_of_steps: int = Field(..., description="Number of steps taken during the automation")
86
+ started_at: datetime | None = Field(None, description="Timestamp when the run started")
87
+ finished_at: datetime | None = Field(None, description="Timestamp when the run finished")
88
+
89
+
90
+ # ============================================================================
91
+ # Response Types - Async Run
92
+ # ============================================================================
93
+
94
+
95
+ class AgentRunAsyncResponse(BaseModel):
96
+ """
97
+ Response from asynchronous automation execution.
98
+
99
+ Returns run_id immediately without waiting for completion.
100
+ Use client.runs.retrieve(run_id) to check status later.
101
+
102
+ Example:
103
+ ```python
104
+ response = client.agent.queue(
105
+ goal="Extract product details",
106
+ url="https://example.com"
107
+ )
108
+ print(f"Run started: {response.run_id}")
109
+
110
+ # Check status later
111
+ run = client.runs.get(response.run_id)
112
+ print(f"Status: {run.status}")
113
+ ```
114
+ """
115
+
116
+ run_id: str | None = Field(None, description="Unique identifier for the created automation run")
117
+ error: RunError | None = Field(None, description="Error details. None if successful.")
118
+
119
+
120
+ # ============================================================================
121
+ # Response Types - Streaming Events
122
+ # ============================================================================
123
+
124
+
125
+ class StartedEvent(BaseModel):
126
+ """SSE event indicating the automation run has started."""
127
+
128
+ model_config = ConfigDict(populate_by_name=True)
129
+
130
+ type: EventType = Field(..., description="Event type")
131
+ run_id: str = Field(..., alias="runId", description="Unique identifier for the automation run")
132
+ timestamp: datetime = Field(..., description="Timestamp of the event")
133
+
134
+
135
+ class StreamingUrlEvent(BaseModel):
136
+ """SSE event providing the live browser streaming URL."""
137
+
138
+ model_config = ConfigDict(populate_by_name=True)
139
+
140
+ type: EventType = Field(..., description="Event type")
141
+ run_id: str = Field(..., alias="runId", description="Unique identifier for the automation run")
142
+ streaming_url: str = Field(..., alias="streamingUrl", description="WebSocket URL for live browser streaming")
143
+ timestamp: datetime = Field(..., description="Timestamp of the event")
144
+
145
+
146
+ class ProgressEvent(BaseModel):
147
+ """SSE event indicating automation progress/activity."""
148
+
149
+ model_config = ConfigDict(populate_by_name=True)
150
+
151
+ type: EventType = Field(..., description="Event type")
152
+ run_id: str = Field(..., alias="runId", description="Unique identifier for the automation run")
153
+ purpose: str = Field(..., description="Description of current automation step/activity")
154
+ timestamp: datetime = Field(..., description="Timestamp of the event")
155
+
156
+
157
+ class HeartbeatEvent(BaseModel):
158
+ """SSE event for connection keepalive."""
159
+
160
+ model_config = ConfigDict(populate_by_name=True)
161
+
162
+ type: EventType = Field(..., description="Event type")
163
+ timestamp: datetime = Field(..., description="Timestamp of the event")
164
+
165
+
166
+ class CompleteEvent(BaseModel):
167
+ """SSE event indicating the automation run has completed."""
168
+
169
+ model_config = ConfigDict(populate_by_name=True)
170
+
171
+ type: EventType = Field(..., description="Event type")
172
+ run_id: str = Field(..., alias="runId", description="Unique identifier for the automation run")
173
+ status: RunStatus = Field(..., description="Final status of the automation")
174
+ timestamp: datetime = Field(..., description="Timestamp of the event")
175
+ result_json: dict[str, object] | None = Field(
176
+ None, alias="resultJson", description="Structured JSON result extracted from the automation"
177
+ )
178
+ error: RunError | None = Field(None, description="Error details if the run failed. None if succeeded.")
179
+
180
+
181
+ AgentRunWithStreamingResponse = StartedEvent | StreamingUrlEvent | ProgressEvent | HeartbeatEvent | CompleteEvent
182
+ """Union type for all possible SSE streaming events."""
tinyfish/client.py ADDED
@@ -0,0 +1,76 @@
1
+ """TinyFish SDK client."""
2
+
3
+ from tinyfish._utils.client import BaseAsyncAPIClient, BaseSyncAPIClient
4
+ from tinyfish._utils.client._base import _DEFAULT_TIMEOUT
5
+
6
+ from .agent import AgentResource, AsyncAgentResource
7
+ from .runs import AsyncRunsResource, RunsResource
8
+
9
+ __all__ = ["TinyFish", "AsyncTinyFish"]
10
+
11
+ API_BASE_URL = "https://agent.tinyfish.ai"
12
+
13
+
14
+ class TinyFish(BaseSyncAPIClient):
15
+ """Synchronous TinyFish client for browser automation."""
16
+
17
+ # Explicit class-level annotations so that IDEs and static type checkers
18
+ # (mypy, Pylance/pyright) know the types of these attributes when the package
19
+ # is installed from PyPI. Without these declarations, tools that inspect the
20
+ # installed wheel rather than the live source may not infer the types from the
21
+ # __init__ assignments below — which means no autocomplete for users who just
22
+ # did `pip install tinyfish`.
23
+ agent: AgentResource
24
+ runs: RunsResource
25
+
26
+ def __init__(
27
+ self,
28
+ *,
29
+ # api_key is optional here: if omitted, _BaseClient will read it from
30
+ # the TINYFISH_API_KEY environment variable and raise a clear ValueError
31
+ # if neither is set.
32
+ api_key: str | None = None,
33
+ base_url: str = API_BASE_URL,
34
+ timeout: float = _DEFAULT_TIMEOUT,
35
+ max_retries: int = 2,
36
+ ):
37
+ super().__init__(
38
+ api_key=api_key,
39
+ base_url=base_url,
40
+ timeout=timeout,
41
+ max_retries=max_retries,
42
+ )
43
+
44
+ self.agent = AgentResource(self)
45
+ self.runs = RunsResource(self)
46
+
47
+
48
+ class AsyncTinyFish(BaseAsyncAPIClient):
49
+ """Asynchronous TinyFish client for browser automation."""
50
+
51
+ # Same reasoning as TinyFish above: explicit annotations ensure that type
52
+ # checkers recognise these as AsyncAgentResource / AsyncRunsResource (not
53
+ # the sync variants) when resolving types from an installed package.
54
+ agent: AsyncAgentResource
55
+ runs: AsyncRunsResource
56
+
57
+ def __init__(
58
+ self,
59
+ *,
60
+ # api_key is optional here: if omitted, _BaseClient will read it from
61
+ # the TINYFISH_API_KEY environment variable and raise a clear ValueError
62
+ # if neither is set.
63
+ api_key: str | None = None,
64
+ base_url: str = API_BASE_URL,
65
+ timeout: float = _DEFAULT_TIMEOUT,
66
+ max_retries: int = 2,
67
+ ):
68
+ super().__init__(
69
+ api_key=api_key,
70
+ base_url=base_url,
71
+ timeout=timeout,
72
+ max_retries=max_retries,
73
+ )
74
+
75
+ self.agent = AsyncAgentResource(self)
76
+ self.runs = AsyncRunsResource(self)
tinyfish/py.typed ADDED
@@ -0,0 +1,11 @@
1
+ # PEP 561 marker file.
2
+ #
3
+ # This empty file tells type checkers (mypy, Pylance/pyright) that this package
4
+ # ships its own type information and should NOT be treated as untyped.
5
+ #
6
+ # Without this file, tools like Pylance will ignore the package's annotations even
7
+ # though they exist in the source — meaning users get zero autocomplete or type
8
+ # errors when they install the SDK. Every typed dependency we rely on (httpx,
9
+ # pydantic, tenacity) includes this file for the same reason.
10
+ #
11
+ # Reference: https://peps.python.org/pep-0561/
@@ -0,0 +1,147 @@
1
+ """Runs resource for retrieving and listing automation runs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from tinyfish._utils.resource import BaseAsyncAPIResource, BaseSyncAPIResource
6
+
7
+ from .types import RunListResponse, RunRetrieveResponse, RunStatus, SortDirection
8
+
9
+
10
+ class RunsResource(BaseSyncAPIResource):
11
+ """Retrieve and list automation runs."""
12
+
13
+ def get(self, run_id: str) -> RunRetrieveResponse:
14
+ """Retrieve a single run by its ID.
15
+
16
+ Args:
17
+ run_id: The run ID returned by `agent.queue()` or any run response.
18
+
19
+ Returns:
20
+ RunRetrieveResponse with the full run details including status and result.
21
+
22
+ Raises:
23
+ ValueError: run_id is empty or whitespace.
24
+ NotFoundError: No run exists with that ID.
25
+ AuthenticationError: Invalid API key.
26
+ """
27
+ if not run_id or not run_id.strip():
28
+ raise ValueError("run_id must be a non-empty string")
29
+ return self._get(f"/v1/runs/{run_id}", cast_to=RunRetrieveResponse)
30
+
31
+ def list(
32
+ self,
33
+ *,
34
+ cursor: str | None = None,
35
+ limit: int | None = None,
36
+ status: RunStatus | None = None,
37
+ goal: str | None = None,
38
+ created_after: str | None = None,
39
+ created_before: str | None = None,
40
+ sort_direction: SortDirection | None = None,
41
+ ) -> RunListResponse:
42
+ """List automation runs, with optional filtering and pagination.
43
+
44
+ Args:
45
+ cursor: Pagination cursor from a previous response's next_cursor field.
46
+ limit: Maximum number of runs to return.
47
+ status: Filter by run status — one of "PENDING", "RUNNING",
48
+ "COMPLETED", "FAILED", or "CANCELLED".
49
+ goal: Filter by goal text.
50
+ created_after: Filter runs created after this ISO timestamp.
51
+ created_before: Filter runs created before this ISO timestamp.
52
+ sort_direction: Sort order — "asc" or "desc".
53
+
54
+ Returns:
55
+ RunListResponse with a list of runs and pagination info.
56
+
57
+ Raises:
58
+ AuthenticationError: Invalid API key.
59
+ """
60
+ params = {}
61
+ if cursor is not None:
62
+ params["cursor"] = cursor
63
+ if limit is not None:
64
+ params["limit"] = limit
65
+ if status is not None:
66
+ params["status"] = status
67
+ if goal is not None:
68
+ params["goal"] = goal
69
+ if created_after is not None:
70
+ params["created_after"] = created_after
71
+ if created_before is not None:
72
+ params["created_before"] = created_before
73
+ if sort_direction is not None:
74
+ params["sort_direction"] = sort_direction
75
+ return self._get("/v1/runs", params=params, cast_to=RunListResponse)
76
+
77
+
78
+ class AsyncRunsResource(BaseAsyncAPIResource):
79
+ """Async retrieve and list automation runs."""
80
+
81
+ async def get(self, run_id: str) -> RunRetrieveResponse:
82
+ """Retrieve a single run by its ID.
83
+
84
+ Async version of `RunsResource.get()`.
85
+
86
+ Args:
87
+ run_id: The run ID returned by `agent.queue()` or any run response.
88
+
89
+ Returns:
90
+ RunRetrieveResponse with the full run details including status and result.
91
+
92
+ Raises:
93
+ ValueError: run_id is empty or whitespace.
94
+ NotFoundError: No run exists with that ID.
95
+ AuthenticationError: Invalid API key.
96
+ """
97
+ if not run_id or not run_id.strip():
98
+ raise ValueError("run_id must be a non-empty string")
99
+ return await self._get(f"/v1/runs/{run_id}", cast_to=RunRetrieveResponse)
100
+
101
+ async def list(
102
+ self,
103
+ *,
104
+ cursor: str | None = None,
105
+ limit: int | None = None,
106
+ status: RunStatus | None = None,
107
+ goal: str | None = None,
108
+ created_after: str | None = None,
109
+ created_before: str | None = None,
110
+ sort_direction: SortDirection | None = None,
111
+ ) -> RunListResponse:
112
+ """List automation runs, with optional filtering and pagination.
113
+
114
+ Async version of `RunsResource.list()`.
115
+
116
+ Args:
117
+ cursor: Pagination cursor from a previous response's next_cursor field.
118
+ limit: Maximum number of runs to return.
119
+ status: Filter by run status — one of "PENDING", "RUNNING",
120
+ "COMPLETED", "FAILED", or "CANCELLED".
121
+ goal: Filter by goal text.
122
+ created_after: Filter runs created after this ISO timestamp.
123
+ created_before: Filter runs created before this ISO timestamp.
124
+ sort_direction: Sort order — "asc" or "desc".
125
+
126
+ Returns:
127
+ RunListResponse with a list of runs and pagination info.
128
+
129
+ Raises:
130
+ AuthenticationError: Invalid API key.
131
+ """
132
+ params = {}
133
+ if cursor is not None:
134
+ params["cursor"] = cursor
135
+ if limit is not None:
136
+ params["limit"] = limit
137
+ if status is not None:
138
+ params["status"] = status
139
+ if goal is not None:
140
+ params["goal"] = goal
141
+ if created_after is not None:
142
+ params["created_after"] = created_after
143
+ if created_before is not None:
144
+ params["created_before"] = created_before
145
+ if sort_direction is not None:
146
+ params["sort_direction"] = sort_direction
147
+ return await self._get("/v1/runs", params=params, cast_to=RunListResponse)
tinyfish/runs/types.py ADDED
@@ -0,0 +1,107 @@
1
+ """Runs management request/response types."""
2
+
3
+ from datetime import datetime
4
+ from enum import StrEnum
5
+
6
+ from pydantic import BaseModel, Field
7
+
8
+ # ============================================================================
9
+ # Shared Types
10
+ # ============================================================================
11
+
12
+
13
+ class RunStatus(StrEnum):
14
+ """Status of an automation run.
15
+
16
+ Use these constants instead of raw strings when filtering or branching on
17
+ run status — your IDE will autocomplete the options and typos become
18
+ type errors.
19
+ """
20
+
21
+ PENDING = "PENDING"
22
+ RUNNING = "RUNNING"
23
+ COMPLETED = "COMPLETED"
24
+ FAILED = "FAILED"
25
+ CANCELLED = "CANCELLED"
26
+
27
+
28
+ class SortDirection(StrEnum):
29
+ """Sort order for list queries."""
30
+
31
+ ASC = "asc"
32
+ DESC = "desc"
33
+
34
+
35
+ class BrowserConfig(BaseModel):
36
+ """Browser configuration used for a run."""
37
+
38
+ proxy_enabled: bool | None = Field(None, description="Whether proxy was enabled")
39
+ proxy_country_code: str | None = Field(None, description="Country code for proxy")
40
+
41
+
42
+ class ErrorCategory(StrEnum):
43
+ """Error category indicating the source of failure.
44
+
45
+ SYSTEM_FAILURE: TinyFish infrastructure issue — safe to retry.
46
+ AGENT_FAILURE: Problem with the run itself — fix the input.
47
+ UNKNOWN: Unclassified — treat as retryable.
48
+ """
49
+
50
+ SYSTEM_FAILURE = "SYSTEM_FAILURE"
51
+ AGENT_FAILURE = "AGENT_FAILURE"
52
+ UNKNOWN = "UNKNOWN"
53
+
54
+
55
+ class RunError(BaseModel):
56
+ """Error details for failed runs."""
57
+
58
+ message: str = Field(..., description="Error message describing why the run failed")
59
+ category: ErrorCategory = Field(..., description="Error category indicating the source of failure")
60
+ retry_after: int | None = Field(None, description="Suggested retry delay in seconds")
61
+ help_url: str | None = Field(None, description="URL to troubleshooting docs")
62
+ help_message: str | None = Field(None, description="Human-readable guidance")
63
+
64
+
65
+ # ============================================================================
66
+ # Run Object (used in both retrieve and list)
67
+ # ============================================================================
68
+
69
+
70
+ class Run(BaseModel):
71
+ """A single automation run with full details."""
72
+
73
+ run_id: str = Field(..., description="Unique identifier for the run")
74
+ status: RunStatus = Field(..., description="Current status of the run")
75
+ goal: str = Field(..., description="Natural language goal for this automation run")
76
+ created_at: datetime = Field(..., description="Timestamp when run was created")
77
+ started_at: datetime | None = Field(None, description="Timestamp when run started executing")
78
+ finished_at: datetime | None = Field(None, description="Timestamp when run finished executing")
79
+ result: dict[str, object] | None = Field(
80
+ None, description="Extracted data from the automation run. None if not completed or failed."
81
+ )
82
+ error: RunError | None = Field(None, description="Error details. None if the run succeeded or is still running.")
83
+ streaming_url: str | None = Field(None, description="URL to watch live browser session (available while running)")
84
+ browser_config: BrowserConfig | None = Field(None, description="Browser configuration used for the run")
85
+
86
+
87
+ # ============================================================================
88
+ # Response Types
89
+ # ============================================================================
90
+
91
+ RunRetrieveResponse = Run
92
+ """Response from retrieving a single run."""
93
+
94
+
95
+ class PaginationInfo(BaseModel):
96
+ """Pagination metadata for list responses."""
97
+
98
+ total: int = Field(..., description="Total number of runs matching the current filters")
99
+ has_more: bool = Field(..., description="Whether there are more results after this page")
100
+ next_cursor: str | None = Field(None, description="Cursor for fetching next page. None if no more results.")
101
+
102
+
103
+ class RunListResponse(BaseModel):
104
+ """Paginated list of automation runs."""
105
+
106
+ data: list[Run] = Field(..., description="Array of runs")
107
+ pagination: PaginationInfo = Field(..., description="Pagination information")
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: tinyfish
3
+ Version: 0.2.2
4
+ Summary: Official Python SDK for the TinyFish API
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: httpx>=0.27.0
7
+ Requires-Dist: pydantic>=2.0.0
8
+ Requires-Dist: tenacity>=8.0.0
@@ -0,0 +1,18 @@
1
+ tinyfish/__init__.py,sha256=hKareLVKDKMAywdmW0IOAKz9a5ZR5d7M86s1_opO7GI,2036
2
+ tinyfish/client.py,sha256=2IS60FJZbDnbbOQww4N0opBi8nMMggFGq2hbGQtoBdY,2599
3
+ tinyfish/py.typed,sha256=yxjSy53_7xEIAsOyy7r2GFVkBYVEjtl2XyXKssTRdqc,532
4
+ tinyfish/_utils/__init__.py,sha256=-uUXnfefCggFPEGiscX1eKN1oDzk82HANesVZYZ9thA,1336
5
+ tinyfish/_utils/exceptions.py,sha256=DjNt7b9biQG6JSsElBdGQrMlpoC8qD7DV5gqklt0p1s,5105
6
+ tinyfish/_utils/resource.py,sha256=N9R-Ku90nOXie3ysEnjb2FZYmjqx3q0Lzbt3IfcGEV8,670
7
+ tinyfish/_utils/sse_parser.py,sha256=-zwym5Aq3axxcU8TJupsrDoC-7jhDmNAL1TiAaT_Jd8,1847
8
+ tinyfish/_utils/client/__init__.py,sha256=3qqhgxPazeJp5KO2WZAPnTAc5l1nfFwmRPR90cIyZkM,178
9
+ tinyfish/_utils/client/_base.py,sha256=i8P8eKU-qPTWnunjKo_bUI-sIGEnHKugUAOALfs10p8,5208
10
+ tinyfish/_utils/client/async_.py,sha256=bYRDI-xRoYtolfz1Q95AAAG8I3szpVVQsIkermIV64A,6452
11
+ tinyfish/_utils/client/sync.py,sha256=p2iXYpQJUsbV8yjy5XwFb4aCM6958Kpf2kCUzs2URAM,6269
12
+ tinyfish/agent/__init__.py,sha256=_OmCgTNzp-e1rdl7UK25zjjbkk2_ICsFi0lljdkGeOI,14278
13
+ tinyfish/agent/types.py,sha256=hwymhzRDn5JklSn0R76TaZE4jKI3PXIFv_G5ampl2RI,6361
14
+ tinyfish/runs/__init__.py,sha256=YLUrveaCsff7xfWDj40fSsXpWnLSxcQU_g2Me54c2jA,5440
15
+ tinyfish/runs/types.py,sha256=jMIjiQyE9lESHPbxm1hTgnh5fUZ0hVk1auuCta1Hx0g,4110
16
+ tinyfish-0.2.2.dist-info/METADATA,sha256=vK3OgMO0sSCVlAyBhnfDc59Q5GUJu8Z3enzb8-wgEFQ,217
17
+ tinyfish-0.2.2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
18
+ tinyfish-0.2.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any