@viztor/dsh-tinyfish 0.3.0
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.
Potentially problematic release.
This version of @viztor/dsh-tinyfish might be problematic. Click here for more details.
- package/LICENSE +21 -0
- package/README.md +212 -0
- package/assets/tinyfish.svg +18 -0
- package/cordis.patch.yml +41 -0
- package/lib/client.js +400 -0
- package/lib/index.d.mts +405 -0
- package/lib/index.mjs +825 -0
- package/package.json +106 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 viztor
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# dsh-tinyfish
|
|
2
|
+
|
|
3
|
+
<img src="assets/tinyfish.svg" alt="TinyFish logo" width="96" />
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/dsh-tinyfish) [](https://www.npmjs.com/package/dsh-tinyfish) [](https://github.com/viztor/dsh-tinyfish/actions/workflows/ci.yml) [](https://github.com/viztor/dsh-tinyfish/blob/main/LICENSE) [](https://nodejs.org)
|
|
6
|
+
|
|
7
|
+
**Search and fetch for the DeepSeek Harness, at $0.**
|
|
8
|
+
|
|
9
|
+
A DSH bundle that makes [TinyFish](https://tinyfish.ai) the implementation of the harness's own `web_search` and `web_fetch` tools. Both endpoints are free, so the web path on your host stops costing money per call.
|
|
10
|
+
|
|
11
|
+
| | `dsh-web`'s default | with `dsh-tinyfish` |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| search | `deepseek-official` | TinyFish `/search` via Monid, **$0** |
|
|
14
|
+
| fetch | `http` | TinyFish `/fetch`, **$0**, returns clean Markdown |
|
|
15
|
+
| turndown conversion | yes, on every fetch | **no** — the content is already Markdown |
|
|
16
|
+
| reversibility | — | two words, no reinstall |
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
cd ~/.dsh/profiles/web
|
|
22
|
+
npm install dsh-tinyfish
|
|
23
|
+
# or, identically:
|
|
24
|
+
npm install @viztor/dsh-tinyfish
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Both names ship the same content from the same release; `dsh-tinyfish` is the name DSH resolves and the docs use.
|
|
28
|
+
|
|
29
|
+
Add the bundle to that profile's `package.json`, then restart DSH:
|
|
30
|
+
|
|
31
|
+
```jsonc
|
|
32
|
+
{
|
|
33
|
+
"dependencies": { "dsh-tinyfish": "^0.2.0" },
|
|
34
|
+
"dsh": {
|
|
35
|
+
"profile": {
|
|
36
|
+
"bundles": [
|
|
37
|
+
"@deepseek-ai/dsh-base",
|
|
38
|
+
"@deepseek-ai/dsh-web-app",
|
|
39
|
+
"dsh-tinyfish",
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
That is the whole install. The bundle's own `cordis.patch.yml` selects itself:
|
|
47
|
+
|
|
48
|
+
```yaml
|
|
49
|
+
- id: web
|
|
50
|
+
name: "@deepseek-ai/dsh-web"
|
|
51
|
+
config:
|
|
52
|
+
searchProvider: tinyfish
|
|
53
|
+
fetchProvider: tinyfish
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Restarting matters: bundles are resolved when the harness boots, so `patchReload` will not pick up a newly mounted one.
|
|
57
|
+
|
|
58
|
+
**To go back**, set those two back to `deepseek-official` and `http`. The bundle stays mounted and idle — registration and selection are separate, and only your profile decides which provider wins.
|
|
59
|
+
|
|
60
|
+
**Requirements:** DSH **0.2.0+**, Node **22.14+**, and a credential (below). The harness supplies the `@deepseek-ai/*` peer packages; you do not install them.
|
|
61
|
+
|
|
62
|
+
## Verify
|
|
63
|
+
|
|
64
|
+
Ask the agent to search for something. Or check the wiring without an agent:
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
cd ~/.dsh/profiles/web
|
|
68
|
+
node -e '
|
|
69
|
+
const m = require("dsh-tinyfish");
|
|
70
|
+
const ctx = { web: { registerSearchProvider(){}, registerFetchProvider(){} }, get: () => undefined };
|
|
71
|
+
m.apply(ctx, m.Config({}));
|
|
72
|
+
console.log("registered:", m.name, "| no throw above = wired");
|
|
73
|
+
'
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
If search reports `WEB_PROVIDER_CREDENTIAL_MISSING` or `WEB_PROVIDER_UNAVAILABLE`, that is the next section.
|
|
77
|
+
|
|
78
|
+
## Credentials
|
|
79
|
+
|
|
80
|
+
A fresh install has none, and never will — a key inside an npm tarball would be published forever. Both endpoints are free, but both need an account.
|
|
81
|
+
|
|
82
|
+
**In the profile patch (no restart needed).** The `web-tinyfish` row in your profile's `cordis.patch.yml` sets `apiKeyEnv`, which the harness resolves through its credentials service and the launch environment, and re-reads on every call — so a change takes effect on the next search.
|
|
83
|
+
|
|
84
|
+
**By hand:**
|
|
85
|
+
|
|
86
|
+
| channel | get a key | then |
|
|
87
|
+
| --- | --- | --- |
|
|
88
|
+
| `direct` default | [tinyfish.ai](https://tinyfish.ai) → API keys | `tinyfish auth login`, or `echo $KEY \| tinyfish auth set` |
|
|
89
|
+
| `monid` | [app.monid.ai](https://app.monid.ai) | `monid keys add`, or `export MONID_API_KEY` |
|
|
90
|
+
|
|
91
|
+
The default is `direct` because the package is named for TinyFish: a fresh install asks for the credential its own name implies rather than for an account at a different service. If you would rather go through Monid — it reuses a platform key a Monid MCP mount already holds, and costs the same — pin it in your own profile patch, which is a host decision and does not need a new release of this plugin:
|
|
92
|
+
|
|
93
|
+
```yaml
|
|
94
|
+
- id: web-tinyfish
|
|
95
|
+
config:
|
|
96
|
+
channel: monid
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Resolution order, first match wins: a literal `apiKey` in the settings row → the harness credentials service → the launch environment → the live environment → the CLI stores. A failing credential service falls through to the next source rather than failing the search.
|
|
100
|
+
|
|
101
|
+
## Configuration
|
|
102
|
+
|
|
103
|
+
Everything lives in one row, `web-tinyfish`, edited in your profile's `cordis.patch.yml`. The row is validated, so an out-of-range value is rejected with a message rather than silently clamped.
|
|
104
|
+
|
|
105
|
+
**Settings → Plugins → TinyFish** edits this row, if you prefer a form to a patch file. The plugin ships both halves: the provider the harness loads, and a client bundle that contributes the page. Changes are staged and written on save, and a key you type is stored by the harness rather than in your profile.
|
|
106
|
+
|
|
107
|
+
The patch file is still the honest place for the first edit — it is where a selection that overrides someone else's layer belongs, and it needs no build. Use whichever suits the change.
|
|
108
|
+
|
|
109
|
+
| key | default | meaning |
|
|
110
|
+
| --- | --- | --- |
|
|
111
|
+
| `channel` | `direct` | `monid` or `direct`; see [Credentials](#credentials) |
|
|
112
|
+
| `apiKey` | _(unset)_ | literal credential for either channel; prefer a ref |
|
|
113
|
+
| `apiKeyEnv` | `TINYFISH_API_KEY` | credential reference, or env var, for `direct` |
|
|
114
|
+
| `monidKeyEnv` | `MONID_API_KEY` | credential reference, or env var, for `monid` |
|
|
115
|
+
| `purpose` | _(unset)_ | goal statement; TinyFish ranks on it |
|
|
116
|
+
| `attempts` | `3` | retries for a transient failure or an empty search (1–5) |
|
|
117
|
+
| `filters.domainType` | _(unset)_ | `web` \| `news` \| `research_paper` |
|
|
118
|
+
| `filters.language` / `.location` | _(unset)_ | geo targeting |
|
|
119
|
+
| `filters.includeDomains` / `.excludeDomains` | _(unset)_ | comma-separated |
|
|
120
|
+
| `monidBase` / `searchBase` / `fetchBase` | upstream | endpoint override, for staging |
|
|
121
|
+
| `search` / `fetch` | `true` | offer this kind at all; `false` declines without unregistering |
|
|
122
|
+
|
|
123
|
+
Search and fetch are switched independently. Both always register, so turning one off makes it report _unavailable_ rather than _missing_ — the harness tells those apart, and only the second means "I turned this off" rather than "the install is broken".
|
|
124
|
+
|
|
125
|
+
```yaml
|
|
126
|
+
- id: web-tinyfish
|
|
127
|
+
config:
|
|
128
|
+
search: true
|
|
129
|
+
fetch: false # keep TinyFish for search, let dsh-web use another fetch
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Where a value comes from
|
|
133
|
+
|
|
134
|
+
Every setting resolves in the same three rungs — **row, then environment, then built-in default** — so a deployment can be retargeted without writing a patch file. This is the shape the shipped providers use for `$DEEPSEEK_SEARCH_BASE_URL`.
|
|
135
|
+
|
|
136
|
+
| setting | environment variable |
|
|
137
|
+
| ------------ | -------------------------- |
|
|
138
|
+
| `monidBase` | `TINYFISH_MONID_BASE_URL` |
|
|
139
|
+
| `searchBase` | `TINYFISH_SEARCH_BASE_URL` |
|
|
140
|
+
| `fetchBase` | `TINYFISH_FETCH_BASE_URL` |
|
|
141
|
+
|
|
142
|
+
An endpoint that does not parse makes the provider report itself unavailable rather than being trusted.
|
|
143
|
+
|
|
144
|
+
### The credential, in order
|
|
145
|
+
|
|
146
|
+
Resolved **per call**, so a key rotated anywhere below takes effect on the next search with no restart. First match wins:
|
|
147
|
+
|
|
148
|
+
| # | source | set it by |
|
|
149
|
+
| --- | --- | --- |
|
|
150
|
+
| 1 | the `apiKey` literal | the row — a secret in config; prefer 2–3 |
|
|
151
|
+
| 2 | the credentials service | `apiKeyEnv` (direct) or `monidKeyEnv` (monid) in the settings UI |
|
|
152
|
+
| 3 | the launch environment | exported before DSH started |
|
|
153
|
+
| 4 | the live environment | `MONID_API_KEY` / `TINYFISH_API_KEY` |
|
|
154
|
+
| 5 | the channel's CLI store | `monid keys add` / `tinyfish auth login` |
|
|
155
|
+
|
|
156
|
+
The harness services sit above the environment on purpose: a value someone typed into Settings is a more deliberate choice than one that merely happens to be exported. A failing service falls through to the next source rather than failing the search, and a host that mounts neither still works.
|
|
157
|
+
|
|
158
|
+
### Two keys, one settings page
|
|
159
|
+
|
|
160
|
+
The two channels authenticate against different services, so each has **its own** credential reference: `apiKeyEnv` (default `TINYFISH_API_KEY`) for `direct`, and `monidKeyEnv` (default `MONID_API_KEY`) for `monid`. Saving one never overwrites the other, so both can be live at once and switching channels back and forth loses nothing.
|
|
161
|
+
|
|
162
|
+
The page shows the key for the **selected** channel only. Showing both at once would invite pasting the Monid platform key into the field TinyFish authenticates with — and a key sent to the wrong service fails as a 401, which reads as "that key is wrong" rather than as "that was the wrong field". The `apiKey` literal still overrides either channel; it exists for a patch file, and the settings page does not write it.
|
|
163
|
+
|
|
164
|
+
## Why the fetch path is a real improvement
|
|
165
|
+
|
|
166
|
+
`dsh-tool-web` renders a `kind: "html"` body by running **turndown** to convert HTML to Markdown, behind a depth cap with a `"[HTML content omitted]"` fallback. TinyFish already extracts clean Markdown in a browser-grade extractor, so this provider returns `kind: "text"` and the content reaches the model with no conversion step at all.
|
|
167
|
+
|
|
168
|
+
## Two channels, one payload
|
|
169
|
+
|
|
170
|
+
Monid is a thin envelope whose `output` is the direct response verbatim, and it forwards parameter names unchanged. One transport serves both, and nothing above it branches on which is active — a test asserts the two agree on the top hit for the same query.
|
|
171
|
+
|
|
172
|
+
| | `direct` (default) | `monid` |
|
|
173
|
+
| ------ | ---------------------------- | -------------------------- |
|
|
174
|
+
| search | `GET api.search.tinyfish.ai` | `POST api.monid.ai/v1/run` |
|
|
175
|
+
| fetch | `POST api.fetch.tinyfish.ai` | `POST api.monid.ai/v1/run` |
|
|
176
|
+
| auth | `X-API-Key` | `Authorization: Bearer` |
|
|
177
|
+
| cost | $0, direct | $0, on the Monid wallet |
|
|
178
|
+
|
|
179
|
+
## Behaviour worth knowing
|
|
180
|
+
|
|
181
|
+
- **A 404 is a result, not an error.** A per-URL fetch failure comes back carrying its status, because that is resource state the model needs.
|
|
182
|
+
- **`publishedAt` is honest.** TinyFish reports dates as human strings (`"Apr 30, 2026"`, `"1 year ago"`). What parses is coerced to ISO-8601; what does not is dropped rather than invented. Unzoned dates are read as UTC, so the same page reports the same day regardless of where the Worker ran.
|
|
183
|
+
- **Search retries an empty result.** The upstream answers a valid query with nothing about one run in three, so a blank result is retried up to `attempts` before it is believed.
|
|
184
|
+
- **A blocked run is terminal.** If a Monid workspace control stops a run, the error says why and links to top up. It is never retried.
|
|
185
|
+
|
|
186
|
+
## Not included
|
|
187
|
+
|
|
188
|
+
TinyFish's `agent` and `browser` surfaces are **not** exposed. They cost $0.016/step and $0.002/min, are metered against a wallet, and do not fit `ctx.web` — that seam has exactly two provider kinds, and an agent run is an action, not a search or a fetch. Use the `tinyfish` CLI directly when a page genuinely needs a real browser.
|
|
189
|
+
|
|
190
|
+
## Development
|
|
191
|
+
|
|
192
|
+
The toolchain is [Vite+](https://viteplus.dev): `vp pack` builds the library with tsdown, `vp test` runs Vitest, and `vp lint` / `vp fmt` are Oxlint and Oxfmt, type-aware. Lint and format settings live in the `lint` and `fmt` blocks of `vite.config.ts` — Vite+ disables nested Oxlint/Oxfmt configs, so a standalone `oxlint.config.ts` would be read by nobody.
|
|
193
|
+
|
|
194
|
+
```sh
|
|
195
|
+
pnpm install
|
|
196
|
+
pnpm test # 78 hermetic tests — no network, no credential
|
|
197
|
+
pnpm run check # format + lint + types
|
|
198
|
+
pnpm run release:gate # build, then the full gate
|
|
199
|
+
pnpm run test:live # the real APIs, still $0, needs credentials
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`pnpm run ci` ends in one script, `scripts/check.mjs`, that runs six package checks in a single pass: `lib/` freshness, peer ranges npm can parse, the bundle contract, the harness surfaces still being present, no credentials in the tree, and — the one that earns its keep — packing the tarball, installing it with plain npm, and loading it. Each is proved by planting the regression it guards, and CI runs all of it.
|
|
203
|
+
|
|
204
|
+
Full process and invariants: [`AGENTS.md`](./AGENTS.md).
|
|
205
|
+
|
|
206
|
+
## Compatibility
|
|
207
|
+
|
|
208
|
+
Requires **DSH 0.2.0+**; tested against 0.2.0-rc.1. The `@deepseek-ai/dsh-*` peers are `^0.2.0-rc.1`, so a DSH patch release will not orphan the plugin, and a 0.3 contract change still fails loudly rather than silently.
|
|
209
|
+
|
|
210
|
+
## License
|
|
211
|
+
|
|
212
|
+
MIT
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="TinyFish logo">
|
|
2
|
+
<title>TinyFish</title>
|
|
3
|
+
<defs>
|
|
4
|
+
<linearGradient id="tf-fish" x1="12" y1="20" x2="52" y2="44" gradientUnits="userSpaceOnUse">
|
|
5
|
+
<stop offset="0" stop-color="#4D6BFE"/>
|
|
6
|
+
<stop offset="1" stop-color="#14B8A6"/>
|
|
7
|
+
</linearGradient>
|
|
8
|
+
</defs>
|
|
9
|
+
<!--
|
|
10
|
+
The DeepSeek Harness icon family: a softly tinted rounded square carrying
|
|
11
|
+
one vibrant glyph. Pale blue container, DeepSeek-blue-to-teal fish, white
|
|
12
|
+
knockout eye. Fill only, so it stays crisp at any size.
|
|
13
|
+
-->
|
|
14
|
+
<rect x="2" y="2" width="60" height="60" rx="15" fill="#E8F1FE"/>
|
|
15
|
+
<ellipse cx="27" cy="33" rx="15" ry="11" fill="url(#tf-fish)"/>
|
|
16
|
+
<polygon points="39,33 53,23 53,43" fill="url(#tf-fish)"/>
|
|
17
|
+
<circle cx="21" cy="30.5" r="2.4" fill="#ffffff"/>
|
|
18
|
+
</svg>
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Bundle patch for `dsh-tinyfish`.
|
|
2
|
+
#
|
|
3
|
+
# The plugin only *offers* the `tinyfish` provider on both seam kinds. Whether
|
|
4
|
+
# it is actually used is the profile's decision, in `dsh-web`'s config:
|
|
5
|
+
#
|
|
6
|
+
# - id: web
|
|
7
|
+
# name: '@deepseek-ai/dsh-web'
|
|
8
|
+
# config:
|
|
9
|
+
# searchProvider: tinyfish
|
|
10
|
+
# fetchProvider: tinyfish
|
|
11
|
+
#
|
|
12
|
+
# Both rows are kept separate from the plugin row so the id is set in one place
|
|
13
|
+
# and swapping back to `deepseek-official` / `http` is a two-word edit rather
|
|
14
|
+
# than a plugin removal. Registering a provider nobody selects is free.
|
|
15
|
+
#
|
|
16
|
+
# `channel` picks the upstream route and is deliberately NOT set here, so the
|
|
17
|
+
# schema default stands: `direct`, which calls TinyFish's own API with the key
|
|
18
|
+
# the `tinyfish` CLI stored in ~/.tinyfish/config.json. A package named for
|
|
19
|
+
# TinyFish should not open by asking for an account at a different service.
|
|
20
|
+
#
|
|
21
|
+
# A host that prefers the Monid envelope — which reuses the credential a Monid
|
|
22
|
+
# MCP mount already holds, and costs the same — pins it in its own patch layer,
|
|
23
|
+
# where the preference belongs and where it can be changed without a new
|
|
24
|
+
# release:
|
|
25
|
+
#
|
|
26
|
+
# - id: web-tinyfish
|
|
27
|
+
# config:
|
|
28
|
+
# channel: monid
|
|
29
|
+
#
|
|
30
|
+
# `apiKey` is not set here either — a secret belongs in the environment, the
|
|
31
|
+
# CLI's own store, or the harness credentials service, never in a patch file.
|
|
32
|
+
- insert:
|
|
33
|
+
- id: web-tinyfish
|
|
34
|
+
name: "dsh-tinyfish"
|
|
35
|
+
config:
|
|
36
|
+
attempts: 3
|
|
37
|
+
- id: web
|
|
38
|
+
name: "@deepseek-ai/dsh-web"
|
|
39
|
+
config:
|
|
40
|
+
searchProvider: tinyfish
|
|
41
|
+
fetchProvider: tinyfish
|