tinyfish 0.2.3__tar.gz → 0.2.5__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.
- tinyfish-0.2.5/.pre-commit-config.yaml +23 -0
- tinyfish-0.2.5/AGENTS.md +1 -0
- tinyfish-0.2.5/CLAUDE.md +60 -0
- {tinyfish-0.2.3 → tinyfish-0.2.5}/PKG-INFO +1 -1
- {tinyfish-0.2.3 → tinyfish-0.2.5}/RELEASE.md +5 -5
- tinyfish-0.2.5/docs/internal/api-integration-header.md +58 -0
- {tinyfish-0.2.3 → tinyfish-0.2.5}/pyproject.toml +5 -1
- {tinyfish-0.2.3 → tinyfish-0.2.5}/src/tinyfish/__init__.py +23 -2
- {tinyfish-0.2.3 → tinyfish-0.2.5}/src/tinyfish/_utils/client/_base.py +12 -1
- {tinyfish-0.2.3 → tinyfish-0.2.5}/src/tinyfish/_utils/client/async_.py +2 -0
- {tinyfish-0.2.3 → tinyfish-0.2.5}/src/tinyfish/_utils/client/sync.py +2 -0
- {tinyfish-0.2.3 → tinyfish-0.2.5}/src/tinyfish/agent/__init__.py +50 -12
- {tinyfish-0.2.3 → tinyfish-0.2.5}/src/tinyfish/agent/types.py +22 -9
- tinyfish-0.2.5/src/tinyfish/browser/__init__.py +86 -0
- tinyfish-0.2.5/src/tinyfish/browser/types.py +24 -0
- {tinyfish-0.2.3 → tinyfish-0.2.5}/src/tinyfish/client.py +15 -0
- tinyfish-0.2.5/src/tinyfish/fetch/__init__.py +99 -0
- tinyfish-0.2.5/src/tinyfish/fetch/types.py +40 -0
- tinyfish-0.2.5/src/tinyfish/search/__init__.py +79 -0
- tinyfish-0.2.5/src/tinyfish/search/types.py +21 -0
- {tinyfish-0.2.3 → tinyfish-0.2.5}/tests/test_agent.py +3 -3
- tinyfish-0.2.5/tests/test_browser.py +141 -0
- {tinyfish-0.2.3 → tinyfish-0.2.5}/tests/test_client.py +43 -0
- tinyfish-0.2.5/tests/test_fetch.py +267 -0
- tinyfish-0.2.5/tests/test_search.py +229 -0
- {tinyfish-0.2.3 → tinyfish-0.2.5}/uv.lock +1 -1
- {tinyfish-0.2.3 → tinyfish-0.2.5}/.gitignore +0 -0
- {tinyfish-0.2.3 → tinyfish-0.2.5}/README.md +0 -0
- {tinyfish-0.2.3 → tinyfish-0.2.5}/docs/exceptions-and-errors-guide.md +0 -0
- {tinyfish-0.2.3 → tinyfish-0.2.5}/docs/internal/exceptions-and-errors-guide.md +0 -0
- {tinyfish-0.2.3 → tinyfish-0.2.5}/docs/internal/publishing-private-guide.md +0 -0
- {tinyfish-0.2.3 → tinyfish-0.2.5}/docs/pagination-guide.md +0 -0
- {tinyfish-0.2.3 → tinyfish-0.2.5}/docs/proxy-and-browser-profiles.md +0 -0
- {tinyfish-0.2.3 → tinyfish-0.2.5}/docs/streaming-guide.md +0 -0
- {tinyfish-0.2.3 → tinyfish-0.2.5}/src/tinyfish/_utils/__init__.py +0 -0
- {tinyfish-0.2.3 → tinyfish-0.2.5}/src/tinyfish/_utils/client/__init__.py +0 -0
- {tinyfish-0.2.3 → tinyfish-0.2.5}/src/tinyfish/_utils/exceptions.py +0 -0
- {tinyfish-0.2.3 → tinyfish-0.2.5}/src/tinyfish/_utils/resource.py +0 -0
- {tinyfish-0.2.3 → tinyfish-0.2.5}/src/tinyfish/_utils/sse_parser.py +0 -0
- {tinyfish-0.2.3 → tinyfish-0.2.5}/src/tinyfish/py.typed +0 -0
- {tinyfish-0.2.3 → tinyfish-0.2.5}/src/tinyfish/runs/__init__.py +0 -0
- {tinyfish-0.2.3 → tinyfish-0.2.5}/src/tinyfish/runs/types.py +0 -0
- {tinyfish-0.2.3 → tinyfish-0.2.5}/tests/__init__.py +0 -0
- {tinyfish-0.2.3 → tinyfish-0.2.5}/tests/conftest.py +0 -0
- {tinyfish-0.2.3 → tinyfish-0.2.5}/tests/test_errors.py +0 -0
- {tinyfish-0.2.3 → tinyfish-0.2.5}/tests/test_runs.py +0 -0
- {tinyfish-0.2.3 → tinyfish-0.2.5}/tests/testing_guide.md +0 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v5.0.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: trailing-whitespace
|
|
6
|
+
- id: end-of-file-fixer
|
|
7
|
+
- id: check-yaml
|
|
8
|
+
- id: check-added-large-files
|
|
9
|
+
- id: check-toml
|
|
10
|
+
- id: debug-statements
|
|
11
|
+
|
|
12
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
13
|
+
rev: v0.15.5
|
|
14
|
+
hooks:
|
|
15
|
+
- id: ruff-check
|
|
16
|
+
- id: ruff-format
|
|
17
|
+
|
|
18
|
+
- repo: https://github.com/astral-sh/uv-pre-commit
|
|
19
|
+
rev: 0.10.9
|
|
20
|
+
hooks:
|
|
21
|
+
- id: uv-lock
|
|
22
|
+
name: uv-lock
|
|
23
|
+
files: ^sdk/sdk-python/(uv\.lock|pyproject\.toml)$
|
tinyfish-0.2.5/AGENTS.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
CLAUDE.md
|
tinyfish-0.2.5/CLAUDE.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# TinyFish Python SDK (`tinyfish`)
|
|
2
|
+
|
|
3
|
+
Official Python SDK for TinyFish. Provides sync (`TinyFish`) and async (`AsyncTinyFish`) clients for running
|
|
4
|
+
automations, streaming events, and managing runs.
|
|
5
|
+
|
|
6
|
+
For full method reference and examples: see `README.md`.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Package Manager
|
|
11
|
+
|
|
12
|
+
`uv` — not pip, not poetry.
|
|
13
|
+
|
|
14
|
+
## Key Entry Points
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
src/tinyfish/__init__.py → exports TinyFish, AsyncTinyFish, and all public types
|
|
18
|
+
src/tinyfish/client.py → TinyFish client class (sync)
|
|
19
|
+
src/tinyfish/agent/ → agent.run(), agent.queue(), agent.stream()
|
|
20
|
+
src/tinyfish/runs/ → runs.get(), runs.list()
|
|
21
|
+
src/tinyfish/_utils/ → HTTP client base (sync + async), retry, error handling
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Usage
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from tinyfish import TinyFish
|
|
28
|
+
|
|
29
|
+
client = TinyFish(api_key="your-api-key") # or set TINYFISH_API_KEY env var
|
|
30
|
+
response = client.agent.run(goal="...", url="https://...")
|
|
31
|
+
print(response.result)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Tests
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
cd sdk/sdk-python && uv run pytest
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Pre-commit Hooks
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
cd sdk/sdk-python && pre-commit install
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Hooks: `ruff format`, `ruff check`, `uv-lock` sync. Run before first commit and after any dep change.
|
|
47
|
+
|
|
48
|
+
## Lint / Format
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
cd sdk/sdk-python && uv run ruff check . && uv run ruff format .
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Package name
|
|
55
|
+
|
|
56
|
+
`tinyfish` (PyPI). The npm org uses a hyphen: `@tiny-fish/` (not `@tinyfish/`).
|
|
57
|
+
|
|
58
|
+
## Further Reading
|
|
59
|
+
|
|
60
|
+
See `README.md` for full usage examples, authentication patterns, and SDK changelog.
|
|
@@ -33,13 +33,13 @@ Commit and merge to main.
|
|
|
33
33
|
### Step 2: Create a GitHub Release
|
|
34
34
|
|
|
35
35
|
1. Go to the `ux-labs` repo on GitHub → **Releases** → **Draft a new release**
|
|
36
|
-
2. Set the tag to match the version exactly, prefixed with `v`:
|
|
37
|
-
- Version `0.2.0` → tag `v0.2.0`
|
|
36
|
+
2. Set the tag to match the version exactly, prefixed with `sdk-py/v`:
|
|
37
|
+
- Version `0.2.0` → tag `sdk-py/v0.2.0`
|
|
38
38
|
3. Set the target to `main`
|
|
39
39
|
4. Write release notes summarizing what changed
|
|
40
40
|
5. Click **Publish release**
|
|
41
41
|
|
|
42
|
-
The workflow validates that the
|
|
42
|
+
The CD workflow (`CD_sdk_python.yml`) only triggers on tags prefixed with `sdk-py/v`. It validates that the version portion of the tag matches `pyproject.toml` — if they don't match, the build fails before anything is published.
|
|
43
43
|
|
|
44
44
|
### Step 3: Monitor the workflow
|
|
45
45
|
|
|
@@ -56,13 +56,13 @@ After the workflow completes, verify the release:
|
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
58
|
pip install tinyfish==0.2.0
|
|
59
|
-
python -c "from tinyfish import
|
|
59
|
+
python -c "from tinyfish import TinyFish; print('ok')"
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
## Troubleshooting
|
|
63
63
|
|
|
64
64
|
**Tag does not match pyproject.toml version**
|
|
65
|
-
The build job validates that the git tag (e.g., `v0.2.0`) matches the version in `pyproject.toml` (e.g., `0.2.0`). Fix by updating `pyproject.toml` to match the tag before publishing, then delete and recreate the release.
|
|
65
|
+
The build job validates that the git tag (e.g., `sdk-py/v0.2.0`) matches the version in `pyproject.toml` (e.g., `0.2.0`). Fix by updating `pyproject.toml` to match the tag before publishing, then delete and recreate the release.
|
|
66
66
|
|
|
67
67
|
**403 / authentication error**
|
|
68
68
|
Verify that `PYPI_API_TOKEN` has been added to the `ux-labs` repo secrets in `github-control`. The token must exist as a GitHub Actions secret before the workflow can publish.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# API Integration Tagging (`TF_API_INTEGRATION`)
|
|
2
|
+
|
|
3
|
+
Internal-only mechanism for identifying which integration platform is making API requests through our SDK.
|
|
4
|
+
|
|
5
|
+
## How it works
|
|
6
|
+
|
|
7
|
+
The SDK reads the `TF_API_INTEGRATION` environment variable when building request bodies. When set, the SDK injects `api_integration` into every request body sent to the automation endpoints.
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
TF_API_INTEGRATION=n8n --> { "goal": "...", "url": "...", "api_integration": "n8n" }
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
This reuses the existing `api_integration` body field that the server already parses, stores in the DB, and emits to PostHog — no backend changes needed.
|
|
14
|
+
|
|
15
|
+
This is intentionally **not** exposed as a constructor parameter or public API. It does not appear in editor autocomplete, type definitions, or documentation. Only we control which integrations set this value.
|
|
16
|
+
|
|
17
|
+
## Environment variable
|
|
18
|
+
|
|
19
|
+
| Variable | Example values | Required |
|
|
20
|
+
| --------------------- | ---------------------- | -------- |
|
|
21
|
+
| `TF_API_INTEGRATION` | `n8n`, `dify`, `zapier` | No |
|
|
22
|
+
|
|
23
|
+
When unset or empty, `api_integration` is omitted from the body. Existing behavior is unchanged.
|
|
24
|
+
|
|
25
|
+
## Where it's read
|
|
26
|
+
|
|
27
|
+
`src/tinyfish/_utils/client/_base.py` — `_inject_integration()`, called from `_request()` in both sync and async clients:
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
integration = os.environ.get("TF_API_INTEGRATION", "").strip()
|
|
31
|
+
if integration:
|
|
32
|
+
json["api_integration"] = integration
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
This runs on every API request automatically — no per-resource setup needed.
|
|
36
|
+
|
|
37
|
+
## Server-side flow
|
|
38
|
+
|
|
39
|
+
1. Server validates `api_integration` via Zod schema (`frontend/app/v1/schemas.ts`)
|
|
40
|
+
2. `buildRunOptions()` extracts it from the request body
|
|
41
|
+
3. Stored in `runs.api_integration` DB column
|
|
42
|
+
4. Emitted on `run_completed` PostHog event as `api_integration` property
|
|
43
|
+
|
|
44
|
+
## Setting up a new integration
|
|
45
|
+
|
|
46
|
+
To tag requests from a new integration partner:
|
|
47
|
+
|
|
48
|
+
1. Set `TF_API_INTEGRATION=<name>` in the environment where the SDK runs (e.g., the n8n node, Dify plugin, Zapier action)
|
|
49
|
+
2. No SDK code changes needed — the env var is picked up automatically
|
|
50
|
+
3. The value will appear in PostHog under `run_completed.api_integration`
|
|
51
|
+
|
|
52
|
+
## Tests
|
|
53
|
+
|
|
54
|
+
`tests/test_client.py`:
|
|
55
|
+
|
|
56
|
+
- `test_integration_in_body_via_env` — `api_integration` is in the request body when env var is set
|
|
57
|
+
- `test_no_integration_in_body_by_default` — `api_integration` is absent when env var is not set
|
|
58
|
+
- `test_async_integration_in_body_via_env` — async client variant
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tinyfish"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.5"
|
|
4
4
|
description = "Official Python SDK for the TinyFish API"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -33,6 +33,10 @@ asyncio_default_fixture_loop_scope = "session"
|
|
|
33
33
|
[tool.ruff]
|
|
34
34
|
src = ["src"]
|
|
35
35
|
line-length = 120
|
|
36
|
+
[tool.ruff.format]
|
|
37
|
+
quote-style = "double"
|
|
38
|
+
docstring-code-format = true
|
|
39
|
+
docstring-code-line-length = 20
|
|
36
40
|
|
|
37
41
|
[tool.ruff.lint]
|
|
38
42
|
select = ["E", "F", "I", "UP", "PYI", "TID"]
|
|
@@ -26,8 +26,6 @@ from ._utils.exceptions import (
|
|
|
26
26
|
|
|
27
27
|
# Agent resource
|
|
28
28
|
from .agent import AgentStream, AsyncAgentStream
|
|
29
|
-
|
|
30
|
-
# Agent types
|
|
31
29
|
from .agent.types import (
|
|
32
30
|
AgentRunAsyncResponse,
|
|
33
31
|
AgentRunResponse,
|
|
@@ -42,7 +40,16 @@ from .agent.types import (
|
|
|
42
40
|
StartedEvent,
|
|
43
41
|
StreamingUrlEvent,
|
|
44
42
|
)
|
|
43
|
+
|
|
44
|
+
# Browser types
|
|
45
|
+
from .browser.types import BrowserSession, BrowserSessionCreateParams
|
|
45
46
|
from .client import AsyncTinyFish, TinyFish
|
|
47
|
+
from .fetch.types import (
|
|
48
|
+
FetchError,
|
|
49
|
+
FetchFormat,
|
|
50
|
+
FetchResponse,
|
|
51
|
+
FetchResult,
|
|
52
|
+
)
|
|
46
53
|
|
|
47
54
|
# Runs types
|
|
48
55
|
from .runs.types import (
|
|
@@ -55,6 +62,9 @@ from .runs.types import (
|
|
|
55
62
|
SortDirection,
|
|
56
63
|
)
|
|
57
64
|
|
|
65
|
+
# Search types
|
|
66
|
+
from .search.types import SearchQueryResponse, SearchResult
|
|
67
|
+
|
|
58
68
|
__version__ = version("tinyfish")
|
|
59
69
|
|
|
60
70
|
__all__ = [
|
|
@@ -80,6 +90,9 @@ __all__ = [
|
|
|
80
90
|
# Agent resource
|
|
81
91
|
"AgentStream",
|
|
82
92
|
"AsyncAgentStream",
|
|
93
|
+
# Browser types
|
|
94
|
+
"BrowserSession",
|
|
95
|
+
"BrowserSessionCreateParams",
|
|
83
96
|
# Agent types
|
|
84
97
|
"EventType",
|
|
85
98
|
"BrowserProfile",
|
|
@@ -93,6 +106,14 @@ __all__ = [
|
|
|
93
106
|
"HeartbeatEvent",
|
|
94
107
|
"CompleteEvent",
|
|
95
108
|
"AgentRunWithStreamingResponse",
|
|
109
|
+
# Fetch types
|
|
110
|
+
"FetchFormat",
|
|
111
|
+
"FetchResult",
|
|
112
|
+
"FetchError",
|
|
113
|
+
"FetchResponse",
|
|
114
|
+
# Search types
|
|
115
|
+
"SearchResult",
|
|
116
|
+
"SearchQueryResponse",
|
|
96
117
|
# Runs types
|
|
97
118
|
"ErrorCategory",
|
|
98
119
|
"SortDirection",
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
6
|
from importlib.metadata import version as _pkg_version
|
|
7
|
-
from typing import TypeVar
|
|
7
|
+
from typing import Any, TypeVar
|
|
8
8
|
|
|
9
9
|
import httpx
|
|
10
10
|
from pydantic import BaseModel
|
|
@@ -79,8 +79,19 @@ class _BaseClient:
|
|
|
79
79
|
"Accept": "application/json",
|
|
80
80
|
"X-API-Key": self._api_key,
|
|
81
81
|
"User-Agent": f"tinyfish-python/{_pkg_version('tinyfish')}",
|
|
82
|
+
"X-TF-Request-Origin": "tinyfish-python",
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
@staticmethod
|
|
86
|
+
def _inject_integration(json: dict[str, Any] | None) -> dict[str, Any] | None:
|
|
87
|
+
"""Inject api_integration from TF_API_INTEGRATION env var into JSON body."""
|
|
88
|
+
if json is None:
|
|
89
|
+
return None
|
|
90
|
+
integration = os.environ.get("TF_API_INTEGRATION", "").strip()
|
|
91
|
+
if integration:
|
|
92
|
+
json["api_integration"] = integration
|
|
93
|
+
return json
|
|
94
|
+
|
|
84
95
|
def _make_status_error(self, response: httpx.Response) -> APIStatusError:
|
|
85
96
|
"""
|
|
86
97
|
Translate an error HTTP response into the appropriate SDK exception.
|
|
@@ -72,6 +72,7 @@ class BaseAsyncAPIClient(_BaseClient):
|
|
|
72
72
|
APIConnectionError: Network/connection error
|
|
73
73
|
APIStatusError: HTTP error status (4xx, 5xx)
|
|
74
74
|
"""
|
|
75
|
+
json = self._inject_integration(json)
|
|
75
76
|
max_attempts = self._max_retries + 1
|
|
76
77
|
|
|
77
78
|
# Retryable: 408, 429, 5xx, and network errors (TimeoutException ⊂ RequestError).
|
|
@@ -142,6 +143,7 @@ class BaseAsyncAPIClient(_BaseClient):
|
|
|
142
143
|
matching the retry behaviour of ``_request()``. Once a 200 response
|
|
143
144
|
is received and streaming begins, no further retries are attempted.
|
|
144
145
|
"""
|
|
146
|
+
json = self._inject_integration(json)
|
|
145
147
|
max_attempts = self._max_retries + 1
|
|
146
148
|
|
|
147
149
|
@retry(
|
|
@@ -72,6 +72,7 @@ class BaseSyncAPIClient(_BaseClient):
|
|
|
72
72
|
APIConnectionError: Network/connection error
|
|
73
73
|
APIStatusError: HTTP error status (4xx, 5xx)
|
|
74
74
|
"""
|
|
75
|
+
json = self._inject_integration(json)
|
|
75
76
|
max_attempts = self._max_retries + 1
|
|
76
77
|
|
|
77
78
|
# Retryable: 408, 429, 5xx, and network errors (TimeoutException ⊂ RequestError).
|
|
@@ -142,6 +143,7 @@ class BaseSyncAPIClient(_BaseClient):
|
|
|
142
143
|
matching the retry behaviour of ``_request()``. Once a 200 response
|
|
143
144
|
is received and streaming begins, no further retries are attempted.
|
|
144
145
|
"""
|
|
146
|
+
json = self._inject_integration(json)
|
|
145
147
|
max_attempts = self._max_retries + 1
|
|
146
148
|
|
|
147
149
|
@retry(
|
|
@@ -41,8 +41,15 @@ class AgentStream:
|
|
|
41
41
|
|
|
42
42
|
Use as::
|
|
43
43
|
|
|
44
|
-
with
|
|
45
|
-
|
|
44
|
+
with (
|
|
45
|
+
client.agent.stream(
|
|
46
|
+
goal=...,
|
|
47
|
+
url=...,
|
|
48
|
+
) as stream
|
|
49
|
+
):
|
|
50
|
+
for (
|
|
51
|
+
event
|
|
52
|
+
) in stream:
|
|
46
53
|
...
|
|
47
54
|
"""
|
|
48
55
|
|
|
@@ -64,8 +71,15 @@ class AsyncAgentStream:
|
|
|
64
71
|
|
|
65
72
|
Use as::
|
|
66
73
|
|
|
67
|
-
async with
|
|
68
|
-
|
|
74
|
+
async with (
|
|
75
|
+
client.agent.stream(
|
|
76
|
+
goal=...,
|
|
77
|
+
url=...,
|
|
78
|
+
) as stream
|
|
79
|
+
):
|
|
80
|
+
async for (
|
|
81
|
+
event
|
|
82
|
+
) in stream:
|
|
69
83
|
...
|
|
70
84
|
"""
|
|
71
85
|
|
|
@@ -166,10 +180,22 @@ class AgentResource(BaseSyncAPIResource):
|
|
|
166
180
|
Use the on_* callbacks for a reactive style, or iterate over
|
|
167
181
|
the stream for a sequential style::
|
|
168
182
|
|
|
169
|
-
with
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
183
|
+
with (
|
|
184
|
+
client.agent.stream(
|
|
185
|
+
goal=...,
|
|
186
|
+
url=...,
|
|
187
|
+
) as stream
|
|
188
|
+
):
|
|
189
|
+
for (
|
|
190
|
+
event
|
|
191
|
+
) in stream:
|
|
192
|
+
if isinstance(
|
|
193
|
+
event,
|
|
194
|
+
ProgressEvent,
|
|
195
|
+
):
|
|
196
|
+
print(
|
|
197
|
+
event.purpose
|
|
198
|
+
)
|
|
173
199
|
|
|
174
200
|
Args:
|
|
175
201
|
goal: Natural language description of what to do on the page.
|
|
@@ -309,10 +335,22 @@ class AsyncAgentResource(BaseAsyncAPIResource):
|
|
|
309
335
|
Use the on_* callbacks for a reactive style, or iterate over
|
|
310
336
|
the stream for a sequential style::
|
|
311
337
|
|
|
312
|
-
async with
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
338
|
+
async with (
|
|
339
|
+
client.agent.stream(
|
|
340
|
+
goal=...,
|
|
341
|
+
url=...,
|
|
342
|
+
) as stream
|
|
343
|
+
):
|
|
344
|
+
async for (
|
|
345
|
+
event
|
|
346
|
+
) in stream:
|
|
347
|
+
if isinstance(
|
|
348
|
+
event,
|
|
349
|
+
ProgressEvent,
|
|
350
|
+
):
|
|
351
|
+
print(
|
|
352
|
+
event.purpose
|
|
353
|
+
)
|
|
316
354
|
|
|
317
355
|
Args:
|
|
318
356
|
goal: Natural language description of what to do on the page.
|
|
@@ -67,12 +67,19 @@ class AgentRunResponse(BaseModel):
|
|
|
67
67
|
```python
|
|
68
68
|
response = client.agent.run(
|
|
69
69
|
goal="Find the price of iPhone 15",
|
|
70
|
-
url="https://www.apple.com"
|
|
70
|
+
url="https://www.apple.com",
|
|
71
71
|
)
|
|
72
|
-
if
|
|
73
|
-
|
|
72
|
+
if (
|
|
73
|
+
response.status
|
|
74
|
+
== "COMPLETED"
|
|
75
|
+
):
|
|
76
|
+
print(
|
|
77
|
+
response.result
|
|
78
|
+
)
|
|
74
79
|
else:
|
|
75
|
-
print(
|
|
80
|
+
print(
|
|
81
|
+
f"Failed: {response.error.message}"
|
|
82
|
+
)
|
|
76
83
|
```
|
|
77
84
|
"""
|
|
78
85
|
|
|
@@ -103,13 +110,19 @@ class AgentRunAsyncResponse(BaseModel):
|
|
|
103
110
|
```python
|
|
104
111
|
response = client.agent.queue(
|
|
105
112
|
goal="Extract product details",
|
|
106
|
-
url="https://example.com"
|
|
113
|
+
url="https://example.com",
|
|
114
|
+
)
|
|
115
|
+
print(
|
|
116
|
+
f"Run started: {response.run_id}"
|
|
107
117
|
)
|
|
108
|
-
print(f"Run started: {response.run_id}")
|
|
109
118
|
|
|
110
119
|
# Check status later
|
|
111
|
-
run = client.runs.get(
|
|
112
|
-
|
|
120
|
+
run = client.runs.get(
|
|
121
|
+
response.run_id
|
|
122
|
+
)
|
|
123
|
+
print(
|
|
124
|
+
f"Status: {run.status}"
|
|
125
|
+
)
|
|
113
126
|
```
|
|
114
127
|
"""
|
|
115
128
|
|
|
@@ -173,7 +186,7 @@ class CompleteEvent(BaseModel):
|
|
|
173
186
|
status: RunStatus = Field(..., description="Final status of the automation")
|
|
174
187
|
timestamp: datetime = Field(..., description="Timestamp of the event")
|
|
175
188
|
result_json: dict[str, object] | None = Field(
|
|
176
|
-
None, alias="
|
|
189
|
+
None, alias="result", description="Structured JSON result extracted from the automation"
|
|
177
190
|
)
|
|
178
191
|
error: RunError | None = Field(None, description="Error details if the run failed. None if succeeded.")
|
|
179
192
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Browser resource for creating remote browser sessions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from tinyfish._utils.client import BaseAsyncAPIClient, BaseSyncAPIClient
|
|
6
|
+
from tinyfish._utils.resource import BaseAsyncAPIResource, BaseSyncAPIResource
|
|
7
|
+
|
|
8
|
+
from .types import BrowserSession, BrowserSessionCreateParams
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BrowserSessionsResource(BaseSyncAPIResource):
|
|
12
|
+
"""Create remote browser sessions (sync)."""
|
|
13
|
+
|
|
14
|
+
def create(
|
|
15
|
+
self,
|
|
16
|
+
*,
|
|
17
|
+
url: str | None = None,
|
|
18
|
+
timeout_seconds: int | None = None,
|
|
19
|
+
) -> BrowserSession:
|
|
20
|
+
"""Create a new remote browser session.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
url: Target URL for the browser session. If omitted, browser starts at about:blank.
|
|
24
|
+
timeout_seconds: Inactivity timeout in seconds. If omitted or exceeds plan max,
|
|
25
|
+
the plan max is used.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
BrowserSession with session_id, cdp_url, and base_url.
|
|
29
|
+
|
|
30
|
+
Raises:
|
|
31
|
+
BadRequestError: Invalid parameters.
|
|
32
|
+
AuthenticationError: Invalid API key.
|
|
33
|
+
"""
|
|
34
|
+
params = BrowserSessionCreateParams(url=url, timeout_seconds=timeout_seconds)
|
|
35
|
+
body = params.model_dump(exclude_none=True)
|
|
36
|
+
return self._post("/v1/browser", json=body, cast_to=BrowserSession)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class AsyncBrowserSessionsResource(BaseAsyncAPIResource):
|
|
40
|
+
"""Create remote browser sessions (async)."""
|
|
41
|
+
|
|
42
|
+
async def create(
|
|
43
|
+
self,
|
|
44
|
+
*,
|
|
45
|
+
url: str | None = None,
|
|
46
|
+
timeout_seconds: int | None = None,
|
|
47
|
+
) -> BrowserSession:
|
|
48
|
+
"""Create a new remote browser session.
|
|
49
|
+
|
|
50
|
+
Async version of `BrowserSessionsResource.create()`.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
url: Target URL for the browser session. If omitted, browser starts at about:blank.
|
|
54
|
+
timeout_seconds: Inactivity timeout in seconds. If omitted or exceeds plan max,
|
|
55
|
+
the plan max is used.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
BrowserSession with session_id, cdp_url, and base_url.
|
|
59
|
+
|
|
60
|
+
Raises:
|
|
61
|
+
BadRequestError: Invalid parameters.
|
|
62
|
+
AuthenticationError: Invalid API key.
|
|
63
|
+
"""
|
|
64
|
+
params = BrowserSessionCreateParams(url=url, timeout_seconds=timeout_seconds)
|
|
65
|
+
body = params.model_dump(exclude_none=True)
|
|
66
|
+
return await self._post("/v1/browser", json=body, cast_to=BrowserSession)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class BrowserResource(BaseSyncAPIResource):
|
|
70
|
+
"""Browser API namespace (sync)."""
|
|
71
|
+
|
|
72
|
+
sessions: BrowserSessionsResource
|
|
73
|
+
|
|
74
|
+
def __init__(self, client: BaseSyncAPIClient) -> None:
|
|
75
|
+
super().__init__(client)
|
|
76
|
+
self.sessions = BrowserSessionsResource(client)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class AsyncBrowserResource(BaseAsyncAPIResource):
|
|
80
|
+
"""Browser API namespace (async)."""
|
|
81
|
+
|
|
82
|
+
sessions: AsyncBrowserSessionsResource
|
|
83
|
+
|
|
84
|
+
def __init__(self, client: BaseAsyncAPIClient) -> None:
|
|
85
|
+
super().__init__(client)
|
|
86
|
+
self.sessions = AsyncBrowserSessionsResource(client)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Browser API request/response types."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BrowserSessionCreateParams(BaseModel):
|
|
7
|
+
"""Parameters for `browser.sessions.create()`."""
|
|
8
|
+
|
|
9
|
+
url: str | None = Field(
|
|
10
|
+
None,
|
|
11
|
+
description="Target URL for the browser session. If omitted, browser starts at about:blank.",
|
|
12
|
+
)
|
|
13
|
+
timeout_seconds: int | None = Field(
|
|
14
|
+
None,
|
|
15
|
+
description="Inactivity timeout in seconds. If omitted or exceeds plan max, the plan max is used.",
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BrowserSession(BaseModel):
|
|
20
|
+
"""Response from creating a browser session."""
|
|
21
|
+
|
|
22
|
+
session_id: str = Field(..., description="Unique session identifier")
|
|
23
|
+
cdp_url: str = Field(..., description="CDP WebSocket URL for direct browser connection")
|
|
24
|
+
base_url: str = Field(..., description="HTTPS base URL for the browser session")
|
|
@@ -4,7 +4,10 @@ from tinyfish._utils.client import BaseAsyncAPIClient, BaseSyncAPIClient
|
|
|
4
4
|
from tinyfish._utils.client._base import _DEFAULT_TIMEOUT
|
|
5
5
|
|
|
6
6
|
from .agent import AgentResource, AsyncAgentResource
|
|
7
|
+
from .browser import AsyncBrowserResource, BrowserResource
|
|
8
|
+
from .fetch import AsyncFetchResource, FetchResource
|
|
7
9
|
from .runs import AsyncRunsResource, RunsResource
|
|
10
|
+
from .search import AsyncSearchResource, SearchResource
|
|
8
11
|
|
|
9
12
|
__all__ = ["TinyFish", "AsyncTinyFish"]
|
|
10
13
|
|
|
@@ -21,7 +24,10 @@ class TinyFish(BaseSyncAPIClient):
|
|
|
21
24
|
# __init__ assignments below — which means no autocomplete for users who just
|
|
22
25
|
# did `pip install tinyfish`.
|
|
23
26
|
agent: AgentResource
|
|
27
|
+
browser: BrowserResource
|
|
28
|
+
fetch: FetchResource
|
|
24
29
|
runs: RunsResource
|
|
30
|
+
search: SearchResource
|
|
25
31
|
|
|
26
32
|
def __init__(
|
|
27
33
|
self,
|
|
@@ -42,7 +48,10 @@ class TinyFish(BaseSyncAPIClient):
|
|
|
42
48
|
)
|
|
43
49
|
|
|
44
50
|
self.agent = AgentResource(self)
|
|
51
|
+
self.browser = BrowserResource(self)
|
|
52
|
+
self.fetch = FetchResource(self)
|
|
45
53
|
self.runs = RunsResource(self)
|
|
54
|
+
self.search = SearchResource(self)
|
|
46
55
|
|
|
47
56
|
|
|
48
57
|
class AsyncTinyFish(BaseAsyncAPIClient):
|
|
@@ -52,7 +61,10 @@ class AsyncTinyFish(BaseAsyncAPIClient):
|
|
|
52
61
|
# checkers recognise these as AsyncAgentResource / AsyncRunsResource (not
|
|
53
62
|
# the sync variants) when resolving types from an installed package.
|
|
54
63
|
agent: AsyncAgentResource
|
|
64
|
+
browser: AsyncBrowserResource
|
|
65
|
+
fetch: AsyncFetchResource
|
|
55
66
|
runs: AsyncRunsResource
|
|
67
|
+
search: AsyncSearchResource
|
|
56
68
|
|
|
57
69
|
def __init__(
|
|
58
70
|
self,
|
|
@@ -73,4 +85,7 @@ class AsyncTinyFish(BaseAsyncAPIClient):
|
|
|
73
85
|
)
|
|
74
86
|
|
|
75
87
|
self.agent = AsyncAgentResource(self)
|
|
88
|
+
self.browser = AsyncBrowserResource(self)
|
|
89
|
+
self.fetch = AsyncFetchResource(self)
|
|
76
90
|
self.runs = AsyncRunsResource(self)
|
|
91
|
+
self.search = AsyncSearchResource(self)
|