hermes-plugin-tinyfish 0.2.4__tar.gz → 0.3.1__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.
Files changed (48) hide show
  1. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/CHANGELOG.md +26 -0
  2. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/PKG-INFO +59 -5
  3. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/README.md +57 -3
  4. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/hermes_plugin_tinyfish/__init__.py +13 -3
  5. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/hermes_plugin_tinyfish/config.py +12 -0
  6. hermes_plugin_tinyfish-0.3.1/hermes_plugin_tinyfish/routing_context.py +75 -0
  7. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/hermes_plugin_tinyfish/setup_cli.py +34 -8
  8. hermes_plugin_tinyfish-0.3.1/hermes_plugin_tinyfish/update_check.py +290 -0
  9. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/plugin.yaml +2 -2
  10. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/pyproject.toml +2 -2
  11. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/tests/test_register.py +6 -1
  12. hermes_plugin_tinyfish-0.3.1/tests/test_routing_context.py +136 -0
  13. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/tests/test_setup_cli.py +34 -3
  14. hermes_plugin_tinyfish-0.3.1/tests/test_update_check.py +336 -0
  15. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/.agents/skills/skill-creator/scripts/__init__.py +0 -0
  16. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/.agents/skills/skill-creator/scripts/aggregate_benchmark.py +0 -0
  17. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/.agents/skills/skill-creator/scripts/generate_report.py +0 -0
  18. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/.agents/skills/skill-creator/scripts/improve_description.py +0 -0
  19. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/.agents/skills/skill-creator/scripts/package_skill.py +0 -0
  20. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/.agents/skills/skill-creator/scripts/quick_validate.py +0 -0
  21. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/.agents/skills/skill-creator/scripts/run_eval.py +0 -0
  22. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/.agents/skills/skill-creator/scripts/run_loop.py +0 -0
  23. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/.agents/skills/skill-creator/scripts/utils.py +0 -0
  24. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/.gitignore +0 -0
  25. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/CODE_OF_CONDUCT.md +0 -0
  26. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/CONTRIBUTING.md +0 -0
  27. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/LICENSE +0 -0
  28. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/SECURITY.md +0 -0
  29. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/__init__.py +0 -0
  30. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/hermes_plugin_tinyfish/browser_provider.py +0 -0
  31. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/hermes_plugin_tinyfish/credit_policy.py +0 -0
  32. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/hermes_plugin_tinyfish/health.py +0 -0
  33. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/hermes_plugin_tinyfish/normalize.py +0 -0
  34. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/hermes_plugin_tinyfish/provider.py +0 -0
  35. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/hermes_plugin_tinyfish/rest_client.py +0 -0
  36. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/scripts/prepare_release.py +0 -0
  37. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/scripts/validate_release.py +0 -0
  38. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/tests/conftest.py +0 -0
  39. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/tests/test_browser_provider.py +0 -0
  40. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/tests/test_credit_policy.py +0 -0
  41. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/tests/test_health.py +0 -0
  42. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/tests/test_live.py +0 -0
  43. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/tests/test_normalize.py +0 -0
  44. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/tests/test_prepare_release.py +0 -0
  45. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/tests/test_provider.py +0 -0
  46. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/tests/test_rest_client.py +0 -0
  47. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/tests/test_validate_release.py +0 -0
  48. {hermes_plugin_tinyfish-0.2.4 → hermes_plugin_tinyfish-0.3.1}/tests/test_version.py +0 -0
@@ -7,6 +7,32 @@ and this project uses semantic versioning.
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## [0.3.1] - 2026-08-04
11
+
12
+ ### Fixed
13
+
14
+ - Added the required Hermes plugin enable step to the Python package install
15
+ instructions before running TinyFish setup.
16
+
17
+ ## [0.3.0] - 2026-08-04
18
+
19
+ ### Added
20
+
21
+ - Added marked, once-per-active-context guidance so Hermes can infer when
22
+ ordinary web requests should use generic provider tools and when
23
+ TinyFish-specific controls require native MCP Search or Fetch without
24
+ accumulating one new copy on every turn.
25
+ - Added nonblocking, source-aware update checks with a 24-hour profile-local
26
+ cache, one-time conversational notices, install-appropriate update guidance,
27
+ opt-outs, and non-networked diagnostics.
28
+
29
+ ### Changed
30
+
31
+ - Expanded diagnostics to schema version 3 with routing state, installed and
32
+ cached-latest versions, release channel, and update availability.
33
+ - Clarified that persistent Search/Fetch settings are REST fallback defaults,
34
+ not required parity configuration for plain-language requests.
35
+
10
36
  ## [0.2.4] - 2026-07-22
