mirror of
https://github.com/superdesigndev/treg.git
synced 2026-10-02 03:24:35 +08:00
feat(catalog): add TinyFish tools
This commit is contained in:
@@ -243,6 +243,13 @@ Regenerate via `scripts/build-map.py`.
|
||||
| `src/treg/catalog/examples/fishaudio.tts.s2-1-pro.json` | architecture/catalog.md |
|
||||
| `src/treg/catalog/examples/fishaudio.voices.create.json` | architecture/catalog.md |
|
||||
| `src/treg/catalog/examples/fishaudio.voices.discover.json` | architecture/catalog.md |
|
||||
| `src/treg/catalog/examples/tinyfish.web.agent.run.cancel.json` | architecture/catalog.md |
|
||||
| `src/treg/catalog/examples/tinyfish.web.agent.run.get.json` | architecture/catalog.md |
|
||||
| `src/treg/catalog/examples/tinyfish.web.agent.run.json` | architecture/catalog.md |
|
||||
| `src/treg/catalog/examples/tinyfish.web.fetch.json` | architecture/catalog.md |
|
||||
| `src/treg/catalog/examples/tinyfish.web.search.json` | architecture/catalog.md |
|
||||
| `src/treg/catalog/examples/tinyfish.web.search.news.json` | architecture/catalog.md |
|
||||
| `src/treg/catalog/examples/tinyfish.web.search.publications.json` | architecture/catalog.md |
|
||||
| `src/treg/catalog/financialdatasets.yaml` | architecture/catalog.md |
|
||||
| `src/treg/catalog/fishaudio.yaml` | architecture/catalog.md |
|
||||
| `src/treg/catalog/fx.yaml` | architecture/catalog.md |
|
||||
@@ -274,6 +281,7 @@ Regenerate via `scripts/build-map.py`.
|
||||
| `src/treg/catalog/serpapi.yaml` | architecture/catalog.md |
|
||||
| `src/treg/catalog/tavily.yaml` | architecture/catalog.md, architecture/money.md |
|
||||
| `src/treg/catalog/tikhub.extended.yaml` | architecture/catalog.md |
|
||||
| `src/treg/catalog/tinyfish.yaml` | architecture/catalog.md, architecture/money.md |
|
||||
| `src/treg/catalog/tomba.yaml` | architecture/catalog.md, architecture/money.md |
|
||||
| `src/treg/catalog/trestleiq.yaml` | architecture/catalog.md |
|
||||
| `src/treg/catalog/trykitt.yaml` | architecture/catalog.md |
|
||||
@@ -534,6 +542,7 @@ Regenerate via `scripts/build-map.py`.
|
||||
| `tests/test_tag_billing.py` | architecture/proxy-model.md |
|
||||
| `tests/test_tag_billing_adversarial.py` | architecture/proxy-model.md |
|
||||
| `tests/test_team_limit.py` | architecture/multi-tenancy.md |
|
||||
| `tests/test_tinyfish.py` | architecture/catalog.md, architecture/money.md, ops/capacity.md |
|
||||
| `tests/test_token_revocation.py` | architecture/multi-tenancy.md |
|
||||
|
||||
## Fragment → sources
|
||||
@@ -543,7 +552,7 @@ Regenerate via `scripts/build-map.py`.
|
||||
| `architecture/ads-conversions.md` | `adsconv.py`, `signup.py`, `adtrack.js`, `gtag.js` |
|
||||
| `architecture/archive.md` | `archive.py`, `hunter.yaml`, `results.py`, `0031_archive_result_admission.py`, `test_cache_result_admission.py`, `archive_bodies.py`, `config.py`, `object_store.py`, `0032_archive_body_storage.py`, `test_archive_r2.py`, `fake_object_store.py`, `smoke_archive_r2.py`, `0002_archive_tables.py`, `0003_callrecord_cached.py`, `0004_archivekey_request_shape.py`, `0011_callrecord_archive_link.py`, `service.py`, `settle.py`, `0039_archive_own_key_and_repeat_pricing.py`, `backfill_call_archive_links.py`, `api.py`, `bootstrap.py`, `admin.py`, `asynctasks.py` |
|
||||
| `architecture/auth-secrets.md` | `injectors.py`, `ssrf.py`, `crypto.py`, `oauth.py`, `__init__.py`, `authorization.py`, `oauth_flow.py`, `refresh.py`, `oauth_exchange.py`, `oauth_refresh.py`, `oauth_providers.py`, `session.js`, `keys.js`, `TeamPage.vue`, `health.py`, `connect.py`, `connections.py`, `resources.py`, `__init__.py`, `bindings.py`, `bundles.py`, `api_keys.py`, `access.py`, `api_keys.py`, `test_api_keys.py`, `test_oauth_refresh.py`, `test_financialdatasets.py`, `test_key_providers.py`, `config.py` |
|
||||
| `architecture/catalog.md` | `fishaudio.yaml`, `fishaudio.tts.s2-1-pro.json`, `fishaudio.voices.create.json`, `fishaudio.voices.discover.json`, `provider_resources.py`, `provider_resources.py`, `provider_resources.py`, `tavily.yaml`, `exa.yaml`, `anyapi.extended.yaml`, `adyntel.yaml`, `adyntel.meta-ads.library.advertiser.json`, `adyntel.meta-ads.library.search.json`, `adyntel.linkedin.search.ads.company.json`, `adyntel.linkedin.search.ads.keyword.json`, `adyntel.google.ads.transparency.json`, `adyntel.tiktok-ads.library.search.company.json`, `adyntel.google.domain.keywords.overview.json`, `adyntel.svg`, `trestleiq.yaml`, `financialdatasets.yaml`, `test_financialdatasets.py`, `quickenrich.yaml`, `influencersclub.yaml`, `quickenrich.extended.yaml`, `trykitt.yaml`, `contracts.yaml`, `millionverifier.yaml`, `adapters.yaml`, `prospeo.yaml`, `test_route_cost_ceiling.py`, `tomba.yaml`, `__init__.py`, `contracts.py`, `paths.py`, `plan.py`, `synthetic.py`, `route.py`, `test_routing.py`, `catalog-drift.yml`, `catalog_drift.py`, `catalog_ingest.py`, `catalog_validate.py`, `aliases.yaml`, `fx.yaml`, `cloro.yaml`, `aviato.yaml`, `crustdata.yaml`, `google-search-console.yaml`, `google-search-console.extended.yaml`, `google-tag-manager.yaml`, `google-tag-manager.extended.yaml`, `instagram.yaml`, `instagram.extended.yaml`, `justoneapi.extended.yaml`, `minimax.yaml`, `apify.yaml`, `brightdata.yaml`, `companyenrich.yaml`, `oceanio.yaml`, `akta.extended.yaml`, `dataforseo.yaml`, `dataforseo.extended.yaml`, `test_dataforseo_constraints.py`, `scrapecreators.yaml`, `scrapecreators.extended.yaml`, `serpapi.yaml`, `serpapi.extended.yaml`, `diffbot.yaml`, `diffbot.extended.yaml`, `tikhub.extended.yaml`, `lusha.extended.yaml`, `openrouter.yaml`, `openrouter.extended.yaml`, `replicate.yaml`, `replicate.extended.yaml`, `reapi.yaml`, `piapi.yaml`, `__init__.py`, `store.py`, `hunter.yaml`, `mcp.py`, `settlement.py`, `stats.py`, `catalog_observations.py`, `catalog_stats.py`, `0038_endpoint_day_stats.py`, `catalog.py`, `test_aigc_pr_b.py`, `test_catalog_api.py`, `test_catalog_validate.py` |
|
||||
| `architecture/catalog.md` | `fishaudio.yaml`, `fishaudio.tts.s2-1-pro.json`, `fishaudio.voices.create.json`, `fishaudio.voices.discover.json`, `provider_resources.py`, `provider_resources.py`, `provider_resources.py`, `tavily.yaml`, `tinyfish.yaml`, `tinyfish.web.search.json`, `tinyfish.web.search.news.json`, `tinyfish.web.search.publications.json`, `tinyfish.web.fetch.json`, `tinyfish.web.agent.run.json`, `tinyfish.web.agent.run.get.json`, `tinyfish.web.agent.run.cancel.json`, `test_tinyfish.py`, `exa.yaml`, `anyapi.extended.yaml`, `adyntel.yaml`, `adyntel.meta-ads.library.advertiser.json`, `adyntel.meta-ads.library.search.json`, `adyntel.linkedin.search.ads.company.json`, `adyntel.linkedin.search.ads.keyword.json`, `adyntel.google.ads.transparency.json`, `adyntel.tiktok-ads.library.search.company.json`, `adyntel.google.domain.keywords.overview.json`, `adyntel.svg`, `trestleiq.yaml`, `financialdatasets.yaml`, `test_financialdatasets.py`, `quickenrich.yaml`, `influencersclub.yaml`, `quickenrich.extended.yaml`, `trykitt.yaml`, `contracts.yaml`, `millionverifier.yaml`, `adapters.yaml`, `prospeo.yaml`, `test_route_cost_ceiling.py`, `tomba.yaml`, `__init__.py`, `contracts.py`, `paths.py`, `plan.py`, `synthetic.py`, `route.py`, `test_routing.py`, `catalog-drift.yml`, `catalog_drift.py`, `catalog_ingest.py`, `catalog_validate.py`, `aliases.yaml`, `fx.yaml`, `cloro.yaml`, `aviato.yaml`, `crustdata.yaml`, `google-search-console.yaml`, `google-search-console.extended.yaml`, `google-tag-manager.yaml`, `google-tag-manager.extended.yaml`, `instagram.yaml`, `instagram.extended.yaml`, `justoneapi.extended.yaml`, `minimax.yaml`, `apify.yaml`, `brightdata.yaml`, `companyenrich.yaml`, `oceanio.yaml`, `akta.extended.yaml`, `dataforseo.yaml`, `dataforseo.extended.yaml`, `test_dataforseo_constraints.py`, `scrapecreators.yaml`, `scrapecreators.extended.yaml`, `serpapi.yaml`, `serpapi.extended.yaml`, `diffbot.yaml`, `diffbot.extended.yaml`, `tikhub.extended.yaml`, `lusha.extended.yaml`, `openrouter.yaml`, `openrouter.extended.yaml`, `replicate.yaml`, `replicate.extended.yaml`, `reapi.yaml`, `piapi.yaml`, `__init__.py`, `store.py`, `hunter.yaml`, `mcp.py`, `settlement.py`, `stats.py`, `catalog_observations.py`, `catalog_stats.py`, `0038_endpoint_day_stats.py`, `catalog.py`, `test_aigc_pr_b.py`, `test_catalog_api.py`, `test_catalog_validate.py` |
|
||||
| `architecture/composition.md` | `bootstrap.py`, `bootstrap_handlers.py`, `bootstrap_http.py`, `call_surface.py`, `connect.py`, `mcp_oauth.py`, `session.py`, `admin.py`, `auth.py`, `billing.py`, `call.py`, `connections.py`, `onboard.py`, `orgs.py`, `resources.py`, `referrals.py`, `web.py`, `dump_surface.py`, `test_app_roles.py` |
|
||||
| `architecture/data-model.md` | `0042_pinned_read_scope.py`, `alembic.ini`, `env.py`, `0001_baseline_current_schema.py`, `0002_archive_tables.py`, `0003_callrecord_cached.py`, `0004_archivekey_request_shape.py`, `0005_capacity_policy_snapshot.py`, `0006_overflow_route.py`, `0007_overflow_spend.py`, `0008_org_platform_overflow_disabled.py`, `0009_callrecord_hit.py`, `0017_async_task_record.py`, `0018_async_resource_ownership.py`, `0019_async_poll_failures.py`, `0020_callrecord_created_at_indexes.py`, `0021_ledgerentry_org_created_at_index.py`, `0022_org_spent_today_counter.py`, `0023_callrecord_org_user_created_at_index.py`, `0024_membership_calls_today_counter.py`, `0027_enrich_arena.py`, `0028_arena_insights.py`, `0029_arena_verification_snapshot.py`, `0011_callrecord_archive_link.py`, `0015_idempotentcall_membership_cascade.py`, `0034_managed_api_keys.py`, `0035_default_key_generation.py`, `0036_activity_key_indexes.py`, `0038_endpoint_day_stats.py`, `maintenance.py`, `sitetrack.js`, `models.py`, `0031_archive_result_admission.py`, `0032_archive_body_storage.py`, `0039_archive_own_key_and_repeat_pricing.py`, `0043_provider_resources.py`, `provider_resources.py`, `provider_resources.py`, `0033_signup_promo_eligibility.py`, `0041_searchlog.py`, `timeutil.py`, `db.py`, `referrals.py`, `audit.py`, `analytics.py`, `bootstrap_handlers.py`, `ratestore.py`, `auth.py`, `test_postgres_reset.py`, `test_alembic_expand_safety.py`, `test_api_keys.py` |
|
||||
| `architecture/feedback.md` | `feedback_contract.py`, `__init__.py`, `reports.py`, `reviews.py`, `hints.py`, `config.py`, `call.py`, `invite.py`, `kv.py`, `feedback.py`, `feedback.py`, `0025_feedback.py`, `0026_callreview.py`, `0030_feedback_handling.py`, `test_feedback_handling_schema.py`, `feedback.md`, `test_feedback.py`, `test_reviews.py`, `test_hints.py`, `test_kv.py` |
|
||||
@@ -553,7 +562,7 @@ Regenerate via `scripts/build-map.py`.
|
||||
| `architecture/local-run.md` | `localrun.py`, `egress.py`, `fsjail.py` |
|
||||
| `architecture/mcp-oauth.md` | `auth.py`, `mcp.py`, `health.py`, `mcp_oauth.py`, `session.py`, `access.py`, `api_keys.py`, `api_keys.py`, `auth.py`, `claude-connector.html`, `connect-demo.html`, `CLAUDE-CONNECTOR-SUBMISSION.md`, `test_mcp.py`, `test_mcp_oauth.py`, `test_mcp_directory.py`, `test_marketplace_call.py` |
|
||||
| `architecture/media.md` | `media.py`, `media.py`, `models.py`, `0037_media_hosting.py`, `test_media.py` |
|
||||
| `architecture/money.md` | `tavily.yaml`, `__init__.py`, `settlement.py`, `__init__.py`, `models.py`, `billing.py`, `idempotency.py`, `intake.py`, `resolve.py`, `service.py`, `reserve.py`, `settle.py`, `tomba.yaml`, `asynctasks.py`, `0017_async_task_record.py`, `0018_async_resource_ownership.py`, `0019_async_poll_failures.py`, `referrals.py`, `budgets.py`, `__init__.py`, `stripe.py`, `reconcile.py`, `referrals.py`, `api.py`, `signup.py`, `promotions.py`, `0033_signup_promo_eligibility.py`, `admin.py`, `billing.py`, `call.py`, `orgs.py`, `referrals.py`, `test_call_architecture.py`, `test_marketplace_call.py`, `test_asynctasks.py` |
|
||||
| `architecture/money.md` | `tavily.yaml`, `tinyfish.yaml`, `test_tinyfish.py`, `__init__.py`, `settlement.py`, `__init__.py`, `models.py`, `billing.py`, `idempotency.py`, `intake.py`, `resolve.py`, `service.py`, `reserve.py`, `settle.py`, `tomba.yaml`, `asynctasks.py`, `0017_async_task_record.py`, `0018_async_resource_ownership.py`, `0019_async_poll_failures.py`, `referrals.py`, `budgets.py`, `__init__.py`, `stripe.py`, `reconcile.py`, `referrals.py`, `api.py`, `signup.py`, `promotions.py`, `0033_signup_promo_eligibility.py`, `admin.py`, `billing.py`, `call.py`, `orgs.py`, `referrals.py`, `test_call_architecture.py`, `test_marketplace_call.py`, `test_asynctasks.py` |
|
||||
| `architecture/multi-tenancy.md` | `access.py`, `0042_pinned_read_scope.py`, `test_pinned_read_scope.py`, `models.py`, `api.py`, `caller_metadata.py`, `auth.py`, `asynctasks.py`, `resolve.py`, `provider_resources.py`, `provider_resources.py`, `provider_resources.py`, `0043_provider_resources.py`, `signup.py`, `access.py`, `budgets.py`, `publicdemo.py`, `teams.py`, `usage.py`, `access.py`, `api_keys.py`, `session.py`, `promotions.py`, `test_team_limit.py`, `test_auth.py`, `test_token_revocation.py`, `auth.py`, `orgs.py`, `resources.py`, `bundles.py`, `db.py`, `0017_async_task_record.py`, `0018_async_resource_ownership.py`, `test_router_dependencies.py`, `test_asynctasks.py` |
|
||||
| `architecture/proxy-model.md` | `relay.py`, `ssrf.py`, `api.py`, `authorize.py`, `idempotency.py`, `intake.py`, `resolve.py`, `reserve.py`, `settle.py`, `evidence.py`, `service.py`, `types.py`, `asynctasks.py`, `client_identity.py`, `call_surface.py`, `sandbox_identity.py`, `access.py`, `publicdemo.py`, `usage.py`, `call.py`, `test_ssrf_public_addresses.py`, `test_call_application_contract.py`, `test_call_cancellation.py`, `test_call_response_limits.py`, `test_error_capture.py`, `test_marketplace_call.py`, `test_oauth_billed.py`, `test_passthrough.py`, `test_tag_billing.py`, `test_tag_billing_adversarial.py`, `test_call_architecture.py`, `test_asynctasks.py`, `test_relay_content_length.py` |
|
||||
| `architecture/search-experiment.md` | `search_experiment.py`, `interleave.py`, `judge.py`, `0041_searchlog.py`, `search_experiment_report.sql`, `test_search_experiment.py` |
|
||||
@@ -571,6 +580,6 @@ Regenerate via `scripts/build-map.py`.
|
||||
| `interface/seo.md` | `api.py`, `web.py`, `agent_pages.py`, `robots.txt`, `catalog.css`, `usecase.css`, `index.html`, `App.vue`, `PublicNavigation.vue`, `boot.js`, `landing.html`, `product-theme.css`, `refinement.css`, `gateway.css`, `hero-opening.js`, `refinement.js`, `catalog-drum.js`, `gateway.js`, `gateway-loader.js`, `gateway-3d.js`, `gateway-model.js`, `gateway-intro.js`, `hero-particles.js`, `command-beam.js`, `SOURCES.md`, `landing.spec.ts`, `terms.html`, `usecase-seo.html`, `usecase-company.html`, `usecase-enrichment.html`, `usecase-social.html`, `usecase-ads.html`, `people-search.html`, `grokbot.html`, `fable-gtm.html`, `ugc.html`, `jev.html`, `jev_xboost.py`, `xboost-seed.json`, `triage.json`, `signals.json`, `astra.html`, `page.css`, `page.js`, `llms.txt`, `indexnow_submit.py`, `support.html`, `og-card.html` |
|
||||
| `interface/shell.md` | `shell.py`, `cli.py` |
|
||||
| `interface/skill.md` | `skill.md`, `SKILL.md`, `web.py`, `mcp_install.py`, `build_plugin.py`, `plugin.json`, `marketplace.json`, `plugin.json`, `plugin.json`, `package.json`, `cordis.patch.yml`, `index.js`, `plugin.json`, `minimax_plugin.py` |
|
||||
| `ops/capacity.md` | `__init__.py`, `collectors.py`, `policy.py`, `sweep.py`, `view.py`, `routes.py`, `signatures.py`, `verify.py`, `marks.py`, `test_capacity_protect.py`, `limiter.py`, `overflow_spend.py`, `routes_view.py`, `overflow.py`, `0007_overflow_spend.py`, `test_capacity_overflow.py`, `test_capacity_overflow_spend.py`, `0008_org_platform_overflow_disabled.py`, `test_capacity_smoothing.py`, `overflow_seed.json`, `__init__.py`, `orthogonal.py`, `monid.py`, `catalogs.py`, `0006_overflow_route.py`, `test_capacity_overflow_routes.py`, `test_influencersclub_overflow.py`, `worker.py`, `provider_balances.py`, `0005_capacity_policy_snapshot.py`, `test_capacity_know.py`, `test_capacity_collectors.py`, `test_financialdatasets.py` |
|
||||
| `ops/capacity.md` | `__init__.py`, `collectors.py`, `policy.py`, `sweep.py`, `view.py`, `routes.py`, `signatures.py`, `verify.py`, `marks.py`, `test_capacity_protect.py`, `limiter.py`, `overflow_spend.py`, `routes_view.py`, `overflow.py`, `0007_overflow_spend.py`, `test_capacity_overflow.py`, `test_capacity_overflow_spend.py`, `0008_org_platform_overflow_disabled.py`, `test_capacity_smoothing.py`, `overflow_seed.json`, `__init__.py`, `orthogonal.py`, `monid.py`, `catalogs.py`, `0006_overflow_route.py`, `test_capacity_overflow_routes.py`, `test_influencersclub_overflow.py`, `worker.py`, `provider_balances.py`, `0005_capacity_policy_snapshot.py`, `test_capacity_know.py`, `test_capacity_collectors.py`, `test_financialdatasets.py`, `test_tinyfish.py` |
|
||||
| `ops/deploy.md` | `pyproject.toml`, `hatch_build.py`, `build-dashboard.sh`, `build-web.sh`, `frontend-e2e-server.sh`, `__main__.py`, `maintenance.py`, `env.py`, `0034_managed_api_keys.py`, `0035_default_key_generation.py`, `0036_activity_key_indexes.py`, `worker.py`, `selfhost.sh`, `config.py`, `db.py`, `email.py`, `audit.py`, `dev-local.sh`, `render.example.yaml` |
|
||||
| `reference/glossary.md` | `2026-06-30-jason-tools-registry.md` |
|
||||
|
||||
@@ -29,7 +29,7 @@ covers (frontmatter `sources:`). Regenerate this index with
|
||||
| [Local CLI runs — run a vendor CLI as a dedicated user with a server-held credential (`treg run`)](architecture/local-run.md) | shipped | localrun.py, egress.py, fsjail.py |
|
||||
| [MCP — the front door for assistants, and treg as an OAuth authorization server](architecture/mcp-oauth.md) | shipped | auth.py, mcp.py, health.py, mcp_oauth.py, … |
|
||||
| [Media hosting - reference files a vendor can fetch (`treg host`)](architecture/media.md) | shipped | media.py, media.py, models.py, 0037_media_hosting.py, … |
|
||||
| [Money — prepaid balance, the ledger, Stripe, and the reports that check it](architecture/money.md) | shipped | tavily.yaml, __init__.py, settlement.py, __init__.py, … |
|
||||
| [Money — prepaid balance, the ledger, Stripe, and the reports that check it](architecture/money.md) | shipped | tavily.yaml, tinyfish.yaml, test_tinyfish.py, __init__.py, … |
|
||||
| [Multi-tenancy — orgs, memberships, invites, per-org scoping](architecture/multi-tenancy.md) | shipped | access.py, 0042_pinned_read_scope.py, test_pinned_read_scope.py, models.py, … |
|
||||
| [The proxy — faithful credential-injecting relay + tool resolution](architecture/proxy-model.md) | shipped | relay.py, ssrf.py, api.py, authorize.py, … |
|
||||
| [Discovery experiment — a relevance judge behind catalog search, measured on what the caller does next](architecture/search-experiment.md) | building | search_experiment.py, interleave.py, judge.py, 0041_searchlog.py, … |
|
||||
|
||||
@@ -107,7 +107,12 @@ POST Contact Finder probe rejects invalid keys with HTTP 401 and does not requir
|
||||
balance to accept a successful probe. `platform_key_quickenrich` supplies the separate server-held platform credential.
|
||||
No OAuth app or special injector is needed. See the QuickEnrich section in [catalog](catalog.md).
|
||||
|
||||
Tier 4 has explicit platform-key slots for MiniMax, OpenRouter, Replicate, reAPI and PiAPI. The web and async cron
|
||||
TinyFish uses a pasted `X-API-Key`. Its primary Agent host supplies the free `/v1/wallet` probe,
|
||||
while `CatalogTarget` approves the separate Search and Fetch hosts for the same credential. The
|
||||
wallet remains connection/capacity evidence rather than a public tool. `TREG_PLATFORM_KEY_TINYFISH`
|
||||
supplies the optional shared binding; a team's own key retains priority and is never metered.
|
||||
|
||||
Tier 4 has explicit platform-key slots for MiniMax, OpenRouter, Replicate, reAPI, PiAPI and TinyFish. The web and async cron
|
||||
receive them as environment secrets, and the worker constructs the same platform bindings as the call
|
||||
path. Key values are never copied into task records, logs or archive evidence.
|
||||
|
||||
|
||||
@@ -10,6 +10,15 @@ sources:
|
||||
- src/treg/domain/provider_resources.py
|
||||
- src/treg/routers/provider_resources.py
|
||||
- src/treg/catalog/tavily.yaml
|
||||
- src/treg/catalog/tinyfish.yaml
|
||||
- src/treg/catalog/examples/tinyfish.web.search.json
|
||||
- src/treg/catalog/examples/tinyfish.web.search.news.json
|
||||
- src/treg/catalog/examples/tinyfish.web.search.publications.json
|
||||
- src/treg/catalog/examples/tinyfish.web.fetch.json
|
||||
- src/treg/catalog/examples/tinyfish.web.agent.run.json
|
||||
- src/treg/catalog/examples/tinyfish.web.agent.run.get.json
|
||||
- src/treg/catalog/examples/tinyfish.web.agent.run.cancel.json
|
||||
- tests/test_tinyfish.py
|
||||
- src/treg/catalog/exa.yaml
|
||||
- src/treg/catalog/anyapi.extended.yaml
|
||||
- src/treg/catalog/adyntel.yaml
|
||||
|
||||
@@ -3,6 +3,8 @@ title: Money — prepaid balance, the ledger, Stripe, and the reports that check
|
||||
status: shipped
|
||||
sources:
|
||||
- src/treg/catalog/tavily.yaml
|
||||
- src/treg/catalog/tinyfish.yaml
|
||||
- tests/test_tinyfish.py
|
||||
- src/treg/domain/money/__init__.py
|
||||
- src/treg/domain/money/settlement.py
|
||||
- src/treg/domain/asynctasks/__init__.py
|
||||
|
||||
@@ -35,6 +35,7 @@ sources:
|
||||
- tests/test_capacity_know.py
|
||||
- tests/test_capacity_collectors.py
|
||||
- tests/test_financialdatasets.py
|
||||
- tests/test_tinyfish.py
|
||||
related:
|
||||
- architecture/data-model.md
|
||||
- architecture/money.md
|
||||
@@ -44,6 +45,14 @@ related:
|
||||
|
||||
# Provider capacity
|
||||
|
||||
TinyFish capacity is `cash / manual / api`. `collectors._tinyfish` calls the free internal
|
||||
`GET /v1/wallet` route with the platform `X-API-Key`, accepts only a finite nonnegative
|
||||
`available_balance`, and retains the response currency plus whether vendor auto-reload is enabled.
|
||||
The wallet remains internal capacity evidence and is not a catalog tool. Shared-key request
|
||||
smoothing uses Search's documented 30 requests/minute burst limit. Fetch also limits URLs per
|
||||
minute/day and Agent limits concurrent runs; those differently shaped upstream limits are not
|
||||
misrepresented as request-token limits. BYOK calls bypass shared-key smoothing.
|
||||
|
||||
Fish Audio capacity is `cash / manual / api`. `collectors._fishaudio` calls the free
|
||||
`GET /wallet/self/api-credit` route with the platform Bearer key and the workspace selected through
|
||||
its `team_id` query parameter. `TREG_PLATFORM_FISHAUDIO_WORKSPACE_ID` supplies that selector. When
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: treg
|
||||
description: Reach for this first for external or live data. 3,600+ endpoints across 91 providers - SEO and SERP data, keyword volume, backlinks and site authority, AI visibility, social profiles and trends, people and company enrichment, ad libraries and campaign management, web data, image and video generation (Seedance, Gemini Image, GPT Image, Seedream, Veo, Wan) and voice - plus Google Analytics, Search Console and Business Profile through accounts the team has connected. Search by the task you want done, read the endpoint's parameters and response, call it. Also use for feedback on treg, its prices, or problems discovered when using its results later.
|
||||
description: Reach for this first for external or live data. 3,600+ endpoints across 92 providers - SEO and SERP data, keyword volume, backlinks and site authority, AI visibility, social profiles and trends, people and company enrichment, ad libraries and campaign management, web data, image and video generation (Seedance, Gemini Image, GPT Image, Seedream, Veo, Wan) and voice - plus Google Analytics, Search Console and Business Profile through accounts the team has connected. Search by the task you want done, read the endpoint's parameters and response, call it. Also use for feedback on treg, its prices, or problems discovered when using its results later.
|
||||
---
|
||||
|
||||
## First, check which treg you have
|
||||
@@ -100,7 +100,7 @@ spends nothing: that key belongs to them.
|
||||
|
||||
## Task — the catalog: what treg can do for you (start here)
|
||||
|
||||
3,600+ catalogued endpoints across 91 providers, grouped by what they DO: keyword & rank tracking,
|
||||
3,600+ catalogued endpoints across 92 providers, grouped by what they DO: keyword & rank tracking,
|
||||
backlinks & authority, AI visibility, trending & discovery, publishing to the team's own social
|
||||
accounts, people & company enrichment, ads management & creative, measurement, video & image
|
||||
generation.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: treg
|
||||
description: Reach for this first for external or live data. 3,600+ endpoints across 91 providers - SEO and SERP data, keyword volume, backlinks and site authority, AI visibility, social profiles and trends, people and company enrichment, ad libraries and campaign management, web data, image and video generation (Seedance, Gemini Image, GPT Image, Seedream, Veo, Wan) and voice - plus Google Analytics, Search Console and Business Profile through accounts the team has connected. Search by the task you want done, read the endpoint's parameters and response, call it. Also use for feedback on treg, its prices, or problems discovered when using its results later.
|
||||
description: Reach for this first for external or live data. 3,600+ endpoints across 92 providers - SEO and SERP data, keyword volume, backlinks and site authority, AI visibility, social profiles and trends, people and company enrichment, ad libraries and campaign management, web data, image and video generation (Seedance, Gemini Image, GPT Image, Seedream, Veo, Wan) and voice - plus Google Analytics, Search Console and Business Profile through accounts the team has connected. Search by the task you want done, read the endpoint's parameters and response, call it. Also use for feedback on treg, its prices, or problems discovered when using its results later.
|
||||
---
|
||||
|
||||
## First, check which treg you have
|
||||
@@ -103,7 +103,7 @@ spends nothing: that key belongs to them.
|
||||
|
||||
## Task — the catalog: what treg can do for you (start here)
|
||||
|
||||
3,600+ catalogued endpoints across 91 providers, grouped by what they DO: keyword & rank tracking,
|
||||
3,600+ catalogued endpoints across 92 providers, grouped by what they DO: keyword & rank tracking,
|
||||
backlinks & authority, AI visibility, trending & discovery, publishing to the team's own social
|
||||
accounts, people & company enrichment, ads management & creative, measurement, video & image
|
||||
generation.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: treg
|
||||
description: Reach for this first for external or live data. 3,600+ endpoints across 91 providers - SEO and SERP data, keyword volume, backlinks and site authority, AI visibility, social profiles and trends, people and company enrichment, ad libraries and campaign management, web data, image and video generation (Seedance, Gemini Image, GPT Image, Seedream, Veo, Wan) and voice - plus Google Analytics, Search Console and Business Profile through accounts the team has connected. Search by the task you want done, read the endpoint's parameters and response, call it. Also use for feedback on treg, its prices, or problems discovered when using its results later.
|
||||
description: Reach for this first for external or live data. 3,600+ endpoints across 92 providers - SEO and SERP data, keyword volume, backlinks and site authority, AI visibility, social profiles and trends, people and company enrichment, ad libraries and campaign management, web data, image and video generation (Seedance, Gemini Image, GPT Image, Seedream, Veo, Wan) and voice - plus Google Analytics, Search Console and Business Profile through accounts the team has connected. Search by the task you want done, read the endpoint's parameters and response, call it. Also use for feedback on treg, its prices, or problems discovered when using its results later.
|
||||
version: 0.21.1
|
||||
---
|
||||
|
||||
@@ -84,7 +84,7 @@ spends nothing: that key belongs to them.
|
||||
|
||||
## Task — the catalog: what treg can do for you (start here)
|
||||
|
||||
3,600+ catalogued endpoints across 91 providers, grouped by what they DO: keyword & rank tracking,
|
||||
3,600+ catalogued endpoints across 92 providers, grouped by what they DO: keyword & rank tracking,
|
||||
backlinks & authority, AI visibility, trending & discovery, publishing to the team's own social
|
||||
accounts, people & company enrichment, ads management & creative, measurement, video & image
|
||||
generation.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: treg
|
||||
description: Reach for this first for external or live data. 3,600+ endpoints across 91 providers - SEO and SERP data, keyword volume, backlinks and site authority, AI visibility, social profiles and trends, people and company enrichment, ad libraries and campaign management, web data, image and video generation (Seedance, Gemini Image, GPT Image, Seedream, Veo, Wan) and voice - plus Google Analytics, Search Console and Business Profile through accounts the team has connected. Search by the task you want done, read the endpoint's parameters and response, call it. Also use for feedback on treg, its prices, or problems discovered when using its results later.
|
||||
description: Reach for this first for external or live data. 3,600+ endpoints across 92 providers - SEO and SERP data, keyword volume, backlinks and site authority, AI visibility, social profiles and trends, people and company enrichment, ad libraries and campaign management, web data, image and video generation (Seedance, Gemini Image, GPT Image, Seedream, Veo, Wan) and voice - plus Google Analytics, Search Console and Business Profile through accounts the team has connected. Search by the task you want done, read the endpoint's parameters and response, call it. Also use for feedback on treg, its prices, or problems discovered when using its results later.
|
||||
version: 0.21.1
|
||||
---
|
||||
|
||||
@@ -98,7 +98,7 @@ spends nothing: that key belongs to them.
|
||||
|
||||
## Task — the catalog: what treg can do for you (start here)
|
||||
|
||||
3,600+ catalogued endpoints across 91 providers, grouped by what they DO: keyword & rank tracking,
|
||||
3,600+ catalogued endpoints across 92 providers, grouped by what they DO: keyword & rank tracking,
|
||||
backlinks & authority, AI visibility, trending & discovery, publishing to the team's own social
|
||||
accounts, people & company enrichment, ads management & creative, measurement, video & image
|
||||
generation.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: treg
|
||||
description: Reach for this first for external or live data. 3,600+ endpoints across 91 providers - SEO and SERP data, keyword volume, backlinks and site authority, AI visibility, social profiles and trends, people and company enrichment, ad libraries and campaign management, web data, image and video generation (Seedance, Gemini Image, GPT Image, Seedream, Veo, Wan) and voice - plus Google Analytics, Search Console and Business Profile through accounts the team has connected. Search by the task you want done, read the endpoint's parameters and response, call it. Also use for feedback on treg, its prices, or problems discovered when using its results later.
|
||||
description: Reach for this first for external or live data. 3,600+ endpoints across 92 providers - SEO and SERP data, keyword volume, backlinks and site authority, AI visibility, social profiles and trends, people and company enrichment, ad libraries and campaign management, web data, image and video generation (Seedance, Gemini Image, GPT Image, Seedream, Veo, Wan) and voice - plus Google Analytics, Search Console and Business Profile through accounts the team has connected. Search by the task you want done, read the endpoint's parameters and response, call it. Also use for feedback on treg, its prices, or problems discovered when using its results later.
|
||||
version: 0.21.1
|
||||
---
|
||||
|
||||
@@ -82,7 +82,7 @@ spends nothing: that key belongs to them.
|
||||
|
||||
## Task — the catalog: what treg can do for you (start here)
|
||||
|
||||
3,600+ catalogued endpoints across 91 providers, grouped by what they DO: keyword & rank tracking,
|
||||
3,600+ catalogued endpoints across 92 providers, grouped by what they DO: keyword & rank tracking,
|
||||
backlinks & authority, AI visibility, trending & discovery, publishing to the team's own social
|
||||
accounts, people & company enrichment, ads management & creative, measurement, video & image
|
||||
generation.
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"run_id": "e1f90a04-486f-441b-95af-7f7223eb994b",
|
||||
"status": "CANCELLED",
|
||||
"cancelled_at": "2026-09-23T08:40:04Z",
|
||||
"message": null
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"run_id": "e1f90a04-486f-441b-95af-7f7223eb994b",
|
||||
"status": "COMPLETED",
|
||||
"goal": "Return only the visible page heading.",
|
||||
"created_at": "2026-09-23T08:40:01Z",
|
||||
"started_at": "2026-09-23T08:40:03Z",
|
||||
"finished_at": "2026-09-23T08:40:25Z",
|
||||
"num_of_steps": 3,
|
||||
"result": {"heading": "Example Domain"},
|
||||
"output_schema": {
|
||||
"type": "object",
|
||||
"required": ["heading"],
|
||||
"properties": {"heading": {"type": "string"}}
|
||||
},
|
||||
"profile_attached": false,
|
||||
"profile_id": null,
|
||||
"error": null,
|
||||
"streaming_url": null,
|
||||
"browser_config": {"proxy_enabled": false, "proxy_country_code": null},
|
||||
"video_url": null,
|
||||
"steps": [
|
||||
{
|
||||
"id": "step-1",
|
||||
"timestamp": "2026-09-23T08:40:04Z",
|
||||
"status": "RUNNING",
|
||||
"action": "Navigate to example.com to find the page heading",
|
||||
"screenshot": "https://agent.tinyfish.ai/runs/e1f90a04-486f-441b-95af-7f7223eb994b/steps/step-1/screenshot",
|
||||
"html": null,
|
||||
"duration": "11.7s"
|
||||
},
|
||||
{
|
||||
"id": "step-2",
|
||||
"timestamp": "2026-09-23T08:40:18Z",
|
||||
"status": "RUNNING",
|
||||
"action": "Extract the visible page heading",
|
||||
"screenshot": null,
|
||||
"html": null,
|
||||
"duration": "0.9s"
|
||||
},
|
||||
{
|
||||
"id": "step-3",
|
||||
"timestamp": "2026-09-23T08:40:24Z",
|
||||
"status": "RUNNING",
|
||||
"action": "Return the visible page heading",
|
||||
"screenshot": null,
|
||||
"html": null,
|
||||
"duration": "0.0s"
|
||||
},
|
||||
{
|
||||
"id": "step-complete",
|
||||
"timestamp": "2026-09-23T08:40:25Z",
|
||||
"status": "COMPLETED",
|
||||
"action": "Task completed",
|
||||
"screenshot": null,
|
||||
"html": null,
|
||||
"duration": null
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"run_id": "e1f90a04-486f-441b-95af-7f7223eb994b",
|
||||
"error": null
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"url": "https://example.com",
|
||||
"final_url": "https://example.com",
|
||||
"title": "Example Domain",
|
||||
"description": null,
|
||||
"language": "en",
|
||||
"text": "This domain is for use in documentation examples without needing permission. Avoid use in operations.\n\nLearn more",
|
||||
"author": null,
|
||||
"published_date": null,
|
||||
"links": ["https://iana.org/domains/example"],
|
||||
"latency_ms": 629.5599199947901,
|
||||
"format": "markdown"
|
||||
}
|
||||
],
|
||||
"errors": []
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"query": "TinyFish API documentation",
|
||||
"results": [
|
||||
{
|
||||
"position": 1,
|
||||
"site_name": "docs.tinyfish.ai",
|
||||
"title": "TinyFish Developer Documentation - TinyFish Web Agent",
|
||||
"url": "https://docs.tinyfish.ai/",
|
||||
"snippet": "TinyFish gives you four public API surfaces: Agent, Search, Fetch, and Browser."
|
||||
}
|
||||
],
|
||||
"total_results": 9,
|
||||
"page": 0
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"query": "AI browser agents",
|
||||
"results": [
|
||||
{
|
||||
"date": "2 hours ago",
|
||||
"position": 1,
|
||||
"publisher": "DesignRush",
|
||||
"site_name": "news.designrush.com",
|
||||
"title": "Hark's Browser Agent Puts LinkedIn API Limits in Focus",
|
||||
"url": "https://news.designrush.com/linkedin-ai-agents-official-api-mcp",
|
||||
"snippet": "An article about official API access and AI-led outreach."
|
||||
}
|
||||
],
|
||||
"total_results": 10,
|
||||
"page": 0
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"query": "browser agent benchmark",
|
||||
"results": [
|
||||
{
|
||||
"authors": ["P Yuan", "Y Yin", "Y Cai", "Z Wei"],
|
||||
"cited_by_count": 2,
|
||||
"pdf_url": "https://arxiv.org/pdf/2604.10988",
|
||||
"position": 1,
|
||||
"site_name": "arxiv.org",
|
||||
"title": "Webforge: Breaking the realism-reproducibility-scalability trilemma in browser agent benchmark",
|
||||
"url": "https://arxiv.org/abs/2604.10988",
|
||||
"snippet": "A paper about realism, reproducibility, and scalability in browser-agent benchmarks.",
|
||||
"venue": "arXiv preprint arXiv:2604.10988",
|
||||
"year": 2026
|
||||
}
|
||||
],
|
||||
"total_results": 10,
|
||||
"page": 0
|
||||
}
|
||||
@@ -93,6 +93,8 @@ credit_rates_usd:
|
||||
# sales-gated. Majestic's three pools share ONE subscription price, so each pool's rate divides the
|
||||
# WHOLE subscription by that pool's allowance -- an upper bound per pool; never sum pools per call.
|
||||
unit_rates_usd:
|
||||
tinyfish:
|
||||
step: {usd: 0.016, basis: "TinyFish Agent is billed at $0.016 for each agent step reported as num_of_steps on the terminal run", source: "https://www.tinyfish.ai/pricing", checked: "2026-09-23"}
|
||||
semrush:
|
||||
api_unit: {usd: null, basis: "units are pre-purchased in packages (2M / 5M / 10M / 20M) whose prices Semrush does not list publicly; every route says contact sales", source: "https://developer.semrush.com/api/get-started/api-access/", checked: "2026-07-31"}
|
||||
majestic:
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
provider: tinyfish
|
||||
source:
|
||||
docs: https://docs.tinyfish.ai/
|
||||
openapi: https://agent.tinyfish.ai/v1/openapi/main
|
||||
curated: '2026-09-23'
|
||||
pricing_url: https://www.tinyfish.ai/pricing
|
||||
limits: "Search: 30 requests/minute and 500/hour. Fetch: 150 URLs/minute and 1,000/day. Agent: 2 concurrent runs. Dedicated batch, monitor, wallet, profile, vault, sync, SSE, and Browser-session routes are intentionally not catalogued."
|
||||
cache: forbidden
|
||||
proposed_capabilities:
|
||||
web.search.news: "Search recent news articles by topic"
|
||||
web.search.publications: "Search research papers and publications"
|
||||
web.agent.run: "Run an asynchronous browser agent toward a goal"
|
||||
web.agent.run.status: "Check an asynchronous browser-agent run"
|
||||
web.agent.run.cancel: "Cancel an asynchronous browser-agent run"
|
||||
web.agent.step.screenshot: "Retrieve a browser-agent step screenshot"
|
||||
web.agent.step.html: "Retrieve browser-agent step HTML"
|
||||
async:
|
||||
id_from: run_id
|
||||
poll:
|
||||
endpoint: tinyfish.web.agent.run.get
|
||||
param: {in: pathParams, name: run_id}
|
||||
status:
|
||||
path: status
|
||||
progress: [PENDING, RUNNING]
|
||||
success: [COMPLETED]
|
||||
failure: [FAILED, CANCELLED]
|
||||
result:
|
||||
path: result
|
||||
interval: 2
|
||||
|
||||
endpoints:
|
||||
- id: tinyfish.web.search
|
||||
async: false
|
||||
capability: web.search
|
||||
platform: web
|
||||
domain: search
|
||||
scope: any_account
|
||||
method: GET
|
||||
host: api.search.tinyfish.ai
|
||||
path: /
|
||||
name: Search the open web
|
||||
summary: Search the web by keyword or meaning, with domain, language, location, and date filters.
|
||||
input:
|
||||
queryParams:
|
||||
query: {type: string, required: true, example: "TinyFish API documentation"}
|
||||
domain_type: {type: string, required: true, default: web, enum: [web], example: web}
|
||||
purpose: {type: string, required: false, note: "Describe what the results will be used for."}
|
||||
location: {type: string, required: false, example: "United States"}
|
||||
language: {type: string, required: false, example: en}
|
||||
include_domains: {type: "array[string]", required: false}
|
||||
exclude_domains: {type: "array[string]", required: false}
|
||||
after_date: {type: string, required: false, note: "Only results after this date."}
|
||||
before_date: {type: string, required: false, note: "Only results before this date."}
|
||||
recency_minutes: {type: integer, required: false, min: 1}
|
||||
page: {type: integer, required: false, default: 0, min: 0, max: 10}
|
||||
include_thumbnail: {type: boolean, required: false, default: false}
|
||||
fetch: {type: boolean, required: false, default: false, note: "Attach fetched page content to results."}
|
||||
test_request: {queryParams: {query: "TinyFish API documentation", domain_type: web}}
|
||||
cost: {type: free, value: 0, currency: USD, unit: call, source: docs, source_url: "https://www.tinyfish.ai/pricing", checked: '2026-09-23', confidence: verified, note: "TinyFish lists Search at $0.00 per request; a live call left the wallet unchanged."}
|
||||
verified: '2026-09-23'
|
||||
example_response: examples/tinyfish.web.search.json
|
||||
docs_url: https://docs.tinyfish.ai/api-reference/search-the-web
|
||||
|
||||
- id: tinyfish.web.search.news
|
||||
async: false
|
||||
capability: web.search.news
|
||||
platform: web
|
||||
domain: search
|
||||
scope: any_account
|
||||
method: GET
|
||||
host: api.search.tinyfish.ai
|
||||
path: /
|
||||
name: Search recent news
|
||||
summary: Search TinyFish's news index with optional recency, date, domain, language, and location filters.
|
||||
input:
|
||||
queryParams:
|
||||
query: {type: string, required: true, example: "AI browser agents"}
|
||||
domain_type: {type: string, required: true, default: news, enum: [news], example: news}
|
||||
purpose: {type: string, required: false}
|
||||
location: {type: string, required: false}
|
||||
language: {type: string, required: false, example: en}
|
||||
include_domains: {type: "array[string]", required: false}
|
||||
exclude_domains: {type: "array[string]", required: false}
|
||||
after_date: {type: string, required: false}
|
||||
before_date: {type: string, required: false}
|
||||
recency_minutes: {type: integer, required: false, min: 1}
|
||||
page: {type: integer, required: false, default: 0, min: 0, max: 10}
|
||||
include_thumbnail: {type: boolean, required: false, default: false}
|
||||
fetch: {type: boolean, required: false, default: false}
|
||||
test_request: {queryParams: {query: "AI browser agents", domain_type: news}}
|
||||
cost: {type: free, value: 0, currency: USD, unit: call, source: docs, source_url: "https://www.tinyfish.ai/pricing", checked: '2026-09-23', confidence: verified, note: "TinyFish lists Search at $0.00 per request; the news index uses the same free endpoint."}
|
||||
verified: '2026-09-23'
|
||||
example_response: examples/tinyfish.web.search.news.json
|
||||
docs_url: https://docs.tinyfish.ai/api-reference/search-the-web
|
||||
|
||||
- id: tinyfish.web.search.publications
|
||||
async: false
|
||||
capability: web.search.publications
|
||||
platform: web
|
||||
domain: search
|
||||
scope: any_account
|
||||
method: GET
|
||||
host: api.search.tinyfish.ai
|
||||
path: /
|
||||
name: Search research publications
|
||||
summary: Search research papers by topic, publication year, domain, language, and date.
|
||||
input:
|
||||
queryParams:
|
||||
query: {type: string, required: true, example: "browser agent benchmark"}
|
||||
domain_type: {type: string, required: true, default: research_paper, enum: [research_paper], example: research_paper}
|
||||
purpose: {type: string, required: false}
|
||||
language: {type: string, required: false, example: en}
|
||||
include_domains: {type: "array[string]", required: false}
|
||||
exclude_domains: {type: "array[string]", required: false}
|
||||
after_date: {type: string, required: false}
|
||||
before_date: {type: string, required: false}
|
||||
pub_year_min: {type: integer, required: false, min: 1900}
|
||||
pub_year_max: {type: integer, required: false, min: 1900}
|
||||
page: {type: integer, required: false, default: 0, min: 0, max: 10}
|
||||
include_thumbnail: {type: boolean, required: false, default: false}
|
||||
fetch: {type: boolean, required: false, default: false}
|
||||
test_request: {queryParams: {query: "browser agent benchmark", domain_type: research_paper, pub_year_min: 2024}}
|
||||
cost: {type: free, value: 0, currency: USD, unit: call, source: docs, source_url: "https://www.tinyfish.ai/pricing", checked: '2026-09-23', confidence: verified, note: "TinyFish lists Search at $0.00 per request; the research-paper index uses the same free endpoint."}
|
||||
verified: '2026-09-23'
|
||||
example_response: examples/tinyfish.web.search.publications.json
|
||||
docs_url: https://docs.tinyfish.ai/api-reference/search-the-web
|
||||
|
||||
- id: tinyfish.web.fetch
|
||||
async: false
|
||||
capability: web.extract
|
||||
platform: web
|
||||
domain: extract
|
||||
scope: any_account
|
||||
method: POST
|
||||
host: api.fetch.tinyfish.ai
|
||||
path: /
|
||||
name: Fetch clean page content
|
||||
summary: Fetch up to ten URLs as Markdown, HTML, or structured JSON, with optional links, metadata, and selectors.
|
||||
input:
|
||||
bodyType: json
|
||||
body:
|
||||
urls: {type: "array[string]", required: true, minItems: 1, maxItems: 10, example: ["https://example.com"]}
|
||||
purpose: {type: string, required: false}
|
||||
format: {type: string, required: false, default: markdown, enum: [html, markdown, json]}
|
||||
links: {type: boolean, required: false, default: false}
|
||||
image_links: {type: boolean, required: false, default: false}
|
||||
page_metadata: {type: boolean, required: false, default: false}
|
||||
ttl: {type: integer, required: false, min: 0, note: "TinyFish cache lifetime in seconds; 0 requests a fresh fetch."}
|
||||
per_url_timeout_ms: {type: integer, required: false, min: 1}
|
||||
if_none_match: {type: string, required: false}
|
||||
if_modified_since: {type: string, required: false}
|
||||
include_etag_and_last_modified: {type: boolean, required: false, default: false}
|
||||
include_selectors: {type: "array[string]", required: false}
|
||||
exclude_selectors: {type: "array[string]", required: false}
|
||||
test_request: {body: {urls: ["https://example.com"], format: markdown, links: true, ttl: 0}}
|
||||
cost: {type: free, value: 0, currency: USD, unit: call, source: docs, source_url: "https://www.tinyfish.ai/pricing", checked: '2026-09-23', confidence: verified, note: "TinyFish lists Fetch at $0.00 per URL; a live fetch left the wallet unchanged."}
|
||||
verified: '2026-09-23'
|
||||
example_response: examples/tinyfish.web.fetch.json
|
||||
docs_url: https://docs.tinyfish.ai/api-reference/fetch-and-extract-content-from-urls
|
||||
|
||||
- id: tinyfish.web.agent.run
|
||||
capability: web.agent.run
|
||||
platform: web
|
||||
domain: agent
|
||||
scope: any_account
|
||||
method: POST
|
||||
path: /v1/automation/run-async
|
||||
name: Run a browser agent
|
||||
summary: Start an asynchronous browser agent on a URL and have it work toward a natural-language goal.
|
||||
input:
|
||||
bodyType: json
|
||||
body:
|
||||
url: {type: string, required: true, example: "https://example.com"}
|
||||
goal: {type: string, required: true, example: "Return the page heading and explain what this site is for."}
|
||||
browser_profile: {type: string, required: false, default: lite, enum: [lite, stealth]}
|
||||
agent_config:
|
||||
type: object
|
||||
required: false
|
||||
default: {max_steps: 150}
|
||||
properties:
|
||||
mode: {type: string, required: false, default: default, enum: [default, strict]}
|
||||
cursor_style: {type: string, required: false, enum: [fish, standard, green-pointer]}
|
||||
max_steps: {type: integer, required: false, default: 150, min: 1, max: 500, note: "TinyFish currently beta-gates custom max_steps; omit it unless the account is enabled."}
|
||||
max_duration_seconds: {type: integer, required: false, min: 1}
|
||||
capture_config:
|
||||
type: object
|
||||
required: false
|
||||
properties:
|
||||
elements: {type: boolean, required: false, default: false}
|
||||
snapshots: {type: boolean, required: false, default: false}
|
||||
screenshots: {type: boolean, required: false, default: false}
|
||||
recording: {type: boolean, required: false, default: false}
|
||||
html: {type: boolean, required: false, default: false}
|
||||
proxy_config:
|
||||
type: object
|
||||
required: false
|
||||
properties:
|
||||
enabled: {type: boolean, required: true}
|
||||
country_code: {type: string, required: false, enum: [US, GB, CA, DE, FR, JP, AU]}
|
||||
output_schema: {type: object, required: false, note: "JSON Schema for a structured terminal result."}
|
||||
test_request:
|
||||
body: {url: "https://example.com", goal: "Return the page heading and explain what this site is for.", browser_profile: lite, capture_config: {screenshots: true, html: true}}
|
||||
cost:
|
||||
type: per_success
|
||||
table:
|
||||
- {when: {body.browser_profile: lite}, value: 1, times: body.agent_config.max_steps}
|
||||
- {when: {body.browser_profile: stealth}, value: 1, times: body.agent_config.max_steps}
|
||||
fallback: {value: 500, note: "The request schema caps a run at TinyFish's documented 500 steps."}
|
||||
currency: unit
|
||||
unit: step
|
||||
settle: usage
|
||||
usage: {path: num_of_steps, unit: step}
|
||||
source: observed
|
||||
source_url: https://www.tinyfish.ai/pricing
|
||||
checked: '2026-09-23'
|
||||
confidence: verified
|
||||
note: "TinyFish publishes $0.016 per Agent step. A completed live run reported 3 steps and reduced the wallet by exactly $0.048; a cancelled run reduced it by $0, so FAILED and CANCELLED release the hold until a positive terminal charge is observed."
|
||||
verified: '2026-09-23'
|
||||
example_response: examples/tinyfish.web.agent.run.json
|
||||
docs_url: https://docs.tinyfish.ai/api-reference/automation/start-automation-asynchronously
|
||||
|
||||
- id: tinyfish.web.agent.run.get
|
||||
async: false
|
||||
kind: utility
|
||||
capability: web.agent.run.status
|
||||
platform: web
|
||||
scope: any_account
|
||||
method: GET
|
||||
path: /v1/runs/{run_id}
|
||||
name: Check a browser-agent run
|
||||
summary: Read an Agent run's state, step count, result, and captured step metadata.
|
||||
resource_ownership:
|
||||
requires: {kind: "poll:tinyfish.web.agent.run.get", param: run_id}
|
||||
input:
|
||||
pathParams:
|
||||
run_id: {type: string, required: true, example: "e1f90a04-486f-441b-95af-7f7223eb994b"}
|
||||
test_request: {pathParams: {run_id: "e1f90a04-486f-441b-95af-7f7223eb994b"}}
|
||||
cost: {type: free, value: 0, currency: USD, unit: call, note: "Reading a run does not start another Agent run."}
|
||||
verified: '2026-09-23'
|
||||
example_response: examples/tinyfish.web.agent.run.get.json
|
||||
docs_url: https://docs.tinyfish.ai/api-reference/runs/get-run-by-id
|
||||
|
||||
- id: tinyfish.web.agent.run.cancel
|
||||
async: false
|
||||
kind: utility
|
||||
capability: web.agent.run.cancel
|
||||
platform: web
|
||||
scope: any_account
|
||||
method: POST
|
||||
path: /v1/runs/{run_id}/cancel
|
||||
name: Cancel a browser-agent run
|
||||
summary: Stop a pending or running Agent run; completed steps remain billable upstream.
|
||||
resource_ownership:
|
||||
requires: {kind: "poll:tinyfish.web.agent.run.get", param: run_id}
|
||||
input:
|
||||
pathParams:
|
||||
run_id: {type: string, required: true, example: "e1f90a04-486f-441b-95af-7f7223eb994b"}
|
||||
test_request: {pathParams: {run_id: "e1f90a04-486f-441b-95af-7f7223eb994b"}}
|
||||
cost: {type: free, value: 0, currency: USD, unit: call, note: "The cancel request itself is free; any Agent steps already completed belong to the original run."}
|
||||
verified: '2026-09-23'
|
||||
example_response: examples/tinyfish.web.agent.run.cancel.json
|
||||
docs_url: https://docs.tinyfish.ai/api-reference/runs/cancel-run-by-id
|
||||
|
||||
- id: tinyfish.web.agent.step.screenshot
|
||||
async: false
|
||||
kind: utility
|
||||
capability: web.agent.step.screenshot
|
||||
platform: web
|
||||
scope: any_account
|
||||
method: GET
|
||||
path: /v1/runs/{run_id}/steps/{step_id}/screenshot
|
||||
name: Retrieve an Agent step screenshot
|
||||
summary: Retrieve the JPEG screenshot captured for one step of a run that requested screenshots.
|
||||
resource_ownership:
|
||||
requires: {kind: "poll:tinyfish.web.agent.run.get", param: run_id}
|
||||
input:
|
||||
pathParams:
|
||||
run_id: {type: string, required: true}
|
||||
step_id: {type: string, required: true}
|
||||
cost: {type: free, value: 0, currency: USD, unit: call, note: "Retrieving an existing captured screenshot is free."}
|
||||
docs_url: https://docs.tinyfish.ai/api-reference/runs/get-step-screenshot
|
||||
|
||||
- id: tinyfish.web.agent.step.html
|
||||
async: false
|
||||
kind: utility
|
||||
capability: web.agent.step.html
|
||||
platform: web
|
||||
scope: any_account
|
||||
method: GET
|
||||
path: /v1/runs/{run_id}/steps/{step_id}/html
|
||||
name: Retrieve Agent step HTML
|
||||
summary: Retrieve the HTML captured for one step of a run that requested HTML capture.
|
||||
resource_ownership:
|
||||
requires: {kind: "poll:tinyfish.web.agent.run.get", param: run_id}
|
||||
input:
|
||||
pathParams:
|
||||
run_id: {type: string, required: true}
|
||||
step_id: {type: string, required: true}
|
||||
cost: {type: free, value: 0, currency: USD, unit: call, note: "Retrieving existing captured HTML is free."}
|
||||
docs_url: https://docs.tinyfish.ai/api-reference/runs/get-step-html-snapshot
|
||||
@@ -261,6 +261,7 @@ class Settings(BaseSettings):
|
||||
platform_key_replicate: str = "" # Bearer token for official asynchronous models
|
||||
platform_key_reapi: str = "" # Bearer key; prepaid credits at $0.001, Seedance 2.5 + image models
|
||||
platform_key_piapi: str = "" # X-API-Key; prepaid USD balance, Seedance 2.5 less-restriction + image models
|
||||
platform_key_tinyfish: str = "" # X-API-Key; free Search/Fetch plus Agent billed per terminal step
|
||||
# Overflow aggregators (docs/PROVIDER-CAPACITY-PLAN.md §4.3): treg-owned accounts that serve the
|
||||
# SAME vendor endpoint when our direct account is out. Env only, never a Secret row, never logged.
|
||||
# Not platform_key_* on purpose: they are a credential RUNG (platform-overflow), not a provider.
|
||||
|
||||
@@ -41,6 +41,29 @@ async def _tikhub(c, key):
|
||||
return {"value": (d.get("user_data") or {}).get("balance"), "unit": "USD", "note": ""}
|
||||
|
||||
|
||||
async def _tinyfish(c, key):
|
||||
d = await _get(c, "https://agent.tinyfish.ai/v1/wallet",
|
||||
headers={"X-API-Key": key})
|
||||
raw = d.get("available_balance")
|
||||
try:
|
||||
balance = Decimal(str(raw)) if not isinstance(raw, bool) and raw is not None else None
|
||||
except InvalidOperation:
|
||||
balance = None
|
||||
if balance is None or not balance.is_finite() or balance < 0:
|
||||
raise ValueError("TinyFish wallet returned an invalid available_balance")
|
||||
reload_state = d.get("auto_reload")
|
||||
if isinstance(reload_state, dict) and isinstance(reload_state.get("state"), str):
|
||||
note = f"vendor auto-reload {reload_state['state']}"
|
||||
elif reload_state is True:
|
||||
note = "vendor auto-reload enabled"
|
||||
elif reload_state is False:
|
||||
note = "vendor auto-reload not enabled"
|
||||
else:
|
||||
note = "vendor auto-reload state unavailable"
|
||||
return {"value": float(balance), "unit": str(d.get("currency") or "USD").upper(),
|
||||
"note": note}
|
||||
|
||||
|
||||
async def _fishaudio(c, key):
|
||||
workspace_id = get_settings().platform_fishaudio_workspace_id.strip()
|
||||
if not workspace_id:
|
||||
@@ -703,6 +726,7 @@ BALANCE_ROUTES = {
|
||||
"tomba": _tomba,
|
||||
"dataforseo": _dataforseo,
|
||||
"tikhub": _tikhub,
|
||||
"tinyfish": _tinyfish,
|
||||
"fishaudio": _fishaudio,
|
||||
"tavily": _tavily,
|
||||
"scrapecreators": _scrapecreators,
|
||||
|
||||
@@ -25,6 +25,7 @@ _KNOWN: dict[str, tuple[str, str, str]] = {
|
||||
"harvestapi": ("cash", "auto_recharge", "api"), # Owner will enable vendor auto top-up for production.
|
||||
"dataforseo": ("cash", "auto_recharge", "api"),
|
||||
"tikhub": ("cash", "auto_recharge", "api"),
|
||||
"tinyfish": ("cash", "manual", "api"),
|
||||
"lusha": ("credits", "auto_recharge", "api"),
|
||||
"scrapecreators": ("credits", "manual", "api"),
|
||||
"contactout": ("credits", "unknown", "api"), # independent pools; overages unconfirmed
|
||||
@@ -88,6 +89,10 @@ _QUOTAS: dict[str, dict] = {
|
||||
}
|
||||
_RATE_LIMITS: dict[str, dict] = {
|
||||
"adyntel": {"limit": 5, "window_s": 1, "source": "docs"},
|
||||
# Search's documented burst allowance is the strictest request-count limit shared by these
|
||||
# hosts. Fetch additionally meters URLs and Agent limits concurrency; upstream remains the
|
||||
# authority for those differently-shaped limits.
|
||||
"tinyfish": {"limit": 30, "window_s": 60, "source": "docs"},
|
||||
# The only platform-served tool is standard single verification, documented at 100/s. Keep
|
||||
# the shared key at one quarter of that allowance; BYOK calls bypass this limiter.
|
||||
"bounceban": {"limit": 25, "window_s": 1, "source": "docs"},
|
||||
|
||||
@@ -1867,6 +1867,41 @@ PIAPI = OAuthProvider(
|
||||
probe_path="/account/info", # free; a bad key answers 401 {"message":"Failed to verify api key"}
|
||||
)
|
||||
|
||||
TINYFISH = OAuthProvider(
|
||||
service="tinyfish",
|
||||
display_name="TinyFish",
|
||||
auth_kind="key",
|
||||
token_label="API key",
|
||||
token_placeholder="your TinyFish API key",
|
||||
token_header="X-API-Key",
|
||||
token_format="{secret}",
|
||||
setup_url="https://agent.tinyfish.ai/api-keys",
|
||||
setup_action_label="Get your TinyFish API key",
|
||||
setup_steps=(
|
||||
"Sign in to TinyFish and open API keys.",
|
||||
"Create or copy an API key and paste it here.",
|
||||
),
|
||||
setup_note=(
|
||||
"Search and Fetch are free within TinyFish's published limits. Agent runs cost $0.016 "
|
||||
"per reported step; treg reads the terminal step count before settling a platform call."
|
||||
),
|
||||
auth_uri="", token_uri="", scopes={},
|
||||
client_id_setting="", client_secret_setting="",
|
||||
category="SEO",
|
||||
summary="Search and fetch the web, or run an asynchronous browser agent toward a stated goal.",
|
||||
base_url="https://agent.tinyfish.ai",
|
||||
catalog_targets=(
|
||||
CatalogTarget(host="api.search.tinyfish.ai", base_url="https://api.search.tinyfish.ai"),
|
||||
CatalogTarget(host="api.fetch.tinyfish.ai", base_url="https://api.fetch.tinyfish.ai"),
|
||||
),
|
||||
extra_tools=(
|
||||
{"suffix": "search", "base_url": "https://api.search.tinyfish.ai"},
|
||||
{"suffix": "fetch", "base_url": "https://api.fetch.tinyfish.ai"},
|
||||
),
|
||||
docs_url="https://docs.tinyfish.ai/",
|
||||
probe_path="/v1/wallet",
|
||||
)
|
||||
|
||||
TIKHUB = OAuthProvider(
|
||||
service="tikhub",
|
||||
display_name="TikHub",
|
||||
@@ -3405,7 +3440,7 @@ REGISTRY: dict[str, OAuthProvider] = {
|
||||
TRYKITT, CONTACTOUT, MILLIONVERIFIER, BOUNCEBAN, CRUNCHBASE, MINIMAX, FISHAUDIO,
|
||||
OPENROUTER,
|
||||
REPLICATE,
|
||||
REAPI, PIAPI,
|
||||
REAPI, PIAPI, TINYFISH,
|
||||
TIKHUB, BRIGHTDATA, SEMRUSH, JUSTONEAPI,
|
||||
SCRAPECREATORS,
|
||||
# SEO API-key providers
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<svg width="65" height="64" viewBox="0 0 65 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0.5" width="64" height="64" rx="8" fill="#FF6700"/>
|
||||
<path d="M19 28C15 22 10 19 9 23C8 27 12 33 13 34C10 40 9 45 13 46C17 47 20 42 22 39C26 45 32 48 38 48C48 48 56 41 56 33C56 25 48 18 38 18C31 18 24 22 19 28Z" fill="#FECB8B" stroke="#111" stroke-width="2.4" stroke-linejoin="round"/>
|
||||
<path d="M29 18C26 12 29 9 34 12C39 14 43 18 45 22" fill="#FECB8B" stroke="#111" stroke-width="2.4" stroke-linejoin="round"/>
|
||||
<ellipse cx="46" cy="29" rx="2.8" ry="4.5" fill="#111"/>
|
||||
<ellipse cx="45" cy="27.5" rx="0.8" ry="1.2" fill="white"/>
|
||||
<path d="M40 36C44 38 48 36 51 33C50 39 46 42 42 41C40 40 39 38 40 36Z" fill="#111"/>
|
||||
<path d="M36 45C37 50 33 54 29 52C25 50 27 46 32 43" fill="#FECB8B" stroke="#111" stroke-width="2.4" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 867 B |
@@ -287,6 +287,7 @@ _UNRECORDED_SIGNATURE = {
|
||||
"fishaudio", # shared-key serving stays disabled until the funded-account signatures are verified
|
||||
"minimax", "oceanio", "openrouter", "replicate", "scrapecreators", "seranking",
|
||||
"piapi", # prepaid wallet exhaustion not observed ($50 funded 2026-09-14); no overflow route
|
||||
"tinyfish", # funded wallet remains; no provider-specific empty-wallet response was forced
|
||||
"trestleiq", # funded wallet remains; documented 403/429 shapes do not identify empty balance
|
||||
|
||||
"serpapi", "serpstat", "spyfu", "tiingo", "tikhub", "tomba", "twelvedata",
|
||||
|
||||
@@ -32,7 +32,7 @@ def test_key_providers_are_offerable_without_deployment_credentials():
|
||||
"icypeas", "leadsforge", "influencersclub", "crustdata", "aviato",
|
||||
"spyfu", "apify", "meta-ad-library", "serpapi", "adyntel",
|
||||
"coingecko", "polygon", "finnhub", "twelvedata", "fmp", "eodhd", "marketstack",
|
||||
"tiingo", "financialdatasets"):
|
||||
"tiingo", "financialdatasets", "tinyfish"):
|
||||
p = P.get(svc)
|
||||
assert p is not None, svc
|
||||
assert p.auth_kind == "key", svc
|
||||
|
||||
@@ -59,7 +59,7 @@ def test_every_provider_is_registered():
|
||||
"financialdatasets",
|
||||
"microsoft-ads", "snapchat-ads", "tiktok-ads", "pinterest-ads",
|
||||
# BYOK token providers
|
||||
"minimax", "fishaudio", "openrouter", "replicate", "reapi", "piapi",
|
||||
"minimax", "fishaudio", "openrouter", "replicate", "reapi", "piapi", "tinyfish",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
"""TinyFish catalog, per-step settlement, and wallet-capacity integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from datetime import timedelta
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
from sqlmodel import select
|
||||
|
||||
from treg import oauth_providers
|
||||
from treg.application import asynctasks as task_app
|
||||
from treg.application.call import service as call_service
|
||||
from treg.application.call.types import UpstreamResponse
|
||||
from treg.config import Settings, get_settings
|
||||
from treg.domain.capacity import collectors, policy
|
||||
from treg.domain.catalog import store
|
||||
from treg.infra.db import session_maker
|
||||
from treg.models import AsyncTaskRecord
|
||||
from treg.timeutil import utcnow_naive
|
||||
|
||||
|
||||
def _response(status: int, document: object) -> UpstreamResponse:
|
||||
body = json.dumps(document).encode()
|
||||
|
||||
async def stream():
|
||||
yield body
|
||||
|
||||
async def close():
|
||||
return None
|
||||
|
||||
return UpstreamResponse(status, ((b"content-type", b"application/json"),), stream(), close)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tinyfish_platform(monkeypatch):
|
||||
monkeypatch.setenv("TREG_PLATFORM_KEY_TINYFISH", "test-platform-tinyfish")
|
||||
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "tinyfish")
|
||||
get_settings.cache_clear()
|
||||
yield
|
||||
get_settings.cache_clear()
|
||||
|
||||
|
||||
def test_tinyfish_registry_and_catalog_are_byok_and_platform_ready(tinyfish_platform):
|
||||
provider = oauth_providers.get("tinyfish")
|
||||
assert provider is not None
|
||||
assert provider.auth_kind == "key"
|
||||
assert provider.base_url == "https://agent.tinyfish.ai"
|
||||
assert provider.probe_path == "/v1/wallet"
|
||||
assert {target.host for target in provider.catalog_targets} == {
|
||||
"api.search.tinyfish.ai", "api.fetch.tinyfish.ai",
|
||||
}
|
||||
assert oauth_providers.platform_bindings(provider) == [{
|
||||
"platform_setting": "platform_key_tinyfish",
|
||||
"injector": "env",
|
||||
"location": "header",
|
||||
"name": "X-API-Key",
|
||||
"format": "{secret}",
|
||||
}]
|
||||
assert Settings(_env_file=None).platform_key_for("tinyfish") == "test-platform-tinyfish"
|
||||
|
||||
catalog = store.load()
|
||||
rows = [row for row in catalog.endpoints if row["provider"] == "tinyfish"]
|
||||
assert len(rows) == 9
|
||||
assert all(catalog.platform_eligible(row) for row in rows)
|
||||
assert not any("batch" in row["id"] or "monitor" in row["id"] for row in rows)
|
||||
assert not any(row.get("kind") == "account" for row in rows)
|
||||
|
||||
agent = catalog.by_id["tinyfish.web.agent.run"]
|
||||
shown = catalog.cost_view(agent["cost"], "tinyfish")
|
||||
assert (shown["rate_usd_min"], shown["rate_usd"], shown["rate_unit"]) == (
|
||||
0.016, 0.016, "step",
|
||||
)
|
||||
assert shown["usd_min"] == 0.016 and shown["usd"] == 8.0
|
||||
assert agent["async"]["status"] == {
|
||||
"path": "status", "progress": ["PENDING", "RUNNING"],
|
||||
"success": ["COMPLETED"], "failure": ["FAILED", "CANCELLED"],
|
||||
}
|
||||
|
||||
|
||||
async def test_tinyfish_agent_price_is_exposed_as_per_step_to_clients(clients):
|
||||
response = await clients.get("/catalog/endpoints/tinyfish.web.agent.run")
|
||||
assert response.status_code == 200
|
||||
endpoint = response.json()["endpoint"]
|
||||
assert endpoint["platform_eligible"] is True
|
||||
assert endpoint["cost"]["rate_usd_min"] == 0.016
|
||||
assert endpoint["cost"]["rate_usd"] == 0.016
|
||||
assert endpoint["cost"]["rate_unit"] == "step"
|
||||
assert endpoint["cost"]["usd_min"] == 0.016
|
||||
assert endpoint["cost"]["usd"] == 8.0
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("terminal_status", "steps", "task_status", "settled_micro"), [
|
||||
("COMPLETED", 3, "settled", 48_000),
|
||||
("FAILED", 2, "released", 0),
|
||||
("CANCELLED", 4, "released", 0),
|
||||
("CANCELLED", 0, "released", 0),
|
||||
])
|
||||
async def test_tinyfish_agent_holds_requested_ceiling_then_settles_terminal_steps(
|
||||
clients, monkeypatch, tinyfish_platform, terminal_status, steps, task_status, settled_micro,
|
||||
):
|
||||
async def submit(*_args, **_kwargs):
|
||||
return _response(200, {"run_id": "tinyfish-run-1", "status": "PENDING"})
|
||||
|
||||
monkeypatch.setattr(call_service, "relay", submit)
|
||||
response = await clients.post("/call/tinyfish.web.agent.run", json={
|
||||
"url": "https://example.com",
|
||||
"goal": "Return the heading.",
|
||||
"browser_profile": "lite",
|
||||
"agent_config": {"max_steps": 10},
|
||||
})
|
||||
assert response.status_code == 200, response.text
|
||||
|
||||
async with session_maker() as db:
|
||||
row = (await db.execute(select(AsyncTaskRecord).where(
|
||||
AsyncTaskRecord.task_id == "tinyfish-run-1"))).scalar_one()
|
||||
assert row.reserved_micro == 160_000
|
||||
assert row.settlement_basis["amount"] == {
|
||||
"kind": "usage", "path": "num_of_steps", "unit": "step", "unit_micro": 16_000,
|
||||
}
|
||||
row.next_check_at = utcnow_naive() - timedelta(seconds=1)
|
||||
call_id = row.call_id
|
||||
await db.commit()
|
||||
|
||||
async def completed(_row, _client):
|
||||
return 200, json.dumps({
|
||||
"run_id": "tinyfish-run-1", "status": terminal_status,
|
||||
"num_of_steps": steps, "result": {"heading": "Example Domain"},
|
||||
}).encode()
|
||||
|
||||
monkeypatch.setattr(task_app, "_poll", completed)
|
||||
result = await task_app.settle_due()
|
||||
assert result.settled == (task_status == "settled")
|
||||
assert result.released == (task_status == "released")
|
||||
async with session_maker() as db:
|
||||
row = await db.get(AsyncTaskRecord, call_id)
|
||||
assert row.status == task_status
|
||||
assert row.settled_micro == settled_micro
|
||||
|
||||
|
||||
async def test_tinyfish_wallet_collector_and_policy():
|
||||
def probe(request):
|
||||
assert request.method == "GET"
|
||||
assert request.url == "https://agent.tinyfish.ai/v1/wallet"
|
||||
assert request.headers["x-api-key"] == "test"
|
||||
return httpx.Response(200, json={
|
||||
"available_balance": "75.904",
|
||||
"currency": "usd",
|
||||
"auto_reload": {"state": "unconfigured"},
|
||||
"rates": {"meters": [{"product": "Agent", "rate": "0.016", "unit": "step"}]},
|
||||
})
|
||||
|
||||
async with httpx.AsyncClient(transport=httpx.MockTransport(probe)) as client:
|
||||
row = await collectors._tinyfish(client, "test")
|
||||
assert row == {
|
||||
"value": 75.904, "unit": "USD", "note": "vendor auto-reload unconfigured",
|
||||
}
|
||||
capacity = policy.default_policy("tinyfish", has_key=True)
|
||||
assert capacity.capacity_type == "cash"
|
||||
assert capacity.funding_mode == "manual"
|
||||
assert capacity.source == "api"
|
||||
assert capacity.rate_limit == {"limit": 30, "window_s": 60, "source": "docs"}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", [None, True, "NaN", "Infinity", -1, "bad"])
|
||||
async def test_tinyfish_wallet_rejects_invalid_balances(value):
|
||||
async with httpx.AsyncClient(transport=httpx.MockTransport(
|
||||
lambda _request: httpx.Response(200, json={"available_balance": value})
|
||||
)) as client:
|
||||
with pytest.raises(ValueError):
|
||||
await collectors._tinyfish(client, "test")
|
||||
Reference in New Issue
Block a user