dsh-web-tavily 0.4.1 → 0.4.2

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/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  All notable changes to this project are documented in this file.
4
4
 
5
+ ## 0.4.2 (2026-09-16)
6
+
7
+ ### Fixed
8
+
9
+ - **`ipaddr.js` is a declared dependency now.** `lib/index.js` imported it while `package.json` declared no `dependencies` at all, so the import resolved only because the dsh installation closure happens to hoist it (it is a dependency of `@deepseek-ai/dsh-web-fetch-http`). It is a module-scope import, so a closure without it would have failed the plugin tree. Declared as `^2.5.0`.
10
+ - **`USER_AGENT` can no longer drift from the package version.** It was the literal `dsh-web-tavily/0.4.0` while the package shipped 0.4.1. It is now read from `package.json` at load time (through `createRequire(import.meta.url)`), with a graceful fallback to the bare plugin name when the manifest is unreadable — loading still never fails because of it.
11
+ - **`diag()` creates the log directory.** `~/.dsh-tui/dsh-web-tavily.log` was appended to without creating `~/.dsh-tui/`, so on a fresh machine every line was swallowed by the outer catch. The write path now creates the parent directory first; the "never throws" contract is unchanged.
12
+ - **Peer ranges widened to `^0.1.2-rc.1 || ^0.1.5-rc.1`.** `^0.1.2-rc.1` cannot match `0.1.5-rc.1`: semver lets a prerelease satisfy a range only when a comparator shares its `[major, minor, patch]` tuple, so every `0.1.x-rc.n` after `0.1.2` fell outside it. Today that is a warning rather than a failure (the profile template pins `autoInstallPeers: false`), but it would become an install error under strict peer enforcement. A future engine prerelease line needs its own clause.
13
+
14
+ ### Documentation
15
+
16
+ - **The install steps now warn that saving `cordis.patch.yml` hot-reloads the running session.** A custom profile defaults to `patchReload: live`, so the edit takes effect immediately — not at the restart the README previously implied. If the `dsh-web-tavily` row has not been loaded yet, the `web` row points at a provider that is not registered and the session's web capability fails with `WEB_PROVIDER_CONFIGURED_MISSING` until the next restart. The safe order (install → `/restart` → edit the patch) is now spelled out.
17
+ - **Getting a Tavily API key is step 1**, with where the key goes (`~/.dsh/.credentials.yaml` under `refs:`, or the launching environment's `TAVILY_API_KEY`) and an explicit note not to commit it.
18
+ - The documented key-resolution order is corrected to match the code: literal `config.apiKey` first, then the credentials service, then the launching environment. The module header comment carried the same wrong order and is fixed too.
19
+
5
20
  ## 0.4.1 (2026-09-13)
6
21
 
7
22
  - Documentation only, no code change. Installation now goes through `dsh plugin --profile dsh-tui add dsh-web-tavily` (the package is published) instead of copying files into `node_modules`, both READMEs gain a CI badge, and the release section records the OIDC trusted-publishing path.
package/README.md CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  **English** · [中文](README.zh.md)
6
6
 
7
+ Built for [dsh-TUI](https://github.com/ccch1mneyyy/dsh-TUI).
8
+
7
9
  A host-plane Cordis plugin for dsh: it registers the **`tavily`** search provider
8
10
  on dsh's web seam (`ctx.web`), so the `web_search` tool goes to the **Tavily
9
11
  Search API** (`POST https://api.tavily.com/search`).
@@ -13,10 +15,10 @@ Search API** (`POST https://api.tavily.com/search`).
13
15
  - DeepSeek's native search (`web-search-deepseek`) stays available as a fallback
14
16
  you can switch to; set `web.searchProvider` back to `deepseek-official` (or
15
17
  delete the key) to do so.
16
- - The key is always resolved as a **reference** (default `TAVILY_API_KEY`): the
17
- credentials service (`refs:` in `~/.dsh/.credentials.yaml`) first, then the
18
- process environment, and only then a literal `apiKey` in the configuration. The
19
- provider never stores a key.
18
+ - The key is resolved per operation in this order: a literal `apiKey` in the
19
+ configuration first, then the credentials service (`refs:` in
20
+ `~/.dsh/.credentials.yaml`), then the launch environment. The reference
21
+ defaults to `TAVILY_API_KEY`, and the provider never stores a key.
20
22
 
21
23
  ## How it works
22
24
 
@@ -46,7 +48,14 @@ Search API** (`POST https://api.tavily.com/search`).
46
48
 
47
49
  ## Install
48
50
 
49
- 1. Install it into your profile it is published on npm as `dsh-web-tavily`:
51
+ 1. **Get a Tavily account and an API key.** Register at <https://tavily.com/>,
52
+ create a key in the dashboard, and store it under `refs:` in
53
+ `~/.dsh/.credentials.yaml` — or export `TAVILY_API_KEY` in the environment
54
+ that launches dsh (see
55
+ [Where the key lives](#where-the-key-lives-local-only--never-in-a-repository)).
56
+ Never commit the key to a repository or paste it into a settings document.
57
+
58
+ 2. Install it into your profile — it is published on npm as `dsh-web-tavily`:
50
59
 
51
60
  ```sh
52
61
  dsh plugin --profile dsh-tui add dsh-web-tavily
@@ -54,7 +63,12 @@ Search API** (`POST https://api.tavily.com/search`).
54
63
  # dsh plugin --profile dsh-tui add file:<path to this package>
55
64
  ```
56
65
 
57
- 2. **Make tavily the default search backend.** This has to be written in the
66
+ 3. **Restart dsh-tui before editing the patch** (`/restart`). Adding a plugin
67
+ only rewrites the profile's bundle list; it does **not** load the plugin into
68
+ the running session. Restarting here means the `tavily` provider is already
69
+ registered when the patch below is saved.
70
+
71
+ 4. **Make tavily the default search backend.** This has to be written in the
58
72
  profile's user patch layer (the `web:` section of `settings.yaml` does not
59
73
  affect service configuration) — edit
60
74
  `~/.dsh/profiles/dsh-tui/cordis.patch.yml` and add:
@@ -67,12 +81,21 @@ Search API** (`POST https://api.tavily.com/search`).
67
81
  fetchProvider: http-trusted
68
82
  ```
69
83
 
84
+ ⚠️ **Saving this file hot-reloads the running session — it is not
85
+ "restart to apply".** The profile's patch layer is watched, so the session is
86
+ reconfigured the instant the file is written. If the plugin is not loaded yet
87
+ (step 3 skipped), the `web` row immediately points at a provider that is not
88
+ registered, and `web_search` / web fetch fail in that session with
89
+ `WEB_PROVIDER_CONFIGURED_MISSING`. **A `/restart` recovers it**, because at
90
+ boot the plugin is registered and the patched row is read together. Editing
91
+ the patch first and restarting later is also safe for the boot — it just
92
+ leaves this session's web capability unavailable until that restart.
93
+
70
94
  The `web` row belongs to the `dsh-base` layer, so **do not add a new row with
71
95
  `insert:`** — the duplicate id crashes the boot with
72
96
  `duplicate loader entry id: web`. Override the existing row by id instead, and
73
97
  note that an override **replaces the whole config**, which is why
74
98
  `fetchProvider` is restated above.
75
- 3. Restart dsh-tui (`/restart`).
76
99
 
77
100
  ## Configuration
78
101
 
@@ -106,6 +129,11 @@ refs:
106
129
  TAVILY_API_KEY: 'tvly-...'
107
130
  ```
108
131
 
132
+ Exporting `TAVILY_API_KEY` in the environment that launches dsh works too — the
133
+ credentials store is consulted first, the launch environment second. A literal
134
+ key in `dsh-web-tavily.apiKey` is accepted as well and wins over both, but it
135
+ puts the secret in a settings document, so treat it as a last resort.
136
+
109
137
  ## Switching back to DeepSeek's native search
110
138
 
111
139
  Set `web.searchProvider` to `deepseek-official` (or delete the key and let the
@@ -151,8 +179,10 @@ exchange for the complete safety preflight.
151
179
  - `duplicate loader entry id: web` on boot — the profile's `cordis.patch.yml`
152
180
  used `insert:` for a row that already exists; rewrite it as the by-id override
153
181
  shown above.
154
- - `web_search` answers `WEB_PROVIDER_CONFIGURED_MISSING` — the plugin did not
155
- load: check the bundles list and the patch above.
182
+ - `web_search` answers `WEB_PROVIDER_CONFIGURED_MISSING` — the `web` row points
183
+ at a provider that is not registered: either the plugin did not load (check the
184
+ bundles list), or `cordis.patch.yml` was saved before the plugin was loaded,
185
+ which is a live reload (see [Install](#install)). `/restart` recovers it.
156
186
  - `web_fetch` answers `WEB_BLOCKED_URL ... non-public IP` — the proxy fake-IP
157
187
  preflight refused it: switch to `http-trusted`, or turn the proxy's fake-IP
158
188
  mode off.
package/README.zh.md CHANGED
@@ -4,11 +4,15 @@
4
4
 
5
5
  **中文** · [English](README.md)
6
6
 
7
+ 为 [dsh-TUI](https://github.com/ccch1mneyyy/dsh-TUI) 构建。
8
+
7
9
  dsh host 平面 cordis 插件:为 dsh 的 web 能力 seam(`ctx.web`)注册搜索提供商 **`tavily`**,让 `web_search` 工具默认走 **Tavily Search API**(`POST https://api.tavily.com/search`)。
8
10
 
9
11
  - 不替换 `web_search` 工具本身:工具、系统提示、展示卡片均保持原样,只有搜索后端换成 Tavily。
10
12
  - 保留 DeepSeek 原生搜索(`web-search-deepseek`)为可切换备选;想切回只需把 `web.searchProvider` 改为 `deepseek-official` 或删掉该键。
11
- - key 一律按**引用**解析(默认 `TAVILY_API_KEY`):优先凭证服务(`~/.dsh/.credentials.yaml` 的 `refs:`),其次启动环境变量,最后才是配置里的字面 `apiKey`。provider 不保留 key。
13
+ - key 按以下顺序解析:配置里的字面 `apiKey` 最优先,其次凭证服务
14
+ (`~/.dsh/.credentials.yaml` 的 `refs:`,引用名默认 `TAVILY_API_KEY`),最后是启动
15
+ 环境变量。provider 不保留 key。
12
16
 
13
17
  ## 工作原理
14
18
 
@@ -32,7 +36,12 @@ dsh host 平面 cordis 插件:为 dsh 的 web 能力 seam(`ctx.web`)注册
32
36
 
33
37
  ## 安装
34
38
 
35
- 1. 装进 profile —— 它以 `dsh-web-tavily` 发布在 npm 上:
39
+ 1. **先注册 Tavily 账号并创建 API key。** <https://tavily.com/> 注册,在控制台创建
40
+ key,然后放进 `~/.dsh/.credentials.yaml` 的 `refs:` 下,或在启动 dsh 的环境里导出
41
+ `TAVILY_API_KEY`(详见下文「key 存放」)。**不要把 key 提交进仓库,也不要写进设置
42
+ 文档。**
43
+
44
+ 2. 装进 profile —— 它以 `dsh-web-tavily` 发布在 npm 上:
36
45
 
37
46
  ```sh
38
47
  dsh plugin --profile dsh-tui add dsh-web-tavily
@@ -40,7 +49,11 @@ dsh host 平面 cordis 插件:为 dsh 的 web 能力 seam(`ctx.web`)注册
40
49
  # dsh plugin --profile dsh-tui add file:<本包路径>
41
50
  ```
42
51
 
43
- 2. **把 web 服务默认搜索改为 tavily**(必须写在 profile 用户补丁层,`settings.yaml` `web:` 段对服务配置无效)——
52
+ 3. **先 `/restart` 重启一次**,让插件行载入。`dsh plugin add` 只改写 profile 的 bundles
53
+ 清单,**不会**把插件加载进运行中的会话;先重启,下一步保存补丁时 `tavily` provider
54
+ 才已经注册好。
55
+
56
+ 4. **把 web 服务默认搜索改为 tavily**(必须写在 profile 用户补丁层,`settings.yaml` 的 `web:` 段对服务配置无效)——
44
57
  编辑 `~/.dsh/profiles/dsh-tui/cordis.patch.yml` 追加:
45
58
 
46
59
  ```yaml
@@ -51,10 +64,15 @@ dsh host 平面 cordis 插件:为 dsh 的 web 能力 seam(`ctx.web`)注册
51
64
  fetchProvider: http-trusted
52
65
  ```
53
66
 
67
+ ⚠️ **保存该文件会立即热重载运行中的会话,不是「重启后才生效」。** profile 补丁层被
68
+ 文件监听器盯着,落盘那一刻当前会话就会被重新配置。若插件此时尚未加载(跳过了第 3
69
+ 步),`web` 行立刻指向一个还没注册的 provider,当前会话的 `web_search`/联网能力当场报
70
+ `WEB_PROVIDER_CONFIGURED_MISSING`。**重启即恢复**:启动时插件与补丁一起读入。先改
71
+ 补丁、稍后再重启对启动同样安全,只是当前会话的联网能力要等到那次重启才可用。
72
+
54
73
  `web` 行由基座 dsh-base 提供,**不要用 `insert:` 新增同 id 行**——那会撞 id 直接崩
55
74
  `duplicate loader entry id: web`;这里要按 id 覆盖既有行,且覆盖是**整行重写,需写全
56
75
  `fetchProvider`**。
57
- 3. 重启 dsh-tui(`/restart`)生效。
58
76
 
59
77
  ## 配置
60
78
 
@@ -82,6 +100,10 @@ refs:
82
100
  TAVILY_API_KEY: 'tvly-...'
83
101
  ```
84
102
 
103
+ 也可以在启动 dsh 的环境里导出 `TAVILY_API_KEY`:解析时先查凭证服务、再查启动环境。
104
+ 配置里的字面 `dsh-web-tavily.apiKey` 同样可用,且优先于以上两者,但它等于把密钥写进
105
+ 设置文档,只建议作为最后手段。
106
+
85
107
  ## 切回 DeepSeek 原生搜索
86
108
 
87
109
  把 `web.searchProvider` 改为 `deepseek-official`(或删除该键、让 seam 用唯一可用 provider)。注意 seam 不支持自动回退:Tavily 配置失效时搜索会明确报错,而**不会**悄悄走 DeepSeek。
@@ -106,7 +128,9 @@ config:
106
128
  ## 加载失败排查
107
129
 
108
130
  - 插件启动报 `duplicate loader entry id: web`:profile `cordis.patch.yml` 误用 `insert:` 新增同 id 行,按上文改写为直接行。
109
- - `web_search` 报 `WEB_PROVIDER_CONFIGURED_MISSING`:插件未加载(检查 bundles 列表与上述补丁)。
131
+ - `web_search` 报 `WEB_PROVIDER_CONFIGURED_MISSING`:`web` 行指向的 provider 尚未注册
132
+ ——要么插件没加载(检查 bundles 清单),要么 `cordis.patch.yml` 在插件加载前就被保存
133
+ (保存即热重载,见「安装」);`/restart` 即可恢复。
110
134
  - `web_fetch` 报 `WEB_BLOCKED_URL ... non-public IP`:代理假 IP 预检拒绝,改用 `http-trusted` 或关闭代理 fake-IP 模式。
111
135
 
112
136
  ## 限制说明
package/dsh-plugin.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "manifestVersion": "0.15",
4
4
  "id": "com.dsh-tui.dsh-web-tavily",
5
5
  "name": "dsh-web-tavily",
6
- "version": "0.4.1",
6
+ "version": "0.4.2",
7
7
  "facets": {
8
8
  "host": {
9
9
  "entry": "lib/index.js",
package/lib/diag.js CHANGED
@@ -11,9 +11,9 @@
11
11
  * `~/.dsh-tui/` capped at 128 KiB, and no file writes under `node --test`.
12
12
  */
13
13
 
14
- import { appendFileSync, readFileSync, statSync, writeFileSync } from 'node:fs';
14
+ import { appendFileSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
15
15
  import { homedir } from 'node:os';
16
- import { join } from 'node:path';
16
+ import { dirname, join } from 'node:path';
17
17
 
18
18
  /** Plugin-owned log path. */
19
19
  export const DIAG_LOG = join(homedir(), '.dsh-tui', 'dsh-web-tavily.log');
@@ -31,6 +31,10 @@ function appendLogLine(path, line) {
31
31
  catch {
32
32
  // Missing or unreadable: the append below recreates it.
33
33
  }
34
+ // A fresh machine has no ~/.dsh-tui/ yet; create it first, or the append
35
+ // throws and the caller's catch swallows the line — losing exactly the
36
+ // evidence this log exists to keep.
37
+ mkdirSync(dirname(path), { recursive: true });
34
38
  appendFileSync(path, line);
35
39
  }
36
40
 
package/lib/index.js CHANGED
@@ -3,9 +3,9 @@
3
3
  * capability seam (ctx.web). Registers the `tavily` provider so the
4
4
  * model-facing web_search tool runs against the Tavily Search API
5
5
  * (POST https://api.tavily.com/search). The provider holds no credential
6
- * value: the key is resolved per operation through the credentials service
7
- * (ref, default TAVILY_API_KEY), the launch environment, or a literal
8
- * `apiKey` config, in that order.
6
+ * value: the key is resolved per operation from a literal `apiKey` config
7
+ * first, then the credentials service (ref, default TAVILY_API_KEY), then the
8
+ * launch environment, in that order.
9
9
  *
10
10
  * The bundle additionally registers `http-trusted`, a proxy-friendly HTTP(S)
11
11
  * fetch provider: it reuses the official @deepseek-ai/dsh-web-fetch-http
@@ -26,6 +26,7 @@ import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
26
26
  import { WebError } from "@deepseek-ai/dsh-web";
27
27
  import { HttpFetchProvider } from "@deepseek-ai/dsh-web-fetch-http";
28
28
  import { lookup } from "node:dns/promises";
29
+ import { createRequire } from "node:module";
29
30
  import { isIP } from "node:net";
30
31
  import { diag } from "./diag.js";
31
32
  import { applySettingsCard } from "./settings-card.js";
@@ -55,8 +56,24 @@ const DEFAULT_API_KEY_ENV = "TAVILY_API_KEY";
55
56
  const BASE_URL_ENV = "TAVILY_BASE_URL";
56
57
  /** Settings namespace carrying this provider's endpoint and behavior options. */
57
58
  const SETTINGS_NAMESPACE = "dsh-web-tavily";
58
- /** Attribution header sent on every request. Bump with the package version. */
59
- const USER_AGENT = "dsh-web-tavily/0.4.0";
59
+ /**
60
+ * Build the attribution header from the installed manifest (`../package.json`,
61
+ * beside `lib/`). The version is read at load time so a release can never leave
62
+ * it stale; a missing/unreadable manifest or version degrades to the bare
63
+ * plugin name — attribution is never a reason a module fails to load.
64
+ */
65
+ function userAgent() {
66
+ try {
67
+ const version = createRequire(import.meta.url)("../package.json").version;
68
+ if (typeof version === "string" && version.length > 0) return `${name}/${version}`;
69
+ }
70
+ catch {
71
+ // Fall through to the name-only header.
72
+ }
73
+ return name;
74
+ }
75
+ /** Attribution header sent on every request. */
76
+ const USER_AGENT = userAgent();
60
77
  /** Fallback result cap when the caller sends no maxResults. */
61
78
  const DEFAULT_MAX_RESULTS = 10;
62
79
  /** Tavily's documented per-request result cap. */
package/package.json CHANGED
@@ -1,8 +1,18 @@
1
1
  {
2
2
  "name": "dsh-web-tavily",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "Tavily-backed search provider for dsh: registers the `tavily` provider on ctx.web so the web_search tool runs against the Tavily Search API",
5
5
  "license": "MIT",
6
+ "keywords": [
7
+ "dsh",
8
+ "dsh-plugin",
9
+ "dsh-tui",
10
+ "deepseek-harness",
11
+ "plugin",
12
+ "search",
13
+ "tavily",
14
+ "web-search"
15
+ ],
6
16
  "repository": {
7
17
  "type": "git",
8
18
  "url": "git+https://github.com/VviLliAm-qwq/dsh-web-tavily.git"
@@ -29,10 +39,13 @@
29
39
  "peerDependencies": {
30
40
  "@deepseek-ai/cordis": "^4.0.2",
31
41
  "@deepseek-ai/schemastery": "^3.18.2",
32
- "@deepseek-ai/dsh-web": "^0.1.2-rc.1",
33
- "@deepseek-ai/dsh-web-fetch-http": "^0.1.2-rc.1",
34
- "@deepseek-ai/dsh-credentials": "^0.1.2-rc.1",
35
- "@deepseek-ai/dsh-launch-environment": "^0.1.2-rc.1",
36
- "@deepseek-ai/dsh-settings": "^0.1.2-rc.1"
42
+ "@deepseek-ai/dsh-web": "^0.1.2-rc.1 || ^0.1.5-rc.1",
43
+ "@deepseek-ai/dsh-web-fetch-http": "^0.1.2-rc.1 || ^0.1.5-rc.1",
44
+ "@deepseek-ai/dsh-credentials": "^0.1.2-rc.1 || ^0.1.5-rc.1",
45
+ "@deepseek-ai/dsh-launch-environment": "^0.1.2-rc.1 || ^0.1.5-rc.1",
46
+ "@deepseek-ai/dsh-settings": "^0.1.2-rc.1 || ^0.1.5-rc.1"
47
+ },
48
+ "dependencies": {
49
+ "ipaddr.js": "^2.5.0"
37
50
  }
38
51
  }