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.
- tinyfish/__init__.py +104 -0
- tinyfish/_utils/__init__.py +26 -0
- tinyfish/_utils/client/__init__.py +4 -0
- tinyfish/_utils/client/_base.py +137 -0
- tinyfish/_utils/client/async_.py +192 -0
- tinyfish/_utils/client/sync.py +191 -0
- tinyfish/_utils/exceptions.py +159 -0
- tinyfish/_utils/resource.py +23 -0
- tinyfish/_utils/sse_parser.py +62 -0
- tinyfish/agent/__init__.py +368 -0
- tinyfish/agent/types.py +182 -0
- tinyfish/client.py +76 -0
- tinyfish/py.typed +11 -0
- tinyfish/runs/__init__.py +147 -0
- tinyfish/runs/types.py +107 -0
- tinyfish-0.2.2.dist-info/METADATA +8 -0
- tinyfish-0.2.2.dist-info/RECORD +18 -0
- tinyfish-0.2.2.dist-info/WHEEL +4 -0
tinyfish/agent/types.py
ADDED
|
@@ -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,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,,
|