dsh-web-search-plugin 0.3.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/CHANGELOG.md +25 -1
- package/README.md +4 -4
- package/lib/client.js +34 -24
- package/lib/index.js +17 -19
- package/lib/rest.js +6 -2
- package/lib/shared.js +1 -1
- package/package.json +7 -9
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
本项目的重要变更都记录在此。格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循 [Semantic Versioning](https://semver.org/lang/zh-CN/)。
|
|
4
4
|
|
|
5
|
+
## [0.4.0] - 2026-09-02
|
|
6
|
+
|
|
7
|
+
### 变更
|
|
8
|
+
|
|
9
|
+
- **适配 DeepSeek Harness 最新 main(`0.1.2-alpha.4`)**:`@deepseek-ai/dsh-settings` 已移除顶层 `installSettingsSection` / `settingsNamespace`,Host 侧改为在 `ctx.inject(['settings'])` 后调用 `settingsCtx.settings.installSection(ctx, ...)`;settings namespace 直接使用普通字符串。
|
|
10
|
+
- **浏览器端移除对 `@deepseek-ai/dsh-client-runtime` / 旧 `connection` 的依赖**:该包已不在最新 Harness workspace / 客户端模块表中。`createSnapshotStore` 改从平台常驻模块 `@deepseek-ai/dsh-client-store` 导入;`dsh.client.inject` 与客户端 `inject` 对齐为 `slots` / `locale` / `remote` / `settingsScope`。
|
|
11
|
+
- **凭据远端接口迁移到 `ctx.remote`**:`credentials.describe(refs[])` / `set(ref, value)` 使用最新 Remote 参数与响应形状(`{ ok, value }`);凭据变更事件从 `credentials/updated` 改为 `credentials/reference-updated`;不再经旧 `connection.api` 调用。
|
|
12
|
+
- peerDependencies 中 dsh 包版本从 `0.1.0-rc.7` 提升到 `0.1.2-alpha.4`;`@deepseek-ai/cordis` / `@deepseek-ai/schemastery` 分别对齐 `^4.0.2` / `^3.18.2`;版本号升至 `0.4.0`,`User-Agent` 同步。
|
|
13
|
+
|
|
14
|
+
## [0.3.1] - 2026-08-30
|
|
15
|
+
|
|
16
|
+
### 修复
|
|
17
|
+
|
|
18
|
+
- **Tavily keyless 模式失效**:通用 REST 后端不再在 keyless 模式下强制解析 API key;无 key 时只发送 `x-tavily-access-mode: keyless`,可正常使用免费额度。
|
|
19
|
+
- **POST 类 provider 的 count 参数类型错误**:Tavily `max_results`、Serper `num`、Exa `num_results`、Firecrawl `limit` 等在 JSON body 中恢复为数字,不再被强转字符串。
|
|
20
|
+
- **Scavio / Firecrawl 设置卡严重卡死**:补齐浏览器端缺失的 `scavioApiKeyEnv` / `scavioBaseURL` / `firecrawlApiKeyEnv` / `firecrawlBaseURL` 表单字段与投影,修复切换后输入框无法输入、引擎无法切换的问题。
|
|
21
|
+
- **Scavio / Firecrawl 默认凭据名错误**:未显式填写 keyEnv 时分别回退到 `SCAVIO_API_KEY` / `FIRECRAWL_API_KEY`,不再错误使用 `EXA_API_KEY`。
|
|
22
|
+
- **Brave 环境代理回退丢失**:恢复 `HTTPS_PROXY` / `HTTP_PROXY` 环境变量回退,未显式配置 `proxy` 时也能按环境代理请求。
|
|
23
|
+
- `User-Agent` 更新为 `0.3.1`。
|
|
24
|
+
|
|
25
|
+
### 优化
|
|
26
|
+
|
|
27
|
+
- 内置 REST 渠道(Serper / SerpApi / Exa / SearXNG / Scavio / Firecrawl)的 Base URL 默认值预填到设置卡,切换渠道后不再是空 URL。
|
|
28
|
+
|
|
5
29
|
## [0.3.0] - 2026-08-25
|
|
6
30
|
|
|
7
31
|
### 新增
|
|
@@ -84,7 +108,7 @@
|
|
|
84
108
|
|
|
85
109
|
### 修复
|
|
86
110
|
|
|
87
|
-
- **客户端 bundle 缺少 `exports.inject`**(`lib/client.js`)。浏览器插件原先只导出 `apply`,cordis 的 `fiber.inject` 为空,访问 `ctx.locale` 等服务会抛 `cannot get property "locale" without inject`。现在导出 `inject = ["slots", "locale", "
|
|
111
|
+
- **客户端 bundle 缺少 `exports.inject`**(`lib/client.js`)。浏览器插件原先只导出 `apply`,cordis 的 `fiber.inject` 为空,访问 `ctx.locale` 等服务会抛 `cannot get property "locale" without inject`。现在导出 `inject = ["slots", "locale", "remote", "settingsScope"]`。
|
|
88
112
|
- **插件卡片的 slot 注册**(`lib/client.js`)。卡片按 rc.6 的 `id`/`order` 形状注册进 `settings.plugin.item`;该 slot 从 rc.6+ 起是 keyed 的,必须带 `key: "dsh-web-search-plugin"`。
|
|
89
113
|
- **`CardForm.field("apiKey")` 崩溃**(`lib/client.js`)。只写凭据字段没有 section spec,对 `undefined` 调用 `spec.format(...)` 会在卡片投影时抛 `Cannot read properties of undefined (reading 'format')`。已加上 secret 字段分支(对齐官方 `CardForm`)。
|
|
90
114
|
- **`dsh.client.inject` 补上 `@deepseek-ai/dsh-client-ui-slots`**(`package.json`),与社区插件的加载集合一致。
|
package/README.md
CHANGED
|
@@ -17,19 +17,19 @@
|
|
|
17
17
|
|
|
18
18
|
## 特性
|
|
19
19
|
|
|
20
|
-
- 与官方 `@deepseek-ai/dsh-web-search-deepseek` 相同的提供方约定:`inject: ['web']` + `
|
|
20
|
+
- 与官方 `@deepseek-ai/dsh-web-search-deepseek` 相同的提供方约定:`inject: ['web']` + `ctx.settings.installSection` + `ctx.web.registerSearchProvider`。
|
|
21
21
|
- 顶层 **设置 → 网页搜索** 分区:两列布局、未保存草稿、保存 toast;结果数量为 1–20 下拉。
|
|
22
22
|
- **默认引擎为 DeepSeek(官方)**:新装 / 未显式改动时,搜索直接走 `deepseek-official`,不再依赖 keyless 的 Tavily。
|
|
23
23
|
- 纯 REST 后端由**静态元数据表 + 通用后端**驱动,新增 provider 即"表里加一行 + 官方跳转链接",无定制执行代码。
|
|
24
24
|
- Tavily keyless 无需密钥即可用;Brave 需要订阅 token(若本机已有 `BRAVE_API_KEY` 凭据,可直接复用)。
|
|
25
25
|
- Tavily keyed / Brave 在设置卡展示额度进度条(DeepSeek 官方与 Tavily keyless / 其余 REST 后端不展示)。
|
|
26
26
|
- 各引擎结果都规范化为接缝的 `WebSearchResult`(可选 `content` + `sources[]`),按 URL 去重。
|
|
27
|
-
- 设置段 `dsh-web-search-plugin` 通过
|
|
27
|
+
- 设置段 `dsh-web-search-plugin` 通过 dsh 的 `settings.installSection` 注册、由 `settings.describe()` 动态暴露,不需要宿主白名单补丁,也不需要自建回环 settings 桥。
|
|
28
28
|
- 错误映射为 `WEB_PROVIDER_ERROR` / `WEB_ABORTED` / `WEB_PROVIDER_CREDENTIAL_MISSING`。
|
|
29
29
|
|
|
30
30
|
## 运行要求
|
|
31
31
|
|
|
32
|
-
- DeepSeek Harness `0.1.
|
|
32
|
+
- DeepSeek Harness `0.1.2-alpha.4`(最新 main)或更新
|
|
33
33
|
- pnpm,用于通过 `dsh plugin` 把插件装进 profile
|
|
34
34
|
|
|
35
35
|
## 安装
|
|
@@ -189,7 +189,7 @@ dsh plugin --profile web remove @dsh-ltctfer/dsh-web-search-brave
|
|
|
189
189
|
npm run check
|
|
190
190
|
```
|
|
191
191
|
|
|
192
|
-
客户端 bundle 必须保持 `window.__ModuleLoader__.load({ id, factory })` 线格式 — 由 `dsh-client-modules` 加载,不是打包器。必须同时导出 `apply` 和 `inject`(`slots`、`locale`、`
|
|
192
|
+
客户端 bundle 必须保持 `window.__ModuleLoader__.load({ id, factory })` 线格式 — 由 `dsh-client-modules` 加载,不是打包器。必须同时导出 `apply` 和 `inject`(`slots`、`locale`、`remote`、`settingsScope`),并把分区注册进 `settings.section`(`id: "web-search"`)。
|
|
193
193
|
|
|
194
194
|
## 发布
|
|
195
195
|
|
package/lib/client.js
CHANGED
|
@@ -21,18 +21,18 @@ window.__ModuleLoader__.load({
|
|
|
21
21
|
var exports = module.exports;
|
|
22
22
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
23
23
|
let react = require("react");
|
|
24
|
-
let
|
|
24
|
+
let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
|
|
25
25
|
|
|
26
26
|
/** Settings namespace of this plugin. */
|
|
27
27
|
const NS = "dsh-web-search-plugin";
|
|
28
28
|
/** Built-in REST providers (mirrors `lib/providers.js`; browser bundle is self-contained). */
|
|
29
29
|
const BUILTIN_REST_PROVIDERS = [
|
|
30
|
-
{ id: "serper", name: "Serper", officialUrl: "https://serper.dev/", keyRequired: true, keyField: "serperApiKey", keyEnvField: "serperApiKeyEnv", baseURLField: "serperBaseURL" },
|
|
31
|
-
{ id: "serpapi", name: "SerpApi", officialUrl: "https://serpapi.com/", keyRequired: true, keyField: "serpapiApiKey", keyEnvField: "serpapiApiKeyEnv", baseURLField: "serpapiBaseURL" },
|
|
32
|
-
{ id: "exa", name: "Exa", officialUrl: "https://dashboard.exa.ai/", keyRequired: true, keyField: "exaApiKey", keyEnvField: "exaApiKeyEnv", baseURLField: "exaBaseURL" },
|
|
33
|
-
{ id: "searxng", name: "SearXNG", officialUrl: "https://docs.searxng.org/", keyRequired: false, keyField: "", keyEnvField: "", baseURLField: "searxngBaseURL" },
|
|
34
|
-
{ id: "scavio", name: "Scavio", officialUrl: "https://scavio.dev/", keyRequired: true, keyField: "scavioApiKey", keyEnvField: "scavioApiKeyEnv", baseURLField: "scavioBaseURL" },
|
|
35
|
-
{ id: "firecrawl", name: "Firecrawl", officialUrl: "https://firecrawl.dev/", keyRequired: true, keyField: "firecrawlApiKey", keyEnvField: "firecrawlApiKeyEnv", baseURLField: "firecrawlBaseURL" }
|
|
30
|
+
{ id: "serper", name: "Serper", officialUrl: "https://serper.dev/", defaultBaseURL: "https://google.serper.dev", keyRequired: true, keyField: "serperApiKey", keyEnvField: "serperApiKeyEnv", baseURLField: "serperBaseURL" },
|
|
31
|
+
{ id: "serpapi", name: "SerpApi", officialUrl: "https://serpapi.com/", defaultBaseURL: "https://serpapi.com", keyRequired: true, keyField: "serpapiApiKey", keyEnvField: "serpapiApiKeyEnv", baseURLField: "serpapiBaseURL" },
|
|
32
|
+
{ id: "exa", name: "Exa", officialUrl: "https://dashboard.exa.ai/", defaultBaseURL: "https://api.exa.ai", keyRequired: true, keyField: "exaApiKey", keyEnvField: "exaApiKeyEnv", baseURLField: "exaBaseURL" },
|
|
33
|
+
{ id: "searxng", name: "SearXNG", officialUrl: "https://docs.searxng.org/", defaultBaseURL: "https://searx.be", keyRequired: false, keyField: "", keyEnvField: "", baseURLField: "searxngBaseURL" },
|
|
34
|
+
{ id: "scavio", name: "Scavio", officialUrl: "https://scavio.dev/", defaultBaseURL: "https://api.scavio.dev", keyRequired: true, keyField: "scavioApiKey", keyEnvField: "scavioApiKeyEnv", baseURLField: "scavioBaseURL" },
|
|
35
|
+
{ id: "firecrawl", name: "Firecrawl", officialUrl: "https://firecrawl.dev/", defaultBaseURL: "https://api.firecrawl.dev", keyRequired: true, keyField: "firecrawlApiKey", keyEnvField: "firecrawlApiKeyEnv", baseURLField: "firecrawlBaseURL" }
|
|
36
36
|
];
|
|
37
37
|
/** Find a built-in REST provider by id. */
|
|
38
38
|
function builtinRest(id) {
|
|
@@ -334,9 +334,9 @@ window.__ModuleLoader__.load({
|
|
|
334
334
|
* spec and are written through the credentials domain.
|
|
335
335
|
*/
|
|
336
336
|
var SearchCardForm = class {
|
|
337
|
-
constructor(scope,
|
|
337
|
+
constructor(scope, remote) {
|
|
338
338
|
this.scope = scope;
|
|
339
|
-
this.
|
|
339
|
+
this.remote = remote;
|
|
340
340
|
this.specs = new Map([
|
|
341
341
|
choiceField("provider"),
|
|
342
342
|
choiceField("mode"),
|
|
@@ -364,7 +364,11 @@ window.__ModuleLoader__.load({
|
|
|
364
364
|
textField("serpapiBaseURL"),
|
|
365
365
|
textField("exaApiKeyEnv"),
|
|
366
366
|
textField("exaBaseURL"),
|
|
367
|
-
textField("searxngBaseURL")
|
|
367
|
+
textField("searxngBaseURL"),
|
|
368
|
+
textField("scavioApiKeyEnv"),
|
|
369
|
+
textField("scavioBaseURL"),
|
|
370
|
+
textField("firecrawlApiKeyEnv"),
|
|
371
|
+
textField("firecrawlBaseURL")
|
|
368
372
|
].map((spec) => [spec.field, spec]));
|
|
369
373
|
this.staged = new Map();
|
|
370
374
|
this.saving = false;
|
|
@@ -387,7 +391,7 @@ window.__ModuleLoader__.load({
|
|
|
387
391
|
this.readCredential();
|
|
388
392
|
}
|
|
389
393
|
bind(project) {
|
|
390
|
-
const store = (0,
|
|
394
|
+
const store = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)(project());
|
|
391
395
|
this.listeners.add(() => {
|
|
392
396
|
store.set(project());
|
|
393
397
|
});
|
|
@@ -577,7 +581,7 @@ window.__ModuleLoader__.load({
|
|
|
577
581
|
restKeyRef(rest) {
|
|
578
582
|
const declared = this.sectionValue(rest.keyEnvField);
|
|
579
583
|
if (declared !== void 0 && declared.length > 0) return declared;
|
|
580
|
-
return rest.id === "serper" ? "SERPER_API_KEY" : rest.id === "serpapi" ? "SERPAPI_API_KEY" : "EXA_API_KEY";
|
|
584
|
+
return rest.id === "serper" ? "SERPER_API_KEY" : rest.id === "serpapi" ? "SERPAPI_API_KEY" : rest.id === "exa" ? "EXA_API_KEY" : rest.id === "scavio" ? "SCAVIO_API_KEY" : "FIRECRAWL_API_KEY";
|
|
581
585
|
}
|
|
582
586
|
async readCredential() {
|
|
583
587
|
const ref = this.refOf();
|
|
@@ -587,12 +591,12 @@ window.__ModuleLoader__.load({
|
|
|
587
591
|
}
|
|
588
592
|
let response;
|
|
589
593
|
try {
|
|
590
|
-
response = await this.
|
|
594
|
+
response = await this.remote.credentials.describe([ref]);
|
|
591
595
|
} catch (_credentialReadFailure) {
|
|
592
596
|
return;
|
|
593
597
|
}
|
|
594
|
-
if (!response.
|
|
595
|
-
const view = response.
|
|
598
|
+
if (!response.ok || ref !== this.refOf()) return;
|
|
599
|
+
const view = response.value[ref];
|
|
596
600
|
const next = {
|
|
597
601
|
ref,
|
|
598
602
|
configured: view?.configured ?? false,
|
|
@@ -615,7 +619,8 @@ window.__ModuleLoader__.load({
|
|
|
615
619
|
ref = rest !== void 0 ? this.restKeyRef(rest) : this.tavilyRef();
|
|
616
620
|
}
|
|
617
621
|
try {
|
|
618
|
-
await this.
|
|
622
|
+
const response = await this.remote.credentials.set(ref, value);
|
|
623
|
+
if (!response.ok) return false;
|
|
619
624
|
} catch (_credentialWriteFailure) {}
|
|
620
625
|
await this.readCredential();
|
|
621
626
|
return this.credential.ref === ref ? this.credential.configured : true;
|
|
@@ -624,10 +629,10 @@ window.__ModuleLoader__.load({
|
|
|
624
629
|
|
|
625
630
|
/** Bridges the plugin scope and the credentials domain onto the card. */
|
|
626
631
|
var SearchCardController = class {
|
|
627
|
-
constructor(scope,
|
|
632
|
+
constructor(scope, remote) {
|
|
628
633
|
this.scope = scope;
|
|
629
|
-
this.
|
|
630
|
-
this.form = new SearchCardForm(scope,
|
|
634
|
+
this.remote = remote;
|
|
635
|
+
this.form = new SearchCardForm(scope, remote);
|
|
631
636
|
this.store = this.form.bind(() => this.projection());
|
|
632
637
|
scope.subscribe(() => {
|
|
633
638
|
this.form.readCredential();
|
|
@@ -669,6 +674,12 @@ window.__ModuleLoader__.load({
|
|
|
669
674
|
exaApiKeyEnv: this.form.field("exaApiKeyEnv"),
|
|
670
675
|
exaBaseURL: this.form.field("exaBaseURL"),
|
|
671
676
|
searxngBaseURL: this.form.field("searxngBaseURL"),
|
|
677
|
+
scavioApiKey: this.form.field("scavioApiKey"),
|
|
678
|
+
scavioApiKeyEnv: this.form.field("scavioApiKeyEnv"),
|
|
679
|
+
scavioBaseURL: this.form.field("scavioBaseURL"),
|
|
680
|
+
firecrawlApiKey: this.form.field("firecrawlApiKey"),
|
|
681
|
+
firecrawlApiKeyEnv: this.form.field("firecrawlApiKeyEnv"),
|
|
682
|
+
firecrawlBaseURL: this.form.field("firecrawlBaseURL"),
|
|
672
683
|
apiKeyConfigured: this.form.credential.configured,
|
|
673
684
|
apiKeyWritable: this.form.credential.writable,
|
|
674
685
|
toastKey: this.form.toastKey
|
|
@@ -1075,7 +1086,7 @@ window.__ModuleLoader__.load({
|
|
|
1075
1086
|
fieldRow(t("apiKey"), keyHint, secretInput({ id: "ws-" + restMeta.id + "-key", value: state[restMeta.keyField]?.text ?? "", disabled: !state.apiKeyWritable, placeholder: state.apiKeyConfigured ? t("apiKeySet") : t("apiKeyUnset"), onChange: (e) => actions.edit(restMeta.keyField, e.target.value) })),
|
|
1076
1087
|
row(restMeta.keyEnvField, t("apiKeyEnv"), t("apiKeyEnvHint"), textInput({ id: "ws-" + restMeta.id + "-keyref", value: state[restMeta.keyEnvField]?.text ?? "", disabled, onChange: (e) => actions.edit(restMeta.keyEnvField, e.target.value) }))
|
|
1077
1088
|
) : null,
|
|
1078
|
-
row(restMeta.baseURLField, t("baseURL"), t("baseURLHint"), textInput({ id: "ws-" + restMeta.id + "-base", value: state[restMeta.baseURLField]?.text
|
|
1089
|
+
row(restMeta.baseURLField, t("baseURL"), t("baseURLHint"), textInput({ id: "ws-" + restMeta.id + "-base", value: state[restMeta.baseURLField]?.text || restMeta.defaultBaseURL || "", disabled, onChange: (e) => actions.edit(restMeta.baseURLField, e.target.value) })),
|
|
1079
1090
|
maxResultsRow
|
|
1080
1091
|
) : null,
|
|
1081
1092
|
react.createElement("div", { style: { borderTop: "1px solid var(--dsw-alias-border-l2)", justifyContent: "flex-end", alignItems: "center", gap: "8px", padding: "12px 0 4px", display: "flex" } },
|
|
@@ -1140,11 +1151,10 @@ window.__ModuleLoader__.load({
|
|
|
1140
1151
|
|
|
1141
1152
|
/** Mount the top-level "Web search" settings section. */
|
|
1142
1153
|
function apply(ctx) {
|
|
1143
|
-
const { api } = ctx.get("connection");
|
|
1144
1154
|
const t = ctx.locale.bind(NS);
|
|
1145
1155
|
ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-web-search-plugin: card dictionaries");
|
|
1146
|
-
const controller = new SearchCardController(ctx.settingsScope.bind({ namespace: NS }),
|
|
1147
|
-
ctx.effect(() => ctx.remote.$on("credentials/updated", (ref) => {
|
|
1156
|
+
const controller = new SearchCardController(ctx.settingsScope.bind({ namespace: NS }), ctx.remote);
|
|
1157
|
+
ctx.effect(() => ctx.remote.$on("credentials/reference-updated", (ref) => {
|
|
1148
1158
|
controller.form.refreshCredential(ref);
|
|
1149
1159
|
}), "dsh-web-search-plugin: credential invalidations");
|
|
1150
1160
|
ctx.effect(() => usageStore.start(), "dsh-web-search-plugin: usage poll");
|
|
@@ -1158,7 +1168,7 @@ window.__ModuleLoader__.load({
|
|
|
1158
1168
|
}, makeSection(ctx, controller)));
|
|
1159
1169
|
}
|
|
1160
1170
|
|
|
1161
|
-
const inject = ["slots", "locale", "
|
|
1171
|
+
const inject = ["slots", "locale", "remote", "settingsScope"];
|
|
1162
1172
|
|
|
1163
1173
|
exports.apply = apply;
|
|
1164
1174
|
exports.inject = inject;
|
package/lib/index.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* @module dsh-web-search-plugin
|
|
18
18
|
*/
|
|
19
19
|
import z from "@deepseek-ai/schemastery";
|
|
20
|
-
import {
|
|
20
|
+
import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
|
|
21
21
|
import { MAX_RESULTS_CAP } from "./shared.js";
|
|
22
22
|
import { TAVILY_DEFAULT_API_KEY_ENV, TAVILY_DEFAULT_BASE_URL, TavilySearchProvider, resolveTavilyOptions } from "./tavily.js";
|
|
23
23
|
import { BRAVE_DEFAULT_API_KEY_ENV, BRAVE_DEFAULT_BASE_URL, BraveSearchProvider, resolveBraveOptions } from "./brave.js";
|
|
@@ -33,6 +33,8 @@ const SEARCH_PROVIDER_ID = "dsh-web-search";
|
|
|
33
33
|
const name = "dsh-web-search-plugin";
|
|
34
34
|
/** The web seam this provider registers into. */
|
|
35
35
|
const inject = ["web"];
|
|
36
|
+
/** Settings namespace carrying this plugin's backend switch and options. */
|
|
37
|
+
const WEB_SEARCH_SETTINGS_NAMESPACE = "dsh-web-search-plugin";
|
|
36
38
|
|
|
37
39
|
/** Plugin config (all optional — `apply` fills defaults). */
|
|
38
40
|
const Config = z.object({
|
|
@@ -73,33 +75,33 @@ const Config = z.object({
|
|
|
73
75
|
/** Credential reference for Serper search. */
|
|
74
76
|
serperApiKeyEnv: z.string().role("credential-ref").default("SERPER_API_KEY"),
|
|
75
77
|
/** Serper endpoint base. `/search` is appended. */
|
|
76
|
-
serperBaseURL: z.string().default(""),
|
|
78
|
+
serperBaseURL: z.string().default("https://google.serper.dev"),
|
|
77
79
|
/** Literal SerpApi API key; prefer {@link serpapiApiKeyEnv}. */
|
|
78
80
|
serpapiApiKey: z.string().role("secret").default(""),
|
|
79
81
|
/** Credential reference for SerpApi search. */
|
|
80
82
|
serpapiApiKeyEnv: z.string().role("credential-ref").default("SERPAPI_API_KEY"),
|
|
81
83
|
/** SerpApi endpoint base. `/search.json` is appended. */
|
|
82
|
-
serpapiBaseURL: z.string().default(""),
|
|
84
|
+
serpapiBaseURL: z.string().default("https://serpapi.com"),
|
|
83
85
|
/** Literal Exa API key; prefer {@link exaApiKeyEnv}. */
|
|
84
86
|
exaApiKey: z.string().role("secret").default(""),
|
|
85
87
|
/** Credential reference for Exa search. */
|
|
86
88
|
exaApiKeyEnv: z.string().role("credential-ref").default("EXA_API_KEY"),
|
|
87
89
|
/** Exa endpoint base. `/search` is appended. */
|
|
88
|
-
exaBaseURL: z.string().default(""),
|
|
90
|
+
exaBaseURL: z.string().default("https://api.exa.ai"),
|
|
89
91
|
/** Optional SearXNG instance base URL (self-hosted). `/search` is appended. */
|
|
90
|
-
searxngBaseURL: z.string().default(""),
|
|
92
|
+
searxngBaseURL: z.string().default("https://searx.be"),
|
|
91
93
|
/** Literal Scavio API key; prefer {@link scavioApiKeyEnv}. */
|
|
92
94
|
scavioApiKey: z.string().role("secret").default(""),
|
|
93
95
|
/** Credential reference for Scavio search. */
|
|
94
96
|
scavioApiKeyEnv: z.string().role("credential-ref").default("SCAVIO_API_KEY"),
|
|
95
97
|
/** Scavio endpoint base. `/api/v2/google` is appended. */
|
|
96
|
-
scavioBaseURL: z.string().default(""),
|
|
98
|
+
scavioBaseURL: z.string().default("https://api.scavio.dev"),
|
|
97
99
|
/** Literal Firecrawl API key; prefer {@link firecrawlApiKeyEnv}. */
|
|
98
100
|
firecrawlApiKey: z.string().role("secret").default(""),
|
|
99
101
|
/** Credential reference for Firecrawl search. */
|
|
100
102
|
firecrawlApiKeyEnv: z.string().role("credential-ref").default("FIRECRAWL_API_KEY"),
|
|
101
103
|
/** Firecrawl endpoint base. `/v2/search` is appended. */
|
|
102
|
-
firecrawlBaseURL: z.string().default(""),
|
|
104
|
+
firecrawlBaseURL: z.string().default("https://api.firecrawl.dev"),
|
|
103
105
|
/** Literal DeepSeek API key; prefer {@link deepseekApiKeyEnv}. */
|
|
104
106
|
deepseekApiKey: z.string().role("secret").default(""),
|
|
105
107
|
/** Credential reference for DeepSeek search. */
|
|
@@ -116,11 +118,6 @@ const Config = z.object({
|
|
|
116
118
|
maxUses: z.number().step(1).min(1).default(5)
|
|
117
119
|
});
|
|
118
120
|
|
|
119
|
-
/** Settings namespace carrying this plugin's backend switch and options. */
|
|
120
|
-
const WEB_SEARCH_SETTINGS_NAMESPACE = settingsNamespace("dsh-web-search-plugin");
|
|
121
|
-
/** @deprecated Use {@link WEB_SEARCH_SETTINGS_NAMESPACE}. */
|
|
122
|
-
const WEB_SEARCH_TAVILY_SETTINGS_NAMESPACE = WEB_SEARCH_SETTINGS_NAMESPACE;
|
|
123
|
-
|
|
124
121
|
/**
|
|
125
122
|
* One seam-facing provider whose `id` stays `dsh-web-search`. The active
|
|
126
123
|
* backend is read from the live settings section on every `available` /
|
|
@@ -176,7 +173,7 @@ function resolveOptions(ctx, config) {
|
|
|
176
173
|
// metadata row's default is not enough (self-hosted SearXNG, proxied
|
|
177
174
|
// providers, custom key refs).
|
|
178
175
|
const keySourceByProvider = {
|
|
179
|
-
brave: { literal: config.braveApiKey, envName: config.braveApiKeyEnv ?? BRAVE_DEFAULT_API_KEY_ENV, baseURL: config.braveBaseURL ?? "", proxy: config.proxy },
|
|
176
|
+
brave: { literal: config.braveApiKey, envName: config.braveApiKeyEnv ?? BRAVE_DEFAULT_API_KEY_ENV, baseURL: config.braveBaseURL ?? "", proxy: config.proxy || launchEnvironmentOf(ctx).get("HTTPS_PROXY")?.value || launchEnvironmentOf(ctx).get("HTTP_PROXY")?.value || "" },
|
|
180
177
|
tavily: { literal: config.apiKey, envName: config.apiKeyEnv ?? TAVILY_DEFAULT_API_KEY_ENV, baseURL: config.baseURL ?? "" },
|
|
181
178
|
serper: { literal: config.serperApiKey, envName: config.serperApiKeyEnv ?? "SERPER_API_KEY", baseURL: config.serperBaseURL ?? "" },
|
|
182
179
|
serpapi: { literal: config.serpapiApiKey, envName: config.serpapiApiKeyEnv ?? "SERPAPI_API_KEY", baseURL: config.serpapiBaseURL ?? "" },
|
|
@@ -253,11 +250,13 @@ function sendJson(res, statusCode, data) {
|
|
|
253
250
|
/** Register the dispatched search provider with `ctx.web`. */
|
|
254
251
|
function apply(ctx, config) {
|
|
255
252
|
let current = () => config;
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
253
|
+
ctx.inject(["settings"], (settingsCtx) => {
|
|
254
|
+
settingsCtx.settings.installSection(ctx, WEB_SEARCH_SETTINGS_NAMESPACE, Config, config, {
|
|
255
|
+
setSource: (source) => {
|
|
256
|
+
current = source;
|
|
257
|
+
},
|
|
258
|
+
onChange: () => {}
|
|
259
|
+
});
|
|
261
260
|
});
|
|
262
261
|
const live = () => resolveOptions(ctx, current());
|
|
263
262
|
const usage = new UsageTracker({
|
|
@@ -334,7 +333,6 @@ export {
|
|
|
334
333
|
TavilySearchProvider,
|
|
335
334
|
UsageTracker,
|
|
336
335
|
WEB_SEARCH_SETTINGS_NAMESPACE,
|
|
337
|
-
WEB_SEARCH_TAVILY_SETTINGS_NAMESPACE,
|
|
338
336
|
apply,
|
|
339
337
|
findProvider,
|
|
340
338
|
inject,
|
package/lib/rest.js
CHANGED
|
@@ -188,7 +188,7 @@ export class RestSearchProvider {
|
|
|
188
188
|
const query = {
|
|
189
189
|
[queryParam]: request.query
|
|
190
190
|
};
|
|
191
|
-
if (countParam !== "") query[countParam] = String(count);
|
|
191
|
+
if (countParam !== "") query[countParam] = queryIn === "query" ? String(count) : count;
|
|
192
192
|
const keyed = options.keyed === true;
|
|
193
193
|
for (const param of options.params ?? []) {
|
|
194
194
|
const resolved = resolveParam(param, { keyed, ...options.settings ?? {} });
|
|
@@ -295,8 +295,12 @@ export class RestSearchProvider {
|
|
|
295
295
|
*/
|
|
296
296
|
export function resolveRestProvider(ctx, row, opts) {
|
|
297
297
|
const { config, settings } = opts;
|
|
298
|
-
|
|
298
|
+
let auth = row.auth ?? { type: "none" };
|
|
299
299
|
const keyed = opts.keyed === true;
|
|
300
|
+
// A provider can be keyless by default (e.g. Tavily keyless mode); when no
|
|
301
|
+
// key is expected, drop the auth template so the generic backend does not
|
|
302
|
+
// demand a credential before the request.
|
|
303
|
+
if (row.keyRequired === false && keyed !== true) auth = { type: "none" };
|
|
300
304
|
const base = {
|
|
301
305
|
baseURL: row.baseURL,
|
|
302
306
|
method: row.method ?? "POST",
|
package/lib/shared.js
CHANGED
|
@@ -7,7 +7,7 @@ import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
|
|
|
7
7
|
import { WebError } from "@deepseek-ai/dsh-web";
|
|
8
8
|
|
|
9
9
|
/** Attribution header sent on every request. */
|
|
10
|
-
export const USER_AGENT = "dsh-web-search-plugin/0.
|
|
10
|
+
export const USER_AGENT = "dsh-web-search-plugin/0.4.0";
|
|
11
11
|
/** Shared cap: Tavily `max_results` and Brave `count` both accept 1..20. */
|
|
12
12
|
export const MAX_RESULTS_CAP = 20;
|
|
13
13
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-web-search-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Unified web search for the DeepSeek Harness seam (ctx.web): DeepSeek (official), Tavily, Brave, Serper, SerpApi, Exa, SearXNG, Scavio, and Firecrawl backends",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh-plugin",
|
|
@@ -43,9 +43,7 @@
|
|
|
43
43
|
"client": {
|
|
44
44
|
"platform": "web",
|
|
45
45
|
"inject": [
|
|
46
|
-
"@deepseek-ai/dsh-client-connection",
|
|
47
46
|
"@deepseek-ai/dsh-client-locale",
|
|
48
|
-
"@deepseek-ai/dsh-client-runtime",
|
|
49
47
|
"@deepseek-ai/dsh-client-ui-settings",
|
|
50
48
|
"@deepseek-ai/dsh-client-ui-slots",
|
|
51
49
|
"@deepseek-ai/dsh-api-remotes"
|
|
@@ -59,12 +57,12 @@
|
|
|
59
57
|
"undici": "^6.21.0"
|
|
60
58
|
},
|
|
61
59
|
"peerDependencies": {
|
|
62
|
-
"@deepseek-ai/dsh-web": "^0.1.
|
|
63
|
-
"@deepseek-ai/dsh-credentials": "^0.1.
|
|
64
|
-
"@deepseek-ai/dsh-settings": "^0.1.
|
|
65
|
-
"@deepseek-ai/dsh-launch-environment": "^0.1.
|
|
66
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
67
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
60
|
+
"@deepseek-ai/dsh-web": "^0.1.2-alpha.4",
|
|
61
|
+
"@deepseek-ai/dsh-credentials": "^0.1.2-alpha.4",
|
|
62
|
+
"@deepseek-ai/dsh-settings": "^0.1.2-alpha.4",
|
|
63
|
+
"@deepseek-ai/dsh-launch-environment": "^0.1.2-alpha.4",
|
|
64
|
+
"@deepseek-ai/schemastery": "^3.18.2",
|
|
65
|
+
"@deepseek-ai/cordis": "^4.0.2"
|
|
68
66
|
},
|
|
69
67
|
"license": "MIT",
|
|
70
68
|
"repository": {
|