Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,14 @@ Want to contribute to these integrations? We love adding TinyFish Web Agent to n
## License

These integrations are licensed under the MIT License.

## Identifying your integration to TinyFish

Every integration that calls the REST API directly sends two headers on every request:

| header | value |
|---|---|
| `X-TF-Client-Name` | the bare integration token — the same value used for `api_integration` where one exists: `hermes`, `dify`, `n8n` |
| `X-TF-Client-Version` | the integration's own published version |

Do not send `X-TF-Request-Origin`; `api` is the transport and the server derives it. Integrations that wrap the `tinyfish` SDK set `TF_CLIENT_NAME` / `TF_CLIENT_VERSION` alongside `TF_API_INTEGRATION` instead. Without these, telemetry cannot tell the integration from a hand-written curl.
1 change: 1 addition & 0 deletions hermes/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ __pycache__/
.mypy_cache/
.ruff_cache/
.pytest_cache/
uv.lock
2 changes: 1 addition & 1 deletion hermes/plugin.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: tinyfish
version: 0.1.0
version: 0.1.1
description: "First-party TinyFish provider plugin for Hermes Agent: Search and Fetch over the TinyFish REST APIs with API-key auth, plus credit-gated Browser sessions."
author: TinyFish
kind: backend
Expand Down
2 changes: 1 addition & 1 deletion hermes/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "tinyfish-hermes"
version = "0.1.0"
version = "0.1.1"
description = "TinyFish web provider plugin for Hermes Agent - Search and Fetch over the TinyFish REST APIs"
readme = "README.md"
license = { text = "MIT" }
Expand Down
21 changes: 12 additions & 9 deletions hermes/tests/test_rest_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@
import httpx
import pytest

from tinyfish_hermes import rest_client
from tinyfish_hermes import __version__, rest_client

_AUTH_HEADERS = {
"X-API-Key": "tf_test",
"Accept": "application/json",
"X-TF-Client-Name": "hermes",
"X-TF-Client-Version": __version__,
}


def _response(
Expand Down Expand Up @@ -61,7 +68,7 @@ def fake_get(url: str, **kwargs: Any) -> httpx.Response:
"page": 2,
"purpose": "research",
},
"headers": {"X-API-Key": "tf_test", "Accept": "application/json"},
"headers": _AUTH_HEADERS,
"timeout": 12.5,
}

Expand Down Expand Up @@ -115,11 +122,7 @@ def fake_post(url: str, **kwargs: Any) -> httpx.Response:
"ttl": 300,
"per_url_timeout_ms": 2500,
},
"headers": {
"X-API-Key": "tf_test",
"Accept": "application/json",
"Content-Type": "application/json",
},
"headers": {**_AUTH_HEADERS, "Content-Type": "application/json"},
"timeout": 22.0,
}

Expand Down Expand Up @@ -283,7 +286,7 @@ def fake_delete(url: str, **kwargs: Any) -> httpx.Response:
is True
)
assert captured["url"] == f"{rest_client.BROWSER_URL}/sess_123"
assert captured["headers"] == {"X-API-Key": "tf_test", "Accept": "application/json"}
assert captured["headers"] == _AUTH_HEADERS
assert captured["timeout"] == 9.0


Expand Down Expand Up @@ -424,7 +427,7 @@ def fake_get(url: str, **kwargs: Any) -> httpx.Response:
assert call() == {"items": []}
assert captured == {
"url": expected_url,
"headers": {"X-API-Key": "tf_test", "Accept": "application/json"},
"headers": _AUTH_HEADERS,
"timeout": 11.0,
}

Expand Down
26 changes: 25 additions & 1 deletion hermes/tests/test_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,23 @@
import tinyfish_hermes as plugin


def test_version_prefers_installed_distribution_metadata(
def test_version_prefers_adjacent_manifest_over_stale_metadata(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
manifest = tmp_path / "plugin.yaml"
manifest.write_text("version: 1.2.3\n", encoding="utf-8")
monkeypatch.setattr(plugin, "_PLUGIN_MANIFEST", manifest)
monkeypatch.setattr(plugin.metadata, "version", lambda name: "9.8.7")

assert plugin._resolve_version() == "1.2.3"


def test_version_uses_installed_metadata_when_no_manifest(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
monkeypatch.setattr(plugin, "_PLUGIN_MANIFEST", tmp_path / "missing.yaml")
monkeypatch.setattr(plugin.metadata, "version", lambda name: "9.8.7")

assert plugin._resolve_version() == "9.8.7"


Expand Down Expand Up @@ -53,3 +62,18 @@ def test_public_version_is_exported() -> None:
assert isinstance(plugin.__version__, str)
assert plugin.__version__
assert "__version__" in plugin.__all__


def test_version_rejects_non_ascii_manifest_value(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
manifest = tmp_path / "plugin.yaml"
manifest.write_text("version: 0.1.1é\n", encoding="utf-8")
monkeypatch.setattr(plugin, "_PLUGIN_MANIFEST", manifest)

def missing_distribution(name: str) -> str:
raise metadata.PackageNotFoundError(name)

monkeypatch.setattr(plugin.metadata, "version", missing_distribution)

assert plugin._resolve_version() == "0+unknown"
14 changes: 9 additions & 5 deletions hermes/tinyfish_hermes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,15 @@ def _version_from_plugin_manifest(path: Path | None = None) -> str | None:


def _resolve_version() -> str:
try:
installed_version = metadata.version(_DISTRIBUTION_NAME)
except Exception: # directory installs must not depend on package metadata
installed_version = ""
return installed_version or _version_from_plugin_manifest() or "0+unknown"
# Wheels omit plugin.yaml: an adjacent manifest beats stale dist metadata.
version = _version_from_plugin_manifest()
if not version:
try:
version = metadata.version(_DISTRIBUTION_NAME)
except Exception: # directory installs must not depend on package metadata
version = ""
# httpx sends headers as ASCII; one stray byte fails every request.
return version if version and version.isascii() else "0+unknown"


__version__ = _resolve_version()
Expand Down
10 changes: 9 additions & 1 deletion hermes/tinyfish_hermes/rest_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

import httpx

from . import __version__

SEARCH_URL = "https://api.search.tinyfish.ai"
FETCH_URL = "https://api.fetch.tinyfish.ai"
FETCH_MAX_URLS = 10
Expand All @@ -31,7 +33,13 @@ class TinyFishWalletNotFound(TinyFishRestError):


def _headers(api_key: str) -> dict[str, str]:
return {"X-API-Key": api_key, "Accept": "application/json"}
# Without these, telemetry cannot tell the plugin from raw curl.
return {
"X-API-Key": api_key,
"Accept": "application/json",
"X-TF-Client-Name": "hermes",
"X-TF-Client-Version": __version__,
}


def _json_headers(api_key: str) -> dict[str, str]:
Expand Down