diff --git a/README.md b/README.md index bad79f9..c1867d4 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Every integration that calls the REST API directly must send two headers on ever | header | value | |---|---| -| `X-TF-Client-Name` | the bare integration token — the same value used for `api_integration` where one exists: `hermes`, `n8n` | +| `X-TF-Client-Name` | the bare integration token — the same value used for `api_integration` where one exists: `dify`, `hermes`, `n8n` | | `X-TF-Client-Version` | the integration's own published version | Do not send `X-TF-Request-Origin` yourself; `api` is the transport and the server derives it, and the `tinyfish` SDK already sets its own value. Integrations that wrap the SDK set `TF_CLIENT_NAME` / `TF_CLIENT_VERSION` alongside `TF_API_INTEGRATION` instead — the SDK turns those two into the headers above from `tinyfish>=0.5.0`, and ignores them before that. `langchain` and `google-adk` set only `TF_API_INTEGRATION` today. Without any of this, telemetry cannot tell the integration from a hand-written curl. diff --git a/dify/.difyignore b/dify/.difyignore index 4685c5e..c20647e 100644 --- a/dify/.difyignore +++ b/dify/.difyignore @@ -182,3 +182,4 @@ Thumbs.db # To prevent packaging repetitively *.difypkg +tests/ diff --git a/dify/manifest.yaml b/dify/manifest.yaml index a96685e..ee00de3 100644 --- a/dify/manifest.yaml +++ b/dify/manifest.yaml @@ -1,4 +1,4 @@ -version: 0.0.4 +version: 0.0.5 type: plugin author: tinyfish name: tinyfish-web-agent @@ -32,7 +32,7 @@ plugins: tags: - productivity meta: - version: 0.0.4 + version: 0.0.5 arch: - amd64 - arm64 diff --git a/dify/requirements.txt b/dify/requirements.txt index 69adc2d..53d07c9 100644 --- a/dify/requirements.txt +++ b/dify/requirements.txt @@ -1 +1,2 @@ dify_plugin>=0.4.0,<0.7.0 +pyyaml~=6.0 diff --git a/dify/tests/__init__.py b/dify/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dify/tests/test_headers.py b/dify/tests/test_headers.py new file mode 100644 index 0000000..f697c10 --- /dev/null +++ b/dify/tests/test_headers.py @@ -0,0 +1,32 @@ +import re +from pathlib import Path +from types import SimpleNamespace + +from tools.base import TinyfishMixin +from tools.constants import PLUGIN_VERSION + +MANIFEST = Path(__file__).resolve().parents[1] / "manifest.yaml" + + +def _tool() -> TinyfishMixin: + tool = TinyfishMixin() + tool.runtime = SimpleNamespace(credentials={"api_key": "tf_test"}) # type: ignore[attr-defined] + return tool + + +def test_headers_identify_the_plugin() -> None: + headers = _tool()._api_headers + assert headers["X-API-Key"] == "tf_test" + assert headers["X-TF-Client-Name"] == "dify" + assert headers["X-TF-Client-Version"] == PLUGIN_VERSION + + +def test_plugin_version_is_a_real_release_version() -> None: + # The publish workflow greps `^version:` — same anchor the constant uses. + assert re.fullmatch(r"\d+\.\d+\.\d+", PLUGIN_VERSION), PLUGIN_VERSION + # Column 0 only: meta.version is indented and is the manifest-format version. + assert re.search( + rf"^version: {re.escape(PLUGIN_VERSION)}$", + MANIFEST.read_text(encoding="utf-8"), + re.MULTILINE, + ) diff --git a/dify/tools/base.py b/dify/tools/base.py index d1bb0e5..f30fef0 100644 --- a/dify/tools/base.py +++ b/dify/tools/base.py @@ -2,7 +2,7 @@ import httpx -from tools.constants import API_BASE_URL +from tools.constants import API_BASE_URL, PLUGIN_VERSION class TinyfishMixin: @@ -10,7 +10,12 @@ class TinyfishMixin: @property def _api_headers(self) -> dict[str, str]: - return {"X-API-Key": self.runtime.credentials["api_key"]} + # Without these, telemetry cannot tell the plugin from raw curl. + return { + "X-API-Key": self.runtime.credentials["api_key"], + "X-TF-Client-Name": "dify", + "X-TF-Client-Version": PLUGIN_VERSION, + } def _tf_request( self, diff --git a/dify/tools/constants.py b/dify/tools/constants.py index 0b0d72c..9ea0e55 100644 --- a/dify/tools/constants.py +++ b/dify/tools/constants.py @@ -1 +1,19 @@ +from pathlib import Path + +import yaml + API_BASE_URL = "https://agent.tinyfish.ai" + +# Top-level `version` is the plugin; `meta.version` is the manifest format. +_MANIFEST = Path(__file__).resolve().parents[1] / "manifest.yaml" + + +def _manifest_version() -> str: + try: + version = yaml.safe_load(_MANIFEST.read_text(encoding="utf-8")).get("version") + except (OSError, yaml.YAMLError, AttributeError): + version = None + return str(version) if version else "0+unknown" + + +PLUGIN_VERSION = _manifest_version()