dsh-tinyfish 0.2.0 → 0.4.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.
- package/README.md +106 -116
- package/assets/tinyfish.svg +18 -0
- package/lib/client.js +27 -3
- package/lib/index.mjs +5 -1
- package/package.json +7 -5
package/README.md
CHANGED
|
@@ -1,30 +1,52 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="assets/tinyfish.svg" alt="TinyFish logo" width="120" />
|
|
3
|
+
<h1>dsh-tinyfish</h1>
|
|
4
|
+
<p><strong>Free web search and fetch for the DeepSeek Harness.</strong><br />Give your agent the live web — at $0 per call.</p>
|
|
2
5
|
|
|
3
|
-
[](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)
|
|
6
|
+
[](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)
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
</div>
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
---
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
|
12
|
-
|
|
|
13
|
-
|
|
|
14
|
-
|
|
|
12
|
+
Your agent can already reason. This gives it something to reason _about_: live search results and clean page content, wired straight into the harness's own `web_search` and `web_fetch` tools. Powered by [TinyFish](https://tinyfish.ai) — both endpoints are free, so the web path on your host stops costing money per call.
|
|
13
|
+
|
|
14
|
+
| Before | After |
|
|
15
|
+
| --- | --- |
|
|
16
|
+
| Search bills per call | **$0**, forever |
|
|
17
|
+
| Fetched pages arrive as HTML, converted clumsily | **Clean Markdown**, straight from a browser-grade extractor |
|
|
18
|
+
| Switching providers means reinstalling | **Two words** in a config file, no reinstall |
|
|
15
19
|
|
|
16
|
-
##
|
|
20
|
+
## 🚀 Quick start
|
|
21
|
+
|
|
22
|
+
**1. Install** — in your web profile:
|
|
17
23
|
|
|
18
24
|
```sh
|
|
19
25
|
cd ~/.dsh/profiles/web
|
|
20
|
-
npm install dsh-tinyfish
|
|
26
|
+
npm install dsh-tinyfish # or: npm install @viztor/dsh-tinyfish — same thing
|
|
21
27
|
```
|
|
22
28
|
|
|
23
|
-
|
|
29
|
+
<details>
|
|
30
|
+
<summary><strong>📦 Installing from GitHub Packages instead</strong></summary>
|
|
31
|
+
|
|
32
|
+
<br />
|
|
33
|
+
|
|
34
|
+
Every release mirrors both names to GitHub Packages — a second source if npmjs.org is unreachable, and what populates the repository sidebar. Unlike npmjs, GitHub Packages requires authentication even for public packages: an unauthenticated request 404s without saying whether the package exists. With a token carrying `read:packages`:
|
|
35
|
+
|
|
36
|
+
```ini
|
|
37
|
+
# project-local .npmrc is better than global for a token
|
|
38
|
+
@viztor:registry=https://npm.pkg.github.com
|
|
39
|
+
//npm.pkg.github.com/:_authToken=ghp_xxx
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
then `npm install @viztor/dsh-tinyfish` resolves from the mirror. Unless npmjs is down, prefer it: no token, no extra configuration.
|
|
43
|
+
</details>
|
|
44
|
+
|
|
45
|
+
**2. Mount it** — add to that profile's `package.json`, then restart DSH:
|
|
24
46
|
|
|
25
47
|
```jsonc
|
|
26
48
|
{
|
|
27
|
-
"dependencies": { "dsh-tinyfish": "^0.
|
|
49
|
+
"dependencies": { "dsh-tinyfish": "^0.3.0" },
|
|
28
50
|
"dsh": {
|
|
29
51
|
"profile": {
|
|
30
52
|
"bundles": [
|
|
@@ -37,52 +59,20 @@ Add the bundle to that profile's `package.json`, then restart DSH:
|
|
|
37
59
|
}
|
|
38
60
|
```
|
|
39
61
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
```yaml
|
|
43
|
-
- id: web
|
|
44
|
-
name: "@deepseek-ai/dsh-web"
|
|
45
|
-
config:
|
|
46
|
-
searchProvider: tinyfish
|
|
47
|
-
fetchProvider: tinyfish
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
Restarting matters: bundles are resolved when the harness boots, so `patchReload` will not pick up a newly mounted one.
|
|
51
|
-
|
|
52
|
-
**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.
|
|
53
|
-
|
|
54
|
-
**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.
|
|
55
|
-
|
|
56
|
-
## Verify
|
|
62
|
+
> Bundles resolve at boot, so a restart picks it up — reloading the patch alone won't.
|
|
57
63
|
|
|
58
|
-
|
|
64
|
+
**3. Add a key** — pick a channel below, save the key, and ask your agent to search for something. That's the whole install.
|
|
59
65
|
|
|
60
|
-
|
|
61
|
-
cd ~/.dsh/profiles/web
|
|
62
|
-
node -e '
|
|
63
|
-
const m = require("dsh-tinyfish");
|
|
64
|
-
const ctx = { web: { registerSearchProvider(){}, registerFetchProvider(){} }, get: () => undefined };
|
|
65
|
-
m.apply(ctx, m.Config({}));
|
|
66
|
-
console.log("registered:", m.name, "| no throw above = wired");
|
|
67
|
-
'
|
|
68
|
-
```
|
|
66
|
+
## 🔑 Two channels, one plugin
|
|
69
67
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
## Credentials
|
|
73
|
-
|
|
74
|
-
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.
|
|
75
|
-
|
|
76
|
-
**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.
|
|
77
|
-
|
|
78
|
-
**By hand:**
|
|
79
|
-
|
|
80
|
-
| channel | get a key | then |
|
|
68
|
+
| | Direct _(default)_ | Via Monid |
|
|
81
69
|
| --- | --- | --- |
|
|
82
|
-
|
|
|
83
|
-
|
|
|
70
|
+
| What's behind it | TinyFish's own API | The same TinyFish endpoints, through your Monid wallet |
|
|
71
|
+
| You need | A free key from [tinyfish.ai](https://tinyfish.ai) | A platform key from [app.monid.ai](https://app.monid.ai) |
|
|
72
|
+
| Fastest setup | `tinyfish auth login` | `monid keys add` |
|
|
73
|
+
| Costs | $0 | $0 |
|
|
84
74
|
|
|
85
|
-
The default is `direct
|
|
75
|
+
The default is `direct`, because the package is named for TinyFish — a fresh install asks for the credential its own name implies. Prefer Monid (it reuses a platform key your Monid MCP mount may already hold)? Pin it in your profile patch:
|
|
86
76
|
|
|
87
77
|
```yaml
|
|
88
78
|
- id: web-tinyfish
|
|
@@ -90,31 +80,38 @@ The default is `direct` because the package is named for TinyFish: a fresh insta
|
|
|
90
80
|
channel: monid
|
|
91
81
|
```
|
|
92
82
|
|
|
93
|
-
|
|
83
|
+
Both keys can live side by side — saving one never overwrites the other, and switching channels loses nothing.
|
|
84
|
+
|
|
85
|
+
## ⚙️ Settings page
|
|
94
86
|
|
|
95
|
-
|
|
87
|
+
**Settings → Plugins → TinyFish.** Everything editable lives here: the channel picker, your keys, what the search ranks on, retries, and the search/fetch toggles. Changes stage and save together; a key you type is stored by the harness, never in your profile.
|
|
96
88
|
|
|
97
|
-
|
|
89
|
+
The page shows the key for the **selected** channel, plus the saved-or-not status of both — so you always know where you stand without switching back and forth.
|
|
98
90
|
|
|
99
|
-
|
|
91
|
+
> Going back is two words: set `searchProvider`/`fetchProvider` to `deepseek-official` and `http`. The bundle stays mounted and idle.
|
|
100
92
|
|
|
101
|
-
|
|
93
|
+
<details>
|
|
94
|
+
<summary><strong>📖 Full configuration reference</strong></summary>
|
|
95
|
+
|
|
96
|
+
<br />
|
|
97
|
+
|
|
98
|
+
Everything lives in one row, `web-tinyfish`. The row is validated, so an out-of-range value is rejected with a message rather than silently clamped.
|
|
102
99
|
|
|
103
100
|
| key | default | meaning |
|
|
104
101
|
| --- | --- | --- |
|
|
105
|
-
| `channel` | `direct` | `monid` or `direct
|
|
102
|
+
| `channel` | `direct` | `monid` or `direct` |
|
|
106
103
|
| `apiKey` | _(unset)_ | literal credential for either channel; prefer a ref |
|
|
107
104
|
| `apiKeyEnv` | `TINYFISH_API_KEY` | credential reference, or env var, for `direct` |
|
|
108
105
|
| `monidKeyEnv` | `MONID_API_KEY` | credential reference, or env var, for `monid` |
|
|
109
106
|
| `purpose` | _(unset)_ | goal statement; TinyFish ranks on it |
|
|
110
107
|
| `attempts` | `3` | retries for a transient failure or an empty search (1–5) |
|
|
111
|
-
| `filters.domainType` | _(unset)_ | `web` \| `news` \| `research_paper` |
|
|
112
|
-
| `filters.language` / `.location` | _(unset)_ | geo targeting |
|
|
113
|
-
| `filters.includeDomains` / `.excludeDomains` | _(unset)_ | comma-separated |
|
|
108
|
+
| `filters.domainType` | _(unset)_ | `web` \| `news` \| `research_paper` — patch file only |
|
|
109
|
+
| `filters.language` / `.location` | _(unset)_ | geo targeting — patch file only |
|
|
110
|
+
| `filters.includeDomains` / `.excludeDomains` | _(unset)_ | comma-separated — patch file only |
|
|
114
111
|
| `monidBase` / `searchBase` / `fetchBase` | upstream | endpoint override, for staging |
|
|
115
112
|
| `search` / `fetch` | `true` | offer this kind at all; `false` declines without unregistering |
|
|
116
113
|
|
|
117
|
-
|
|
114
|
+
Turning one off reports _unavailable_ rather than _missing_ — the harness tells those apart, and only the second means "the install is broken".
|
|
118
115
|
|
|
119
116
|
```yaml
|
|
120
117
|
- id: web-tinyfish
|
|
@@ -123,83 +120,76 @@ Search and fetch are switched independently. Both always register, so turning on
|
|
|
123
120
|
fetch: false # keep TinyFish for search, let dsh-web use another fetch
|
|
124
121
|
```
|
|
125
122
|
|
|
126
|
-
###
|
|
123
|
+
### Filters live in the patch file
|
|
127
124
|
|
|
128
|
-
|
|
125
|
+
`filters` is a nested object, and the settings form addresses one flat key per field — so search tuning stays operator-level:
|
|
129
126
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
Resolved **per call**, so a key rotated anywhere below takes effect on the next search with no restart. First match wins:
|
|
141
|
-
|
|
142
|
-
| # | source | set it by |
|
|
143
|
-
| --- | --- | --- |
|
|
144
|
-
| 1 | the `apiKey` literal | the row — a secret in config; prefer 2–3 |
|
|
145
|
-
| 2 | the credentials service | `apiKeyEnv` (direct) or `monidKeyEnv` (monid) in the settings UI |
|
|
146
|
-
| 3 | the launch environment | exported before DSH started |
|
|
147
|
-
| 4 | the live environment | `MONID_API_KEY` / `TINYFISH_API_KEY` |
|
|
148
|
-
| 5 | the channel's CLI store | `monid keys add` / `tinyfish auth login` |
|
|
127
|
+
```yaml
|
|
128
|
+
- id: web-tinyfish
|
|
129
|
+
config:
|
|
130
|
+
channel: monid
|
|
131
|
+
filters:
|
|
132
|
+
domainType: research_paper
|
|
133
|
+
language: zh
|
|
134
|
+
includeDomains: arxiv.org,openreview.net
|
|
135
|
+
```
|
|
149
136
|
|
|
150
|
-
|
|
137
|
+
### Where a credential comes from
|
|
151
138
|
|
|
152
|
-
|
|
139
|
+
Resolved **per call** — a rotated key takes effect on the next search, no restart. First match wins:
|
|
153
140
|
|
|
154
|
-
|
|
141
|
+
1. the `apiKey` literal in the row (a secret in config; prefer 2–3)
|
|
142
|
+
2. the credentials service — `apiKeyEnv` (direct) or `monidKeyEnv` (monid), saved from the settings UI
|
|
143
|
+
3. the launch environment (exported before DSH started)
|
|
144
|
+
4. the live environment (`MONID_API_KEY` / `TINYFISH_API_KEY`)
|
|
145
|
+
5. the channel's CLI store (`monid keys add` / `tinyfish auth login`)
|
|
155
146
|
|
|
156
|
-
|
|
147
|
+
A failing service falls through to the next source rather than failing the search.
|
|
157
148
|
|
|
158
|
-
|
|
149
|
+
### Where an endpoint comes from
|
|
159
150
|
|
|
160
|
-
|
|
151
|
+
Row, then environment, then built-in default — so staging can retarget without a patch:
|
|
161
152
|
|
|
162
|
-
|
|
153
|
+
| setting | environment variable |
|
|
154
|
+
| ------------ | -------------------------- |
|
|
155
|
+
| `monidBase` | `TINYFISH_MONID_BASE_URL` |
|
|
156
|
+
| `searchBase` | `TINYFISH_SEARCH_BASE_URL` |
|
|
157
|
+
| `fetchBase` | `TINYFISH_FETCH_BASE_URL` |
|
|
163
158
|
|
|
164
|
-
|
|
159
|
+
</details>
|
|
165
160
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
| search | `GET api.search.tinyfish.ai` | `POST api.monid.ai/v1/run` |
|
|
169
|
-
| fetch | `POST api.fetch.tinyfish.ai` | `POST api.monid.ai/v1/run` |
|
|
170
|
-
| auth | `X-API-Key` | `Authorization: Bearer` |
|
|
171
|
-
| cost | $0, direct | $0, on the Monid wallet |
|
|
161
|
+
<details>
|
|
162
|
+
<summary><strong>🔍 Behaviour worth knowing</strong></summary>
|
|
172
163
|
|
|
173
|
-
|
|
164
|
+
<br />
|
|
174
165
|
|
|
175
166
|
- **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.
|
|
176
|
-
- **`publishedAt` is honest.** TinyFish reports
|
|
177
|
-
- **
|
|
178
|
-
- **A blocked run is terminal.** If a Monid workspace control stops a run, the error says why and links to top up.
|
|
167
|
+
- **`publishedAt` is honest.** TinyFish reports human dates (`"Apr 30, 2026"`, `"1 year ago"`). What parses becomes ISO-8601; what doesn't is dropped, never invented. Unzoned dates read as UTC, so the same page reports the same day everywhere.
|
|
168
|
+
- **Empty searches retry.** The upstream answers a valid query with nothing about one run in three — a blank result is retried up to `attempts` before it is believed.
|
|
169
|
+
- **A blocked run is terminal.** If a Monid workspace control stops a run, the error says why and links to top up. Never retried.
|
|
179
170
|
|
|
180
|
-
|
|
171
|
+
TinyFish's `agent` and `browser` surfaces are **not** exposed: metered, wallet-billed, and not a search or a fetch. Use the `tinyfish` CLI directly when a page genuinely needs a real browser.
|
|
181
172
|
|
|
182
|
-
|
|
173
|
+
</details>
|
|
183
174
|
|
|
184
|
-
|
|
175
|
+
<details>
|
|
176
|
+
<summary><strong>🛠 Development</strong></summary>
|
|
185
177
|
|
|
186
|
-
|
|
178
|
+
<br />
|
|
179
|
+
|
|
180
|
+
The toolchain is [Vite+](https://viteplus.dev): `vp pack` builds with tsdown, `vp test` runs Vitest, `vp lint` / `vp fmt` are Oxlint and Oxfmt, type-aware. Lint and format live in `vite.config.ts` — Vite+ ignores standalone configs.
|
|
187
181
|
|
|
188
182
|
```sh
|
|
189
183
|
pnpm install
|
|
190
|
-
pnpm test #
|
|
184
|
+
pnpm test # 143 hermetic tests — no network, no credential
|
|
191
185
|
pnpm run check # format + lint + types
|
|
192
|
-
pnpm run release:gate # build, then the full gate
|
|
186
|
+
pnpm run release:gate # build, then the full gate incl. 11 package checks
|
|
193
187
|
pnpm run test:live # the real APIs, still $0, needs credentials
|
|
194
188
|
```
|
|
195
189
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
Full process and invariants: [`AGENTS.md`](./AGENTS.md).
|
|
199
|
-
|
|
200
|
-
## Compatibility
|
|
190
|
+
Requires **DSH 0.2.0+** and **Node 22.14+**. Full process and invariants: [`AGENTS.md`](./AGENTS.md). Contributing: [`CONTRIBUTING.md`](./CONTRIBUTING.md).
|
|
201
191
|
|
|
202
|
-
|
|
192
|
+
</details>
|
|
203
193
|
|
|
204
194
|
## License
|
|
205
195
|
|
|
@@ -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/lib/client.js
CHANGED
|
@@ -233,7 +233,11 @@ function TinyfishCard(props) {
|
|
|
233
233
|
}
|
|
234
234
|
});
|
|
235
235
|
const channel = (state.fields[FIELD.channel]?.text ?? "") === "monid" ? "monid" : "direct";
|
|
236
|
-
const key = state.keys[channel]
|
|
236
|
+
const key = state.keys[channel] ?? {
|
|
237
|
+
text: "",
|
|
238
|
+
named: false,
|
|
239
|
+
ref: channel === "monid" ? DEFAULT_MONID_KEY_REF : DEFAULT_API_KEY_REF
|
|
240
|
+
};
|
|
237
241
|
const keyChannel = channel === "monid" ? FIELD.monidApiKey : FIELD.apiKey;
|
|
238
242
|
const searchOn = switchValue(state.fields[FIELD.search]?.text ?? "");
|
|
239
243
|
const fetchOn = switchValue(state.fields[FIELD.fetch]?.text ?? "");
|
|
@@ -270,6 +274,24 @@ function TinyfishCard(props) {
|
|
|
270
274
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("channelHint") }),
|
|
271
275
|
resetButton(FIELD.channel, state.fields[FIELD.channel]?.overridden ?? false)
|
|
272
276
|
] }),
|
|
277
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
|
|
278
|
+
t("apiKey"),
|
|
279
|
+
":",
|
|
280
|
+
" ",
|
|
281
|
+
state.keys.direct?.named ?? false ? t("apiKeySet") : t("apiKeyUnset"),
|
|
282
|
+
" ",
|
|
283
|
+
"(",
|
|
284
|
+
state.keys.direct?.ref ?? DEFAULT_API_KEY_REF,
|
|
285
|
+
") · ",
|
|
286
|
+
t("monidApiKey"),
|
|
287
|
+
":",
|
|
288
|
+
" ",
|
|
289
|
+
state.keys.monid?.named ?? false ? t("apiKeySet") : t("apiKeyUnset"),
|
|
290
|
+
" ",
|
|
291
|
+
"(",
|
|
292
|
+
state.keys.monid?.ref ?? DEFAULT_MONID_KEY_REF,
|
|
293
|
+
")"
|
|
294
|
+
] }),
|
|
273
295
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SettingsSecretField, {
|
|
274
296
|
id: `plugin-config-tinyfish-${keyChannel}`,
|
|
275
297
|
label: channel === "monid" ? t("monidApiKey") : t("apiKey"),
|
|
@@ -363,11 +385,13 @@ function apply(ctx) {
|
|
|
363
385
|
keys: {
|
|
364
386
|
direct: {
|
|
365
387
|
text: model.field(FIELD.apiKey).text,
|
|
366
|
-
named: refOf(scope.getSnapshot(), "direct") !== DEFAULT_API_KEY_REF
|
|
388
|
+
named: refOf(scope.getSnapshot(), "direct") !== DEFAULT_API_KEY_REF,
|
|
389
|
+
ref: refOf(scope.getSnapshot(), "direct")
|
|
367
390
|
},
|
|
368
391
|
monid: {
|
|
369
392
|
text: model.field(FIELD.monidApiKey).text,
|
|
370
|
-
named: refOf(scope.getSnapshot(), "monid") !== DEFAULT_MONID_KEY_REF
|
|
393
|
+
named: refOf(scope.getSnapshot(), "monid") !== DEFAULT_MONID_KEY_REF,
|
|
394
|
+
ref: refOf(scope.getSnapshot(), "monid")
|
|
371
395
|
}
|
|
372
396
|
}
|
|
373
397
|
}));
|
package/lib/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
2
2
|
import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
|
|
3
3
|
import z from "@deepseek-ai/schemastery";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
4
5
|
import { readFileSync } from "node:fs";
|
|
5
6
|
import { homedir } from "node:os";
|
|
6
7
|
import { join } from "node:path";
|
|
@@ -235,7 +236,10 @@ async function call(url, options) {
|
|
|
235
236
|
if (isAbortError(error)) throw aborted(signal, error);
|
|
236
237
|
throw new WebError(`TinyFish request to ${url} failed: ${String(error)}`, WEB_PROVIDER_ERROR, { cause: error });
|
|
237
238
|
}
|
|
238
|
-
if (response.status === 401 || response.status === 403)
|
|
239
|
+
if (response.status === 401 || response.status === 403) {
|
|
240
|
+
const fingerprint = createHash("sha256").update(key, "utf8").digest("hex").slice(0, 12);
|
|
241
|
+
throw new WebError(`TinyFish rejected the ${channel} API key (HTTP ${response.status}, key sha256:${fingerprint}). Refresh it, or switch the provider's channel.`, WEB_PROVIDER_ERROR);
|
|
242
|
+
}
|
|
239
243
|
if (response.status === 429) throw new TransientWebError("TinyFish rate limit reached (HTTP 429).", WEB_PROVIDER_ERROR);
|
|
240
244
|
if (!response.ok) throw new WebError(`TinyFish returned HTTP ${response.status} for ${url}`, WEB_PROVIDER_ERROR);
|
|
241
245
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-tinyfish",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "TinyFish-backed search and fetch providers for the DeepSeek Harness web capability seam (ctx.web) — $0 SERP and page extraction, direct or via Monid.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cordis",
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"lib/client.js",
|
|
27
27
|
"cordis.patch.yml",
|
|
28
28
|
"README.md",
|
|
29
|
-
"LICENSE"
|
|
29
|
+
"LICENSE",
|
|
30
|
+
"assets/tinyfish.svg"
|
|
30
31
|
],
|
|
31
32
|
"type": "module",
|
|
32
33
|
"main": "lib/index.mjs",
|
|
@@ -44,9 +45,9 @@
|
|
|
44
45
|
}
|
|
45
46
|
},
|
|
46
47
|
"scripts": {
|
|
47
|
-
"build": "vp pack && node scripts/name-client-bundle.
|
|
48
|
+
"build": "vp pack && node scripts/name-client-bundle.ts",
|
|
48
49
|
"check": "vp check",
|
|
49
|
-
"ci": "vp check && vp test && node scripts/check.
|
|
50
|
+
"ci": "vp check && vp test && node scripts/check.ts",
|
|
50
51
|
"clean": "rm -rf lib",
|
|
51
52
|
"format": "vp fmt --check",
|
|
52
53
|
"format:fix": "vp fmt --write",
|
|
@@ -74,7 +75,8 @@
|
|
|
74
75
|
"react": "^18.3.1",
|
|
75
76
|
"typescript": "^7.0.2",
|
|
76
77
|
"ultracite": "^7.12.1",
|
|
77
|
-
"vite-plus": "catalog:"
|
|
78
|
+
"vite-plus": "catalog:",
|
|
79
|
+
"vitest": "5.0.1"
|
|
78
80
|
},
|
|
79
81
|
"peerDependencies": {
|
|
80
82
|
"@deepseek-ai/cordis": "~4.0.4",
|