11
37
 
12
38
  ### Fixed
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hermes-plugin-tinyfish
3
- Version: 0.2.4
4
- Summary: TinyFish Search, Fetch, and optional Browser provider plugin for Hermes Agent
3
+ Version: 0.3.1
4
+ Summary: TinyFish Search, Fetch, plain-language routing, update awareness, and optional Browser for Hermes Agent
5
5
  Project-URL: Homepage, https://github.com/gabeosx/hermes-plugin-tinyfish
6
6
  Project-URL: Documentation, https://github.com/gabeosx/hermes-plugin-tinyfish#readme
7
7
  Project-URL: Issues, https://github.com/gabeosx/hermes-plugin-tinyfish/issues
@@ -38,8 +38,8 @@ Description-Content-Type: text/markdown
38
38
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
39
39
  [![Security Policy](https://img.shields.io/badge/Security-policy-green.svg)](SECURITY.md)
40
40
 
41
- TinyFish Search and Fetch providers, plus optional TinyFish Browser
42
- infrastructure, for
41
+ TinyFish Search and Fetch providers with plain-language tool routing, update
42
+ awareness, and optional TinyFish Browser infrastructure for
43
43
  [Hermes Agent](https://hermes-agent.nousresearch.com/docs/).
44
44
 
45
45
  Hermes remains the agent: it plans, chooses tools, and controls browser
@@ -67,6 +67,7 @@ Python package install:
67
67
 
68
68
  ```bash
69
69
  pip install hermes-plugin-tinyfish
70
+ hermes plugins enable web-tinyfish
70
71
  hermes tinyfish setup
71
72
  ```
72
73
 
@@ -88,6 +89,20 @@ hermes tinyfish status
88
89
  hermes tinyfish doctor
89
90
  ```
90
91
 
92
+ The plugin also performs a nonblocking update check when it loads. Git installs
93
+ are compared with the latest stable GitHub Release; Python package installs are
94
+ compared with the latest stable PyPI version. Results are cached for 24 hours
95
+ at `$HERMES_HOME/cache/web-tinyfish-update.json` in the active profile. If an
96
+ update is available, Hermes mentions it once on the first conversational turn
97
+ where the cached or background result is available. The plugin never installs
98
+ the update itself.
99
+
100
+ The check sends only the installed plugin version in its HTTP User-Agent. It
101
+ does not send Hermes configuration, TinyFish credentials, searches, fetched
102
+ URLs, or conversation content. Network and cache failures are silent. Disable
103
+ the check with `tinyfish.update_check: false` or the conventional
104
+ `NO_UPDATE_NOTIFIER=1` environment variable.
105
+
91
106
  ## Safe Default Setup
92
107
 
93
108
  `hermes tinyfish setup` configures TinyFish for Hermes `web_search` and
@@ -108,6 +123,8 @@ web:
108
123
  extract_backend: tinyfish
109
124
 
110
125
  tinyfish:
126
+ routing_context: true
127
+ update_check: true
111
128
  credit_policy:
112
129
  browser: deny
113
130
  ```
@@ -165,6 +182,35 @@ For browser authorization from a headless or remote terminal:
165
182
  - Treat the MCP-only doctor as authoritative. Some Hermes versions can print
166
183
  an authentication failure while still returning shell status 0.
167
184
 
185
+ ## Automatic Tool Routing
186
+
187
+ Talk to Hermes normally; you do not need to choose “the plugin” or “MCP.” When
188
+ an active context does not already contain the current routing version, the
189
+ plugin adds a short routing note that helps Hermes choose the narrowest tool
190
+ surface:
191
+
192
+ - “Find recent TinyFish documentation” uses Hermes `web_search`.
193
+ - “Read this page and summarize it” uses Hermes `web_extract`.
194
+ - “Search only these domains, in English, after this date, and return page 2”
195
+ uses TinyFish MCP's native `search` tool because the generic search schema
196
+ cannot express all of those controls.
197
+ - “Fetch these URLs using this selector, include image links, and use this
198
+ cache TTL” uses TinyFish MCP's native `fetch_content` tool.
199
+
200
+ Hermes keeps the visible stored user message clean but persists the exact
201
+ API-bound message in an `api_content` sidecar for prompt-cache replay. The note
202
+ contains a versioned marker, so the hook does not add another copy while that
203
+ version remains in the active context. If compression removes the marked
204
+ message, the hook adds one fresh copy. Multiple tool calls inside a turn do not
205
+ run the hook again. Hermes still sees the real tool schemas and makes the final
206
+ choice; the plugin does not keyword-match the prompt or force a tool.
207
+
208
+ Routing guidance defaults on only when the plugin-managed TinyFish MCP server
209
+ is configured. Set `tinyfish.routing_context: false` to disable the guidance
210
+ without disabling the provider. This prevents new injections; a copy already
211
+ present in the active API context may remain until that context is reset or
212
+ compressed.
213
+
168
214
  ## Search and Fetch Options
169
215
 
170
216
  Optional REST fallback defaults can be configured in `config.yaml`:
@@ -185,7 +231,10 @@ tinyfish:
185
231
  ```
186
232
 
187
233
  MCP remains the preferred path when configured. These options apply only to
188
- REST fallback calls.
234
+ REST fallback calls. They are fixed fallback defaults, not required settings
235
+ for ordinary requests and not a substitute for plain-language, per-request MCP
236
+ controls. The plugin does not add persistent include-domain, exclude-selector,
237
+ or similar parity settings.
189
238
 
190
239
  ## Optional Browser Provider
191
240
 
@@ -243,6 +292,9 @@ hermes tinyfish doctor --live-paid
243
292
 
244
293
  - `status` is non-secret and reports ignored `0.2.x` policy keys under
245
294
  `retired_credit_policy_keys`.
295
+ - Status also reports whether routing and update checks are enabled, whether
296
+ routing is active, the installed version, the cached latest version, and
297
+ cached update availability. Reading status never performs an update request.
246
298
  - `mcp_token_cached` reports only whether Hermes's expected cache file is
247
299
  present. It does not mean the access or refresh token is valid.
248
300
  - `/tinyfish-status` shows non-networked status inside CLI or gateway sessions;
@@ -263,6 +315,8 @@ This plugin uses public Hermes extension points:
263
315
  - `ctx.register_web_search_provider(...)`
264
316
  - `ctx.register_browser_provider(...)`
265
317
  - `ctx.register_hook("pre_tool_call", ...)` for Browser credit policy
318
+ - `ctx.register_hook("pre_llm_call", ...)` for context-aware tool routing and
319
+ update notices
266
320
  - `ctx.register_cli_command(...)`
267
321
  - `ctx.register_command(...)` for `/tinyfish-status`
268
322
  - `ctx.dispatch_tool(...)` for registered TinyFish MCP calls
@@ -6,8 +6,8 @@
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
7
  [![Security Policy](https://img.shields.io/badge/Security-policy-green.svg)](SECURITY.md)
8
8
 
9
- TinyFish Search and Fetch providers, plus optional TinyFish Browser
10
- infrastructure, for
9
+ TinyFish Search and Fetch providers with plain-language tool routing, update
10
+ awareness, and optional TinyFish Browser infrastructure for
11
11
  [Hermes Agent](https://hermes-agent.nousresearch.com/docs/).
12
12
 
13
13
  Hermes remains the agent: it plans, chooses tools, and controls browser
@@ -35,6 +35,7 @@ Python package install:
35
35
 
36
36
  ```bash
37
37
  pip install hermes-plugin-tinyfish
38
+ hermes plugins enable web-tinyfish
38
39
  hermes tinyfish setup
39
40
  ```
40
41
 
@@ -56,6 +57,20 @@ hermes tinyfish status
56
57
  hermes tinyfish doctor
57
58
  ```
58
59
 
60
+ The plugin also performs a nonblocking update check when it loads. Git installs
61
+ are compared with the latest stable GitHub Release; Python package installs are
62
+ compared with the latest stable PyPI version. Results are cached for 24 hours
63
+ at `$HERMES_HOME/cache/web-tinyfish-update.json` in the active profile. If an
64
+ update is available, Hermes mentions it once on the first conversational turn
65
+ where the cached or background result is available. The plugin never installs
66
+ the update itself.
67
+
68
+ The check sends only the installed plugin version in its HTTP User-Agent. It
69
+ does not send Hermes configuration, TinyFish credentials, searches, fetched
70
+ URLs, or conversation content. Network and cache failures are silent. Disable
71
+ the check with `tinyfish.update_check: false` or the conventional
72
+ `NO_UPDATE_NOTIFIER=1` environment variable.
73
+
59
74
  ## Safe Default Setup
60
75
 
61
76
  `hermes tinyfish setup` configures TinyFish for Hermes `web_search` and
@@ -76,6 +91,8 @@ web:
76
91
  extract_backend: tinyfish
77
92
 
78
93
  tinyfish:
94
+ routing_context: true
95
+ update_check: true
79
96
  credit_policy:
80
97
  browser: deny
81
98
  ```
@@ -133,6 +150,35 @@ For browser authorization from a headless or remote terminal:
133
150
  - Treat the MCP-only doctor as authoritative. Some Hermes versions can print
134
151
  an authentication failure while still returning shell status 0.
135
152
 
153
+ ## Automatic Tool Routing
154
+
155
+ Talk to Hermes normally; you do not need to choose “the plugin” or “MCP.” When
156
+ an active context does not already contain the current routing version, the
157
+ plugin adds a short routing note that helps Hermes choose the narrowest tool
158
+ surface:
159
+
160
+ - “Find recent TinyFish documentation” uses Hermes `web_search`.
161
+ - “Read this page and summarize it” uses Hermes `web_extract`.
162
+ - “Search only these domains, in English, after this date, and return page 2”
163
+ uses TinyFish MCP's native `search` tool because the generic search schema
164
+ cannot express all of those controls.
165
+ - “Fetch these URLs using this selector, include image links, and use this
166
+ cache TTL” uses TinyFish MCP's native `fetch_content` tool.
167
+
168
+ Hermes keeps the visible stored user message clean but persists the exact
169
+ API-bound message in an `api_content` sidecar for prompt-cache replay. The note
170
+ contains a versioned marker, so the hook does not add another copy while that
171
+ version remains in the active context. If compression removes the marked
172
+ message, the hook adds one fresh copy. Multiple tool calls inside a turn do not
173
+ run the hook again. Hermes still sees the real tool schemas and makes the final
174
+ choice; the plugin does not keyword-match the prompt or force a tool.
175
+
176
+ Routing guidance defaults on only when the plugin-managed TinyFish MCP server
177
+ is configured. Set `tinyfish.routing_context: false` to disable the guidance
178
+ without disabling the provider. This prevents new injections; a copy already
179
+ present in the active API context may remain until that context is reset or
180
+ compressed.
181
+
136
182
  ## Search and Fetch Options
137
183
 
138
184
  Optional REST fallback defaults can be configured in `config.yaml`:
@@ -153,7 +199,10 @@ tinyfish:
153
199
  ```
154
200
 
155
201
  MCP remains the preferred path when configured. These options apply only to
156
- REST fallback calls.
202
+ REST fallback calls. They are fixed fallback defaults, not required settings
203
+ for ordinary requests and not a substitute for plain-language, per-request MCP
204
+ controls. The plugin does not add persistent include-domain, exclude-selector,
205
+ or similar parity settings.
157
206
 
158
207
  ## Optional Browser Provider
159
208
 
@@ -211,6 +260,9 @@ hermes tinyfish doctor --live-paid
211
260
 
212
261
  - `status` is non-secret and reports ignored `0.2.x` policy keys under
213
262
  `retired_credit_policy_keys`.
263
+ - Status also reports whether routing and update checks are enabled, whether
264
+ routing is active, the installed version, the cached latest version, and
265
+ cached update availability. Reading status never performs an update request.
214
266
  - `mcp_token_cached` reports only whether Hermes's expected cache file is
215
267
  present. It does not mean the access or refresh token is valid.
216
268
  - `/tinyfish-status` shows non-networked status inside CLI or gateway sessions;
@@ -231,6 +283,8 @@ This plugin uses public Hermes extension points:
231
283
  - `ctx.register_web_search_provider(...)`
232
284
  - `ctx.register_browser_provider(...)`
233
285
  - `ctx.register_hook("pre_tool_call", ...)` for Browser credit policy
286
+ - `ctx.register_hook("pre_llm_call", ...)` for context-aware tool routing and
287
+ update notices
234
288
  - `ctx.register_cli_command(...)`
235
289
  - `ctx.register_command(...)` for `/tinyfish-status`
236
290
  - `ctx.dispatch_tool(...)` for registered TinyFish MCP calls
@@ -39,22 +39,28 @@ __version__ = _resolve_version()
39
39
 
40
40
 
41
41
  def register(ctx: Any) -> None:
42
- """Register TinyFish providers, Browser policy hook, and CLI commands."""
42
+ """Register TinyFish providers, lifecycle hooks, and CLI commands."""
43
43
 
44
44
  from .browser_provider import TinyFishBrowserProvider
45
45
  from .credit_policy import pre_tool_call_policy
46
46
  from .provider import TinyFishWebSearchProvider
47
+ from .routing_context import TinyFishTurnContext
47
48
  from .setup_cli import dispatch_tinyfish_cli, setup_tinyfish_cli, tinyfish_status_command
49
+ from .update_check import UpdateChecker, resolve_install_info
48
50
 
49
51
  provider = TinyFishWebSearchProvider(dispatch_tool=getattr(ctx, "dispatch_tool", None))
52
+ update_checker = UpdateChecker(resolve_install_info(ctx, __version__))
53
+ turn_context = TinyFishTurnContext(update_checker)
50
54
  ctx.register_web_search_provider(provider)
51
55
  if hasattr(ctx, "register_browser_provider"):
52
56
  ctx.register_browser_provider(TinyFishBrowserProvider())
53
57
  if hasattr(ctx, "register_hook"):
54
58
  ctx.register_hook("pre_tool_call", pre_tool_call_policy)
59
+ ctx.register_hook("pre_llm_call", turn_context)
60
+ update_checker.start()
55
61
 
56
62
  def _dispatch_cli(args: Any) -> int:
57
- exit_code = dispatch_tinyfish_cli(args, provider=provider)
63
+ exit_code = dispatch_tinyfish_cli(args, provider=provider, update_checker=update_checker)
58
64
  if exit_code:
59
65
  raise SystemExit(exit_code)
60
66
  return 0
@@ -69,7 +75,11 @@ def register(ctx: Any) -> None:
69
75
  if hasattr(ctx, "register_command"):
70
76
  ctx.register_command(
71
77
  name="tinyfish-status",
72
- handler=lambda raw_args: tinyfish_status_command(raw_args, provider=provider),
78
+ handler=lambda raw_args: tinyfish_status_command(
79
+ raw_args,
80
+ provider=provider,
81
+ update_checker=update_checker,
82
+ ),
73
83
  description="Show TinyFish provider status; pass 'live' to test Search and Fetch.",
74
84
  args_hint="[live]",
75
85
  )
@@ -4,6 +4,8 @@ from __future__ import annotations
4
4
 
5
5
  from typing import Any, Literal
6
6
 
7
+ TINYFISH_MCP_URL = "https://agent.tinyfish.ai/mcp"
8
+
7
9
  CreditFeature = Literal["browser"]
8
10
  CreditPolicy = Literal["deny", "request", "allow"]
9
11
 
@@ -58,6 +60,16 @@ def tinyfish_config(config: dict[str, Any] | None = None) -> dict[str, Any]:
58
60
  return section if isinstance(section, dict) else {}
59
61
 
60
62
 
63
+ def routing_context_enabled(config: dict[str, Any] | None = None) -> bool:
64
+ value = _bool_option(tinyfish_config(config).get("routing_context"))
65
+ return True if value is None else value
66
+
67
+
68
+ def update_check_enabled(config: dict[str, Any] | None = None) -> bool:
69
+ value = _bool_option(tinyfish_config(config).get("update_check"))
70
+ return True if value is None else value
71
+
72
+
61
73
  def credit_policy(feature: CreditFeature | str, config: dict[str, Any] | None = None) -> CreditPolicy:
62
74
  normalized = normalize_feature(str(feature))
63
75
  section = tinyfish_config(config)
@@ -0,0 +1,75 @@
1
+ """Context-bounded TinyFish tool-routing guidance for Hermes turns."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping, Sequence
6
+ from typing import Any
7
+
8
+ from .config import TINYFISH_MCP_URL, load_config, routing_context_enabled
9
+ from .update_check import UpdateChecker
10
+
11
+ ROUTING_CONTEXT_MARKER = '<tinyfish-routing-context version="1">'
12
+ ROUTING_GUIDANCE = f"""{ROUTING_CONTEXT_MARKER}
13
+ TinyFish tool-routing guidance:
14
+ - For ordinary web discovery or reading a page, use Hermes `web_search` or `web_extract`; the TinyFish provider already keeps those calls MCP-first.
15
+ - When the request needs TinyFish-specific controls that the generic schemas cannot express—domain/date/language/location/purpose/pagination filters, selectors, output formats, link or image extraction, cache TTL, or per-URL timeouts—use the native `search` or `fetch_content` tool registered by the `tinyfish` MCP server. Use the exact names Hermes exposes, commonly `mcp__tinyfish__search` and `mcp__tinyfish__fetch_content`.
16
+ - Infer the choice from the user's plain language. Do not ask them to choose MCP versus the plugin, and do not persist per-request controls as configuration."""
17
+
18
+
19
+ def tinyfish_mcp_configured(config: dict[str, Any]) -> bool:
20
+ servers = config.get("mcp_servers") or {}
21
+ if not isinstance(servers, dict):
22
+ return False
23
+ tinyfish = servers.get("tinyfish") or {}
24
+ return bool(
25
+ isinstance(tinyfish, dict)
26
+ and tinyfish.get("url") == TINYFISH_MCP_URL
27
+ and tinyfish.get("auth") == "oauth"
28
+ )
29
+
30
+
31
+ def _contains_routing_marker(value: object) -> bool:
32
+ if isinstance(value, str):
33
+ return ROUTING_CONTEXT_MARKER in value
34
+ if isinstance(value, Mapping):
35
+ return any(_contains_routing_marker(item) for item in value.values())
36
+ if isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)):
37
+ return any(_contains_routing_marker(item) for item in value)
38
+ return False
39
+
40
+
41
+ def routing_guidance_present(conversation_history: object) -> bool:
42
+ """Return whether Hermes already carries this routing version in context."""
43
+
44
+ if not isinstance(conversation_history, Sequence) or isinstance(
45
+ conversation_history, (str, bytes, bytearray)
46
+ ):
47
+ return False
48
+ for message in conversation_history:
49
+ if not isinstance(message, Mapping):
50
+ continue
51
+ if _contains_routing_marker(message.get("api_content")):
52
+ return True
53
+ if _contains_routing_marker(message.get("content")):
54
+ return True
55
+ return False
56
+
57
+
58
+ class TinyFishTurnContext:
59
+ """Compose once-per-active-context routing with a maintenance notice."""
60
+
61
+ def __init__(self, update_checker: UpdateChecker) -> None:
62
+ self.update_checker = update_checker
63
+
64
+ def __call__(self, **kwargs: Any) -> dict[str, str] | None:
65
+ config = load_config()
66
+ context: list[str] = []
67
+ routing_needed = not routing_guidance_present(kwargs.get("conversation_history"))
68
+ if routing_context_enabled(config) and tinyfish_mcp_configured(config) and routing_needed:
69
+ context.append(ROUTING_GUIDANCE)
70
+ update_notice = self.update_checker.notice_context()
71
+ if update_notice:
72
+ context.append(update_notice)
73
+ if not context:
74
+ return None
75
+ return {"context": "\n\n".join(context)}
@@ -14,11 +14,13 @@ from .config import (
14
14
  CREDIT_FEATURES,
15
15
  CREDIT_POLICIES,
16
16
  RETIRED_CREDIT_POLICY_KEYS,
17
+ TINYFISH_MCP_URL,
17
18
  credit_policy_summary,
18
19
  normalize_feature,
19
20
  normalize_policy,
20
21
  reset_credit_policies,
21
22
  retired_credit_policy_keys,
23
+ routing_context_enabled,
22
24
  set_credit_policy,
23
25
  )
24
26
  from .credit_policy import INDEPENDENT_NOTICE, PRICING_NOTICE, request_credit_approval
@@ -28,8 +30,9 @@ from .provider import (
28
30
  MCP_SEARCH_TOOLS,
29
31
  TinyFishWebSearchProvider,
30
32
  )
33
+ from .routing_context import tinyfish_mcp_configured
34
+ from .update_check import InstallInfo, UpdateChecker
31
35
 
32
- TINYFISH_MCP_URL = "https://agent.tinyfish.ai/mcp"
33
36
  MCP_LOGIN_COMMAND = ("hermes", "mcp", "login", "tinyfish")
34
37
 
35
38
 
@@ -90,14 +93,15 @@ def dispatch_tinyfish_cli(
90
93
  args: argparse.Namespace,
91
94
  *,
92
95
  provider: TinyFishWebSearchProvider | None = None,
96
+ update_checker: UpdateChecker | None = None,
93
97
  ) -> int:
94
98
  command = getattr(args, "tinyfish_command", None) or "status"
95
99
  if command == "setup":
96
- return cmd_setup(args, provider=provider)
100
+ return cmd_setup(args, provider=provider, update_checker=update_checker)
97
101
  if command == "doctor":
98
- return cmd_doctor(args, provider=provider)
102
+ return cmd_doctor(args, provider=provider, update_checker=update_checker)
99
103
  if command == "status":
100
- return cmd_status(args, provider=provider)
104
+ return cmd_status(args, provider=provider, update_checker=update_checker)
101
105
  if command == "reauth":
102
106
  return cmd_reauth(args)
103
107
  if command == "credits":
@@ -207,6 +211,7 @@ def cmd_setup(
207
211
  args: argparse.Namespace,
208
212
  *,
209
213
  provider: TinyFishWebSearchProvider | None = None,
214
+ update_checker: UpdateChecker | None = None,
210
215
  ) -> int:
211
216
  config = _load_config()
212
217
 
@@ -254,7 +259,7 @@ def cmd_setup(
254
259
 
255
260
  if getattr(args, "live", False):
256
261
  doctor_args = argparse.Namespace(json=False, live=True, live_paid=False, transport="auto")
257
- return cmd_doctor(doctor_args, provider=provider)
262
+ return cmd_doctor(doctor_args, provider=provider, update_checker=update_checker)
258
263
 
259
264
  print("Run `hermes tinyfish doctor --live` to verify the setup.")
260
265
  browser_policy = credit_policy_summary(config)["browser"]
@@ -346,6 +351,7 @@ def collect_status(
346
351
  live: bool = False,
347
352
  transport: Transport = "auto",
348
353
  provider: TinyFishWebSearchProvider | None = None,
354
+ update_checker: UpdateChecker | None = None,
349
355
  ) -> dict[str, Any]:
350
356
  config = _load_config()
351
357
  mcp_cfg = (config.get("mcp_servers") or {}).get("tinyfish") or {}
@@ -358,9 +364,17 @@ def collect_status(
358
364
  api_key_configured = bool(_get_env("TINYFISH_API_KEY"))
359
365
  provider = provider or TinyFishWebSearchProvider()
360
366
  health = _health_status(provider)
367
+ if update_checker is None:
368
+ from . import __version__
369
+
370
+ update_checker = UpdateChecker(
371
+ InstallInfo(current_version=__version__, channel="github", update_command=None),
372
+ home=_hermes_home(),
373
+ )
374
+ update_status = update_checker.status(config)
361
375
 
362
376
  checks: dict[str, Any] = {
363
- "diagnostics_schema_version": 2,
377
+ "diagnostics_schema_version": 3,
364
378
  "plugin_loaded": True,
365
379
  "provider_available": provider.is_available(),
366
380
  "mcp_configured": bool(mcp_cfg.get("url") == TINYFISH_MCP_URL and mcp_cfg.get("auth") == "oauth"),
@@ -371,6 +385,8 @@ def collect_status(
371
385
  "api_key_fallback_configured": api_key_configured,
372
386
  "credit_policy": credit_policy_summary(config),
373
387
  "retired_credit_policy_keys": retired_credit_policy_keys(config),
388
+ "routing_context_enabled": routing_context_enabled(config),
389
+ "routing_context_active": bool(routing_context_enabled(config) and tinyfish_mcp_configured(config)),
374
390
  "independent_plugin_notice": INDEPENDENT_NOTICE,
375
391
  "pricing_notice": PRICING_NOTICE,
376
392
  "web_search_backend": web_cfg.get("search_backend") or web_cfg.get("backend") or "",
@@ -378,6 +394,7 @@ def collect_status(
378
394
  "browser_cloud_provider": (config.get("browser") or {}).get("cloud_provider", "")
379
395
  if isinstance(config.get("browser") or {}, dict)
380
396
  else "",
397
+ **update_status,
381
398
  **health,
382
399
  }
383
400
 
@@ -483,8 +500,9 @@ def cmd_status(
483
500
  args: argparse.Namespace,
484
501
  *,
485
502
  provider: TinyFishWebSearchProvider | None = None,
503
+ update_checker: UpdateChecker | None = None,
486
504
  ) -> int:
487
- status = collect_status(live=False, provider=provider)
505
+ status = collect_status(live=False, provider=provider, update_checker=update_checker)
488
506
  if getattr(args, "json", False):
489
507
  print(json.dumps(status, indent=2, sort_keys=True))
490
508
  else:
@@ -496,6 +514,7 @@ def cmd_doctor(
496
514
  args: argparse.Namespace,
497
515
  *,
498
516
  provider: TinyFishWebSearchProvider | None = None,
517
+ update_checker: UpdateChecker | None = None,
499
518
  ) -> int:
500
519
  live = bool(getattr(args, "live", False))
501
520
  transport = str(getattr(args, "transport", "auto") or "auto")
@@ -510,6 +529,7 @@ def cmd_doctor(
510
529
  live=live,
511
530
  transport=cast(Transport, transport),
512
531
  provider=provider,
532
+ update_checker=update_checker,
513
533
  )
514
534
  if getattr(args, "live_paid", False):
515
535
  paid_ok = _run_live_paid_checks(status)
@@ -566,13 +586,19 @@ def tinyfish_status_command(
566
586
  raw_args: str,
567
587
  *,
568
588
  provider: TinyFishWebSearchProvider,
589
+ update_checker: UpdateChecker | None = None,
569
590
  ) -> str:
570
591
  """Serve the in-session ``/tinyfish-status`` command."""
571
592
 
572
593
  option = (raw_args or "").strip().lower()
573
594
  if option not in {"", "live"}:
574
595
  return "Usage: /tinyfish-status [live]"
575
- status = collect_status(live=option == "live", transport="auto", provider=provider)
596
+ status = collect_status(
597
+ live=option == "live",
598
+ transport="auto",
599
+ provider=provider,
600
+ update_checker=update_checker,
601
+ )
576
602
  return "\n".join(_status_lines(status))
577
603
 
578
604