dsh-search-plugin 0.2.0 → 0.2.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/README.md +6 -5
- package/lib/cidr.js +36 -0
- package/lib/client.js +100 -29
- package/lib/fetch.js +2 -13
- package/lib/index.js +3 -6
- package/lib/types/cidr.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,20 +12,21 @@ web_fetch → ctx.web → search-http → 官方 HttpFetchProvider + CIDR resol
|
|
|
12
12
|
```sh
|
|
13
13
|
pnpm install
|
|
14
14
|
pnpm check
|
|
15
|
-
dsh plugin --profile <name> add
|
|
15
|
+
dsh plugin --profile <name> add dsh-search-plugin
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
通过 npm 发布版本安装和更新;修复不直接覆盖本机已安装插件。更新已经安装的包后需要重启 DSH profile,再刷新网页。仅构建本仓库不会更新已经运行的 GUI。bundle 选择 `searchProvider: codex`、`fetchProvider: search-http`,标准 base 需已有 web 服务与工具消费者;旧 `http` provider 保留,其配置不被修改,但默认路由改由本插件接管。
|
|
19
19
|
|
|
20
20
|
## 配置 UI
|
|
21
21
|
|
|
22
|
-
进入 **插件 → 联网搜索 →
|
|
22
|
+
进入 **插件 → 联网搜索 → 配置**。仅保留插件包这一处配置入口,内部组件行不重复提供相同表单。
|
|
23
23
|
|
|
24
24
|
- 搜索协议选择器动态显示对应协议字段;切换协议保留各自配置,保存后下一次操作生效。
|
|
25
25
|
- Alpha 可复用 OpenAI provider,或使用独立端点与凭据。仅 Alpha 显示超时、响应和答案上限。
|
|
26
26
|
- DeepSeek 显示 API 基础地址、独立凭据引用和结果数量;使用官方 Search API 实现和传输限制。
|
|
27
|
-
-
|
|
28
|
-
-
|
|
27
|
+
- 搜索与网页抓取使用独立表单,各自管理草稿、校验、保存、放弃编辑和重置覆盖;操作只修改所属字段。
|
|
28
|
+
- 网页抓取配置包含 CIDR 多行授权、超时与正文限制。按实际网络环境手动填写 CIDR,不提供预设网段按钮。
|
|
29
|
+
- 保存使用官方 ConfigForms 与 revision 冲突检查,支持只读与失败提示。
|
|
29
30
|
- 密钥走官方 Credentials API 单独保存、删除和刷新状态;不回显、不放入公开配置和浏览器持久存储。删除托管值后,环境变量等其他来源仍可能提供同名凭据。
|
|
30
31
|
|
|
31
32
|
## 搜索配置
|
package/lib/cidr.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Schemastery serializes this function for browser forms. Keep it closure-free. */
|
|
2
|
+
export function validateCidr(rule) {
|
|
3
|
+
const [address, prefixText, ...extra] = rule.trim().split('/');
|
|
4
|
+
if (!address || !prefixText || extra.length || !/^(0|[1-9]\d*)$/.test(prefixText)) {
|
|
5
|
+
throw new Error(`Invalid CIDR: ${JSON.stringify(rule)}`);
|
|
6
|
+
}
|
|
7
|
+
let bytes;
|
|
8
|
+
if (address.includes(':')) {
|
|
9
|
+
if (!/^[\da-f:.]+$/i.test(address))
|
|
10
|
+
throw new Error(`Invalid IPv6 CIDR: ${JSON.stringify(rule)}`);
|
|
11
|
+
// URL uses the same IPv6 parser in Node and browsers, including embedded IPv4.
|
|
12
|
+
const normalized = new URL(`http://[${address}]/`).hostname.slice(1, -1);
|
|
13
|
+
const halves = normalized.split('::').map(part => part ? part.split(':') : []);
|
|
14
|
+
const groups = halves.length === 1 ? halves[0] : [...halves[0], ...Array(8 - halves[0].length - halves[1].length).fill('0'), ...halves[1]];
|
|
15
|
+
bytes = groups.flatMap(group => { const n = parseInt(group, 16); return [n >>> 8, n & 255]; });
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
const parts = address.split('.');
|
|
19
|
+
if (parts.length !== 4 || parts.some(part => !/^(0|[1-9]\d{0,2})$/.test(part) || Number(part) > 255)) {
|
|
20
|
+
throw new Error(`Invalid IPv4 CIDR: ${JSON.stringify(rule)}`);
|
|
21
|
+
}
|
|
22
|
+
bytes = parts.map(Number);
|
|
23
|
+
}
|
|
24
|
+
const prefix = Number(prefixText);
|
|
25
|
+
if (prefix > bytes.length * 8)
|
|
26
|
+
throw new Error(`Invalid CIDR prefix: ${JSON.stringify(rule)}`);
|
|
27
|
+
if (bytes.some((byte, i) => (byte & (255 >>> Math.max(0, Math.min(8, prefix - i * 8)))) !== 0)) {
|
|
28
|
+
throw new Error(`CIDR must use its network base: ${JSON.stringify(rule)}`);
|
|
29
|
+
}
|
|
30
|
+
const embeddedV4 = bytes.length === 16 && bytes.slice(0, 10).every(b => b === 0)
|
|
31
|
+
&& ((bytes[10] === 255 && bytes[11] === 255) || (bytes[10] === 0 && bytes[11] === 0));
|
|
32
|
+
if (embeddedV4 && bytes.some(Boolean) && !(bytes.slice(0, 15).every(b => b === 0) && bytes[15] === 1)) {
|
|
33
|
+
throw new Error(`Use an IPv4 CIDR for IPv4-mapped or compatible addresses: ${JSON.stringify(rule)}`);
|
|
34
|
+
}
|
|
35
|
+
return rule;
|
|
36
|
+
}
|
package/lib/client.js
CHANGED
|
@@ -30,14 +30,12 @@ module.exports = __toCommonJS(index_exports);
|
|
|
30
30
|
// src/client/SearchConfig.tsx
|
|
31
31
|
var import_react = require("react");
|
|
32
32
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
33
|
-
var defaults = { protocol: "codex-alpha", authMode: "openai", openaiProvider: "openai", credentialRef: "DSH_CODEX_API_KEY", endpoint: "", timeoutMs: "60000", maxResponseBytes: "2000000", maxAnswerChars: "16000", deepseekEndpoint: "https://api.deepseek.com/v1/tools", deepseekCredentialRef: "DEEPSEEK_API_KEY", deepseekCount: "10"
|
|
33
|
+
var defaults = { protocol: "codex-alpha", authMode: "openai", openaiProvider: "openai", credentialRef: "DSH_CODEX_API_KEY", endpoint: "", timeoutMs: "60000", maxResponseBytes: "2000000", maxAnswerChars: "16000", deepseekEndpoint: "https://api.deepseek.com/v1/tools", deepseekCredentialRef: "DEEPSEEK_API_KEY", deepseekCount: "10" };
|
|
34
34
|
var fields = Object.keys(defaults);
|
|
35
35
|
var alphaLimits = ["timeoutMs", "maxResponseBytes", "maxAnswerChars"];
|
|
36
|
-
var
|
|
37
|
-
var numeric = /* @__PURE__ */ new Set([...alphaLimits, ...fetchLimits, "deepseekCount"]);
|
|
38
|
-
var cidrsOf = (text) => text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
36
|
+
var numeric = /* @__PURE__ */ new Set([...alphaLimits, "deepseekCount"]);
|
|
39
37
|
function valuesOf(value) {
|
|
40
|
-
return Object.fromEntries(fields.map((field) => [field,
|
|
38
|
+
return Object.fromEntries(fields.map((field) => [field, String(value[field] ?? defaults[field])]));
|
|
41
39
|
}
|
|
42
40
|
function validEndpoint(value) {
|
|
43
41
|
try {
|
|
@@ -88,7 +86,7 @@ function SearchConfig(props) {
|
|
|
88
86
|
return;
|
|
89
87
|
}
|
|
90
88
|
if (!reset) {
|
|
91
|
-
const activeNumeric =
|
|
89
|
+
const activeNumeric = deepseek ? ["deepseekCount"] : alphaLimits;
|
|
92
90
|
let valid = activeNumeric.every((field) => Number.isSafeInteger(Number(values[field])) && Number(values[field]) > 0);
|
|
93
91
|
valid &&= deepseek ? Number(values.deepseekCount) <= 50 && validRef && validEndpoint(values.deepseekEndpoint) : independent ? validRef && validEndpoint(values.endpoint) : Boolean(values.openaiProvider.trim());
|
|
94
92
|
if (!valid) {
|
|
@@ -96,7 +94,7 @@ function SearchConfig(props) {
|
|
|
96
94
|
return;
|
|
97
95
|
}
|
|
98
96
|
}
|
|
99
|
-
const ops = reset ? fields.map((field) => ({ op: "unset", path: [field] })) : fields.filter((field) => values[field] !== accepted[field]).map((field) => ({ op: "set", path: [field], value: numeric.has(field) ? Number(values[field]) :
|
|
97
|
+
const ops = reset ? fields.map((field) => ({ op: "unset", path: [field] })) : fields.filter((field) => values[field] !== accepted[field]).map((field) => ({ op: "set", path: [field], value: numeric.has(field) ? Number(values[field]) : values[field] }));
|
|
100
98
|
setBusy(true);
|
|
101
99
|
setMessage(null);
|
|
102
100
|
try {
|
|
@@ -130,7 +128,7 @@ function SearchConfig(props) {
|
|
|
130
128
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { name: field, type: numeric.has(field) ? "number" : "text", min: numeric.has(field) ? 1 : void 0, max: field === "deepseekCount" ? 50 : void 0, step: numeric.has(field) ? 1 : void 0, value: values[field], onChange: (event) => edit(field, event.target.value), disabled: field === refField && keyBusy })
|
|
131
129
|
] }, field);
|
|
132
130
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-search-config", children: [
|
|
133
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit: (event) => {
|
|
131
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { "aria-label": t("searchSettings"), onSubmit: (event) => {
|
|
134
132
|
event.preventDefault();
|
|
135
133
|
void save(false);
|
|
136
134
|
}, children: [
|
|
@@ -158,16 +156,6 @@ function SearchConfig(props) {
|
|
|
158
156
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("legend", { children: t("searchLimits") }),
|
|
159
157
|
alphaLimits.map(input)
|
|
160
158
|
] }),
|
|
161
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("fieldset", { disabled, children: [
|
|
162
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("legend", { children: t("fetchSettings") }),
|
|
163
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "dsh-search-wide", children: [
|
|
164
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("fetchAllowedCidrs") }),
|
|
165
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("textarea", { name: "fetchAllowedCidrs", rows: 4, value: values.fetchAllowedCidrs, onChange: (event) => edit("fetchAllowedCidrs", event.target.value), "aria-describedby": "dsh-search-cidr-hint" })
|
|
166
|
-
] }),
|
|
167
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { id: "dsh-search-cidr-hint", className: "dsh-search-hint dsh-search-wide", children: t("cidrHint") }),
|
|
168
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "dsh-search-wide", onClick: () => edit("fetchAllowedCidrs", [.../* @__PURE__ */ new Set([...cidrsOf(values.fetchAllowedCidrs), "198.18.0.0/15", "192.168.96.0/20"])].join("\n")), children: t("cidrPreset") }),
|
|
169
|
-
fetchLimits.map(input)
|
|
170
|
-
] }),
|
|
171
159
|
!state.writable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "status", children: t("readOnly") }),
|
|
172
160
|
conflict && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "alert", children: t("conflict") }),
|
|
173
161
|
message && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "status", children: t(message) }),
|
|
@@ -216,6 +204,95 @@ function SearchConfig(props) {
|
|
|
216
204
|
] });
|
|
217
205
|
}
|
|
218
206
|
|
|
207
|
+
// src/client/FetchConfig.tsx
|
|
208
|
+
var import_react2 = require("react");
|
|
209
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
210
|
+
var defaults2 = { fetchAllowedCidrs: "", fetchTimeoutMs: "30000", fetchMaxResponseBytes: "5000000", fetchMaxContentChars: "100000" };
|
|
211
|
+
var fields2 = Object.keys(defaults2);
|
|
212
|
+
var limits = ["fetchTimeoutMs", "fetchMaxResponseBytes", "fetchMaxContentChars"];
|
|
213
|
+
function valuesOf2(value) {
|
|
214
|
+
return Object.fromEntries(fields2.map((field) => [field, field === "fetchAllowedCidrs" ? value[field]?.join("\n") ?? "" : String(value[field] ?? defaults2[field])]));
|
|
215
|
+
}
|
|
216
|
+
function FetchConfig(props) {
|
|
217
|
+
const { t } = props;
|
|
218
|
+
const state = props.useConfig((snapshot) => snapshot);
|
|
219
|
+
const [draft, setDraft] = (0, import_react2.useState)(null);
|
|
220
|
+
const [busy, setBusy] = (0, import_react2.useState)(false);
|
|
221
|
+
const [message, setMessage] = (0, import_react2.useState)(null);
|
|
222
|
+
if (state.status !== "ready" || !state.value) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { role: "status", children: t(state.status === "loading" ? "loading" : "unavailable") });
|
|
223
|
+
const accepted = valuesOf2(state.value);
|
|
224
|
+
const values = draft?.values ?? accepted;
|
|
225
|
+
const conflict = draft !== null && draft.revision !== state.revision;
|
|
226
|
+
const disabled = !state.writable || busy;
|
|
227
|
+
const edit = (field, value) => {
|
|
228
|
+
setDraft({ values: { ...values, [field]: value }, revision: draft ? draft.revision : state.revision });
|
|
229
|
+
setMessage(null);
|
|
230
|
+
};
|
|
231
|
+
const save = async (reset) => {
|
|
232
|
+
if (conflict) {
|
|
233
|
+
setMessage("conflict");
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (!reset && !limits.every((field) => Number.isSafeInteger(Number(values[field])) && Number(values[field]) > 0)) {
|
|
237
|
+
setMessage("fetchInvalid");
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const ops = reset ? fields2.map((field) => ({ op: "unset", path: [field] })) : fields2.filter((field) => values[field] !== accepted[field]).map((field) => ({ op: "set", path: [field], value: field === "fetchAllowedCidrs" ? values[field].split(/\r?\n/).map((line) => line.trim()).filter(Boolean) : Number(values[field]) }));
|
|
241
|
+
setBusy(true);
|
|
242
|
+
setMessage(null);
|
|
243
|
+
try {
|
|
244
|
+
if (await props.mutateConfig(ops, draft ? draft.revision : state.revision)) {
|
|
245
|
+
setDraft(null);
|
|
246
|
+
setMessage("saved");
|
|
247
|
+
} else setMessage("failed");
|
|
248
|
+
} catch {
|
|
249
|
+
setMessage("failed");
|
|
250
|
+
} finally {
|
|
251
|
+
setBusy(false);
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dsh-search-config", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { "aria-label": t("fetchSettings"), onSubmit: (event) => {
|
|
255
|
+
event.preventDefault();
|
|
256
|
+
void save(false);
|
|
257
|
+
}, children: [
|
|
258
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("fieldset", { disabled, children: [
|
|
259
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("legend", { children: t("fetchSettings") }),
|
|
260
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "dsh-search-wide", children: [
|
|
261
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: t("fetchAllowedCidrs") }),
|
|
262
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("textarea", { name: "fetchAllowedCidrs", rows: 4, value: values.fetchAllowedCidrs, onChange: (event) => edit("fetchAllowedCidrs", event.target.value), "aria-describedby": "dsh-search-cidr-hint" })
|
|
263
|
+
] }),
|
|
264
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { id: "dsh-search-cidr-hint", className: "dsh-search-hint dsh-search-wide", children: t("cidrHint") }),
|
|
265
|
+
limits.map((field) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { children: [
|
|
266
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: t(field) }),
|
|
267
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { name: field, type: "number", min: 1, step: 1, value: values[field], onChange: (event) => edit(field, event.target.value) })
|
|
268
|
+
] }, field))
|
|
269
|
+
] }),
|
|
270
|
+
!state.writable && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { role: "status", children: t("readOnly") }),
|
|
271
|
+
conflict && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { role: "alert", children: t("conflict") }),
|
|
272
|
+
message && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { role: "status", children: t(message) }),
|
|
273
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-search-actions", children: [
|
|
274
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "submit", disabled: disabled || conflict || !draft, children: t(busy ? "saving" : "save") }),
|
|
275
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", disabled: disabled || conflict, onClick: () => {
|
|
276
|
+
void save(true);
|
|
277
|
+
}, children: t("reset") }),
|
|
278
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", disabled: busy || !draft, onClick: () => {
|
|
279
|
+
setDraft(null);
|
|
280
|
+
setMessage(null);
|
|
281
|
+
}, children: t("discard") })
|
|
282
|
+
] })
|
|
283
|
+
] }) });
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// src/client/BundleConfig.tsx
|
|
287
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
288
|
+
function BundleConfig(props) {
|
|
289
|
+
if (props.view === "summary") return props.t("description");
|
|
290
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
291
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SearchConfig, { ...props }),
|
|
292
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FetchConfig, { ...props })
|
|
293
|
+
] });
|
|
294
|
+
}
|
|
295
|
+
|
|
219
296
|
// src/client/locales.ts
|
|
220
297
|
var en = {
|
|
221
298
|
title: "Web search",
|
|
@@ -236,8 +313,7 @@ var en = {
|
|
|
236
313
|
fetchTimeoutMs: "Fetch timeout (ms)",
|
|
237
314
|
fetchMaxResponseBytes: "Fetch maximum response bytes",
|
|
238
315
|
fetchMaxContentChars: "Fetch maximum content characters",
|
|
239
|
-
cidrHint: "These ranges explicitly allow web_fetch to reach otherwise blocked network addresses. An empty list adds no exceptions. Enter one IPv4 or IPv6 CIDR per line; other URL, DNS, redirect and transport safety checks still apply.
|
|
240
|
-
cidrPreset: "Add 198.18.0.0/15 and 192.168.96.0/20 to draft",
|
|
316
|
+
cidrHint: "These ranges explicitly allow web_fetch to reach otherwise blocked network addresses. An empty list adds no exceptions. Enter one IPv4 or IPv6 CIDR per line; other URL, DNS, redirect and transport safety checks still apply.",
|
|
241
317
|
authMode: "Authentication",
|
|
242
318
|
openai: "Reuse OpenAI provider (default)",
|
|
243
319
|
credential: "Independent credential",
|
|
@@ -271,6 +347,7 @@ var en = {
|
|
|
271
347
|
keySaved: "Credential updated. Environment or other source layers may still supply a value after removal.",
|
|
272
348
|
keyFailed: "Credential operation failed. Check the credential provider and retry.",
|
|
273
349
|
invalidRef: "Use an environment-style reference name (letters, digits, underscore; not starting with a digit).",
|
|
350
|
+
fetchInvalid: "Use positive integer fetch limits.",
|
|
274
351
|
invalid: "Use positive integer limits and 1\u201350 DeepSeek results. Reuse requires a provider route; independent authentication and DeepSeek require a valid credential reference and a full HTTP(S) URL without credentials or a fragment."
|
|
275
352
|
};
|
|
276
353
|
var zh = {
|
|
@@ -292,8 +369,7 @@ var zh = {
|
|
|
292
369
|
fetchTimeoutMs: "\u6293\u53D6\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",
|
|
293
370
|
fetchMaxResponseBytes: "\u6293\u53D6\u54CD\u5E94\u5B57\u8282\u4E0A\u9650",
|
|
294
371
|
fetchMaxContentChars: "\u6293\u53D6\u5185\u5BB9\u5B57\u7B26\u4E0A\u9650",
|
|
295
|
-
cidrHint: "\u8FD9\u4E9B\u7F51\u6BB5\u660E\u786E\u5141\u8BB8 web_fetch \u8BBF\u95EE\u539F\u672C\u53D7\u963B\u7684\u7F51\u7EDC\u5730\u5740\u3002\u7A7A\u5217\u8868\u4E0D\u589E\u52A0\u653E\u884C\u4F8B\u5916\u3002\u6BCF\u884C\u586B\u5199\u4E00\u4E2A IPv4 \u6216 IPv6 CIDR\uFF1B\u5176\u4ED6 URL\u3001DNS\u3001\u91CD\u5B9A\u5411\u548C\u4F20\u8F93\u5B89\u5168\u68C0\u67E5\u4ECD\u7136\u9002\u7528\u3002
|
|
296
|
-
cidrPreset: "\u5C06 198.18.0.0/15 \u548C 192.168.96.0/20 \u52A0\u5165\u8349\u7A3F",
|
|
372
|
+
cidrHint: "\u8FD9\u4E9B\u7F51\u6BB5\u660E\u786E\u5141\u8BB8 web_fetch \u8BBF\u95EE\u539F\u672C\u53D7\u963B\u7684\u7F51\u7EDC\u5730\u5740\u3002\u7A7A\u5217\u8868\u4E0D\u589E\u52A0\u653E\u884C\u4F8B\u5916\u3002\u6BCF\u884C\u586B\u5199\u4E00\u4E2A IPv4 \u6216 IPv6 CIDR\uFF1B\u5176\u4ED6 URL\u3001DNS\u3001\u91CD\u5B9A\u5411\u548C\u4F20\u8F93\u5B89\u5168\u68C0\u67E5\u4ECD\u7136\u9002\u7528\u3002",
|
|
297
373
|
authMode: "\u8BA4\u8BC1\u65B9\u5F0F",
|
|
298
374
|
openai: "\u590D\u7528 OpenAI Provider\uFF08\u9ED8\u8BA4\uFF09",
|
|
299
375
|
credential: "\u72EC\u7ACB\u51ED\u636E",
|
|
@@ -327,6 +403,7 @@ var zh = {
|
|
|
327
403
|
keySaved: "\u51ED\u636E\u5DF2\u66F4\u65B0\uFF1B\u79FB\u9664\u540E\u73AF\u5883\u53D8\u91CF\u6216\u5176\u4ED6\u6765\u6E90\u4ECD\u53EF\u80FD\u63D0\u4F9B\u51ED\u636E\u3002",
|
|
328
404
|
keyFailed: "\u51ED\u636E\u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u51ED\u636E\u63D0\u4F9B\u65B9\u540E\u91CD\u8BD5\u3002",
|
|
329
405
|
invalidRef: "\u8BF7\u8F93\u5165\u73AF\u5883\u53D8\u91CF\u683C\u5F0F\u7684\u5F15\u7528\u540D\uFF08\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\uFF0C\u4E0D\u80FD\u4EE5\u6570\u5B57\u5F00\u5934\uFF09\u3002",
|
|
406
|
+
fetchInvalid: "\u8BF7\u586B\u5199\u6B63\u6574\u6570\u6293\u53D6\u4E0A\u9650\u3002",
|
|
330
407
|
invalid: "\u8BF7\u586B\u5199\u6B63\u6574\u6570\u4E0A\u9650\uFF0CDeepSeek \u7ED3\u679C\u6570\u91CF\u987B\u4E3A 1\u201350\u3002\u590D\u7528\u6A21\u5F0F\u9700\u8981 Provider \u8DEF\u7531\uFF1B\u72EC\u7ACB\u8BA4\u8BC1\u548C DeepSeek \u9700\u8981\u6709\u6548\u51ED\u636E\u5F15\u7528\u540D\u4E0E\u5B8C\u6574 HTTP(S) \u5730\u5740\uFF0CURL \u4E0D\u5F97\u5305\u542B\u7528\u6237\u540D\u3001\u5BC6\u7801\u6216\u7247\u6BB5\u3002"
|
|
331
408
|
};
|
|
332
409
|
|
|
@@ -412,13 +489,7 @@ function apply(ctx) {
|
|
|
412
489
|
key: "dsh-search-plugin",
|
|
413
490
|
locale: "searchPlugin",
|
|
414
491
|
inject: () => face
|
|
415
|
-
},
|
|
416
|
-
ctx.effect(() => ctx.slots.inject("plugins.row.config", () => ctx.slots.register({
|
|
417
|
-
name: "plugins.row.config",
|
|
418
|
-
key: "dsh-search-plugin#dsh-search-plugin",
|
|
419
|
-
locale: "searchPlugin",
|
|
420
|
-
inject: () => face
|
|
421
|
-
}, SearchConfig)));
|
|
492
|
+
}, BundleConfig)));
|
|
422
493
|
}
|
|
423
494
|
|
|
424
495
|
return module.exports;
|
package/lib/fetch.js
CHANGED
|
@@ -1,25 +1,14 @@
|
|
|
1
1
|
import { lookup } from 'node:dns/promises';
|
|
2
2
|
import { isIP } from 'node:net';
|
|
3
3
|
import ipaddr from 'ipaddr.js';
|
|
4
|
+
import { validateCidr } from './cidr.js';
|
|
4
5
|
import { WebError } from '@deepseek-ai/dsh-web';
|
|
5
6
|
import { HttpFetchProvider } from '@deepseek-ai/dsh-web-fetch-http';
|
|
6
7
|
const bareHost = (host) => host.startsWith('[') && host.endsWith(']') ? host.slice(1, -1) : host;
|
|
7
8
|
function parseCidr(rule) {
|
|
8
|
-
const [address, prefixText
|
|
9
|
-
if (!address || !prefixText || extra.length || !/^(0|[1-9]\d*)$/.test(prefixText) || !isIP(address) || address.includes('%')) {
|
|
10
|
-
throw new Error(`Invalid CIDR: ${JSON.stringify(rule)}`);
|
|
11
|
-
}
|
|
9
|
+
const [address, prefixText] = validateCidr(rule).trim().split('/');
|
|
12
10
|
const bytes = ipaddr.parse(address).toByteArray();
|
|
13
11
|
const prefix = Number(prefixText);
|
|
14
|
-
if (prefix > bytes.length * 8)
|
|
15
|
-
throw new Error(`Invalid CIDR prefix: ${JSON.stringify(rule)}`);
|
|
16
|
-
// Requiring the network base catches accidental overly broad operator rules.
|
|
17
|
-
if (bytes.some((byte, i) => (byte & (255 >>> Math.max(0, Math.min(8, prefix - i * 8)))) !== 0)) {
|
|
18
|
-
throw new Error(`CIDR must use its network base: ${JSON.stringify(rule)}`);
|
|
19
|
-
}
|
|
20
|
-
if (bytes.length === 16 && isEmbeddedV4(bytes) && bytes.some(Boolean) && !(bytes.slice(0, 15).every(b => b === 0) && bytes[15] === 1)) {
|
|
21
|
-
throw new Error(`Use an IPv4 CIDR for IPv4-mapped or compatible addresses: ${JSON.stringify(rule)}`);
|
|
22
|
-
}
|
|
23
12
|
return { bytes, prefix };
|
|
24
13
|
}
|
|
25
14
|
/** Validate explicit IPv4/IPv6 network CIDRs; no implicit private ranges are added. */
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Schema from '@deepseek-ai/schemastery';
|
|
2
2
|
import { ConfiguredSearchProvider } from './connection.js';
|
|
3
|
-
import { ConfiguredFetchProvider
|
|
3
|
+
import { ConfiguredFetchProvider } from './fetch.js';
|
|
4
|
+
import { validateCidr } from './cidr.js';
|
|
4
5
|
export const name = 'dsh-search-plugin';
|
|
5
6
|
export const inject = ['web'];
|
|
6
7
|
export const Config = Schema.object({
|
|
@@ -8,11 +9,7 @@ export const Config = Schema.object({
|
|
|
8
9
|
deepseekEndpoint: Schema.string().pattern(/^https?:\/\/[^\s]+$/).default('https://api.deepseek.com/v1/tools').volatile(),
|
|
9
10
|
deepseekCredentialRef: Schema.string().pattern(/^[A-Za-z_][A-Za-z0-9_]*$/).default('DEEPSEEK_API_KEY').volatile(),
|
|
10
11
|
deepseekCount: Schema.number().step(1).min(1).max(50).default(10).volatile(),
|
|
11
|
-
fetchAllowedCidrs: Schema.
|
|
12
|
-
if (!validateCidrs(value))
|
|
13
|
-
throw new Error('Use valid IPv4 or IPv6 network CIDRs');
|
|
14
|
-
return value;
|
|
15
|
-
}, true).default([]).volatile(),
|
|
12
|
+
fetchAllowedCidrs: Schema.array(Schema.transform(Schema.string(), validateCidr, true)).default([]).volatile(),
|
|
16
13
|
fetchTimeoutMs: Schema.number().step(1).min(1).default(30000).volatile(),
|
|
17
14
|
fetchMaxResponseBytes: Schema.number().step(1).min(1).default(5000000).volatile(),
|
|
18
15
|
fetchMaxContentChars: Schema.number().step(1).min(1).default(100000).volatile(),
|