dsh-search-plugin 0.1.5 → 0.2.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 +46 -68
- package/cordis.patch.yml +1 -1
- package/icon.svg +1 -0
- package/lib/client.js +124 -71
- package/lib/connection.js +3 -3
- package/lib/fetch.js +144 -0
- package/lib/index.js +18 -0
- package/lib/protocols.js +15 -0
- package/lib/types/connection.d.ts +1 -1
- package/lib/types/fetch.d.ts +23 -0
- package/lib/types/index.d.ts +24 -0
- package/lib/types/protocols.d.ts +9 -0
- package/package.json +9 -4
package/README.md
CHANGED
|
@@ -1,103 +1,81 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 联网搜索与网页抓取
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
TypeScript ESM 搜索组合包,目标运行版本 **DSH 0.2.1-rc.4**。注册 `codex` 搜索 Provider,复用官方 `web_search`;网页抓取复用本机 `http` Provider 与 `web_fetch`,不重复注册工具。
|
|
3
|
+
面向 **DSH 0.2.1-rc.4** 的 TypeScript ESM 插件。复用官方 `web_search`、`web_fetch` 工具,通过实时配置选择搜索协议,增强本机 HTTP 抓取的地址授权。设计参考 [dsh-tool-web-enhanced](https://github.com/Yurzi/dsh-tool-web-enhanced),本仓库实现仅覆盖 Alpha Search、DeepSeek Search API 与本机抓取。
|
|
6
4
|
|
|
7
5
|
```text
|
|
8
|
-
web_search → ctx.web → codex →
|
|
9
|
-
web_fetch → ctx.web → http
|
|
6
|
+
web_search → ctx.web → codex(稳定路由)→ protocol factory → Alpha / DeepSeek Search API
|
|
7
|
+
web_fetch → ctx.web → search-http → 官方 HttpFetchProvider + CIDR resolver
|
|
10
8
|
```
|
|
11
9
|
|
|
12
|
-
##
|
|
10
|
+
## 构建与安装
|
|
13
11
|
|
|
14
12
|
```sh
|
|
15
13
|
pnpm install
|
|
16
14
|
pnpm check
|
|
17
|
-
# 在父目录执行,将 <name> 替换为目标 profile:
|
|
18
15
|
dsh plugin --profile <name> add ./dsh-search-plugin
|
|
19
16
|
```
|
|
20
17
|
|
|
21
|
-
本地 checkout
|
|
22
|
-
|
|
23
|
-
bundle 要求标准 DSH base 已有 `web`、`web-fetch-http` 与工具消费者,并选择 `searchProvider: codex`、`fetchProvider: http`。不覆盖已有 HTTP fetch 安全限制、超时或 User-Agent;其他后应用的 patch 可覆盖路由。
|
|
24
|
-
|
|
25
|
-
## 默认配置:无需填写端点或模型
|
|
26
|
-
|
|
27
|
-
默认 `authMode: openai`、`openaiProvider: openai`,复用所选 OpenAI Provider 的实时 `baseURL` 与显式 `apiKeyEnv` 引用。例如:
|
|
28
|
-
|
|
29
|
-
```yaml
|
|
30
|
-
baseURL: https://your-api.example/v1
|
|
31
|
-
apiKeyEnv: OPENAI_API_KEY
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
搜索自动请求 `https://your-api.example/v1/alpha/search`,通过官方 Credentials 服务解析同一个密钥引用。**不要求填写搜索模型,不调用 Responses,不做协议探测或失败回退。** 上游必须实现 Alpha 搜索接口,普通 OpenAI 兼容聊天服务不一定支持。
|
|
18
|
+
本地 checkout 安装需要保留目录及构建产物。更新已经安装的包后需要重启 DSH profile,再刷新网页。仅构建本仓库不会更新已经运行的 GUI。bundle 选择 `searchProvider: codex`、`fetchProvider: search-http`,标准 base 需已有 web 服务与工具消费者;旧 `http` provider 保留,其配置不被修改,但默认路由改由本插件接管。
|
|
35
19
|
|
|
36
|
-
|
|
20
|
+
## 配置 UI
|
|
37
21
|
|
|
38
|
-
|
|
22
|
+
进入 **插件 → 联网搜索 → 配置**,或组件行的配置页面。
|
|
39
23
|
|
|
40
|
-
|
|
24
|
+
- 搜索协议选择器动态显示对应协议字段;切换协议保留各自配置,保存后下一次操作生效。
|
|
25
|
+
- Alpha 可复用 OpenAI provider,或使用独立端点与凭据。仅 Alpha 显示超时、响应和答案上限。
|
|
26
|
+
- DeepSeek 显示 API 基础地址、独立凭据引用和结果数量;使用官方 Search API 实现和传输限制。
|
|
27
|
+
- 网页抓取独立分组:CIDR 多行授权、超时与正文限制。可将常见代理网段填入草稿,再保存生效。
|
|
28
|
+
- 保存使用官方 ConfigForms 与 revision 冲突检查,支持放弃编辑、重置覆盖、只读与失败提示。
|
|
29
|
+
- 密钥走官方 Credentials API 单独保存、删除和刷新状态;不回显、不放入公开配置和浏览器持久存储。删除托管值后,环境变量等其他来源仍可能提供同名凭据。
|
|
41
30
|
|
|
42
|
-
|
|
31
|
+
## 搜索配置
|
|
43
32
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
-
|
|
33
|
+
| 字段 | 默认值 | 含义 |
|
|
34
|
+
| --- | --- | --- |
|
|
35
|
+
| `protocol` | `codex-alpha` | `codex-alpha` 或 `deepseek-search-api` |
|
|
36
|
+
| `authMode` | `openai` | Alpha 复用 OpenAI,或 `credential` 独立认证 |
|
|
37
|
+
| `openaiProvider` | `openai` | Alpha 复用的官方 LLM route |
|
|
38
|
+
| `credentialRef` | `DSH_CODEX_API_KEY` | Alpha 独立凭据引用 |
|
|
39
|
+
| `endpoint` | 空 | Alpha 独立模式完整 POST URL |
|
|
40
|
+
| `apiKey` | 无 | 兼容旧版 Host 密钥字段,仅 Alpha 独立模式使用,建议迁移到 Credentials |
|
|
41
|
+
| `timeoutMs` | `60000` | Alpha 请求超时 |
|
|
42
|
+
| `maxResponseBytes` | `2000000` | Alpha 响应上限 |
|
|
43
|
+
| `maxAnswerChars` | `16000` | Alpha 答案字符上限 |
|
|
44
|
+
| `deepseekEndpoint` | `https://api.deepseek.com/v1/tools` | DeepSeek API 基础地址,追加 `/search` |
|
|
45
|
+
| `deepseekCredentialRef` | `DEEPSEEK_API_KEY` | DeepSeek 独立凭据引用 |
|
|
46
|
+
| `deepseekCount` | `10` | 1–50,单次 `maxResults` 更小时使用更小值 |
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
Alpha 复用模式每次读取选中 provider 的实时 `baseURL` 与显式 `apiKeyEnv`,派生 `/alpha/search`,不将复用密钥发送到独立 `endpoint`。不支持 OAuth、私有 token 提取或自定义鉴权 headers 复制。独立模式必须填写完整 URL。保留 `DSH_CODEX_AUTH_MODE`、`DSH_CODEX_ENDPOINT`、`DSH_CODEX_API_KEY` 启动环境兼容。
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
Alpha 使用内置路由模型 `gpt-5.6-luna`,发送原生 Alpha request,读取 `output/results`,不回退 Responses 或聊天协议。DeepSeek 使用官方 `DeepSeekSearchApiProvider`,请求 `query/count/need_content: false`,映射结构化结果。两个协议的凭据独立,不静默尝试其他协议。
|
|
51
51
|
|
|
52
|
-
##
|
|
52
|
+
## 代理网段抓取
|
|
53
53
|
|
|
54
54
|
| 字段 | 默认值 | 含义 |
|
|
55
55
|
| --- | --- | --- |
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
58
|
-
| `
|
|
59
|
-
| `
|
|
60
|
-
| `apiKey` | 无 | 兼容旧版的 Host-only 密钥,仅独立模式使用,优先于引用;推荐改用凭据管理 |
|
|
61
|
-
| `timeoutMs` | `60000` | 单次搜索请求超时,毫秒 |
|
|
62
|
-
| `maxResponseBytes` | `2000000` | 搜索响应主体的字节上限 |
|
|
63
|
-
| `maxAnswerChars` | `16000` | 返回答案的字符上限 |
|
|
64
|
-
|
|
65
|
-
以上字段为即时配置。上限仅控制搜索,不控制本机 HTTP fetch。旧版 `model` 不再使用。复用模式忽略旧 `endpoint`;独立模式升级后需把原 `/responses` 端点改为实际的 Alpha 搜索 URL,插件不会自动改写独立端点。
|
|
56
|
+
| `fetchAllowedCidrs` | `[]` | 额外授权的 IPv4/IPv6 网络 CIDR |
|
|
57
|
+
| `fetchTimeoutMs` | `30000` | 抓取超时 |
|
|
58
|
+
| `fetchMaxResponseBytes` | `5000000` | 响应字节上限 |
|
|
59
|
+
| `fetchMaxContentChars` | `100000` | 正文字符上限 |
|
|
66
60
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
独立模式的 profile patch 示例:
|
|
61
|
+
公网默认可访问;非公开地址仅在显式 CIDR 授权内可访问。对于 Fake-IP DNS,可在 UI 填入并保存:
|
|
70
62
|
|
|
71
63
|
```yaml
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
endpoint: https://your-search.example/v1/alpha/search
|
|
76
|
-
credentialRef: SEARCH_API_KEY
|
|
64
|
+
fetchAllowedCidrs:
|
|
65
|
+
- 198.18.0.0/15
|
|
66
|
+
- 192.168.96.0/20
|
|
77
67
|
```
|
|
78
68
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
## 协议与安全边界
|
|
82
|
-
|
|
83
|
-
只支持 **POST `/v1/alpha/search`**,不支持 Responses、Chat Completions 或直接连接 ChatGPT 私有后端;独立模式的 `endpoint` 是完整 URL,不自动补路径。协议按 [Codex 官方 SearchRequest/SearchResponse](https://github.com/openai/codex/blob/main/codex-rs/codex-api/src/search.rs) 实现,而不是替换 Responses URL:
|
|
84
|
-
|
|
85
|
-
- 每次请求生成 `id`,发送 `commands.search_query: [{ q: 查询文本 }]`、用户 `input`、`settings: { allowed_callers: ['direct'], external_web_access: true }` 和 `max_output_tokens: 2048`,不发送 Responses `tools`、`tool_choice`、`include`、`stream` 或 `store`。
|
|
86
|
-
- Alpha 协议自身要求 `model`。适配器内置路由模型 `gpt-5.6-luna`([已公开 Alpha 适配器的默认值](https://unpkg.com/@caiqy/dsh-plugin-web-search@0.1.0/lib/providers.js)),不继承聊天模型,也不要求用户填写。目标服务必须支持此路由模型。
|
|
87
|
-
- 响应读取文本 `output` 和可选 `results`(缺失、`null`、空数组均支持)。`results` 是可扩展的不透明条目,只提取有合法 HTTP(S) `url` 的来源及可选 `title`,不要求存在这些字段,不输出 `encrypted_output`。清理 Alpha 私有引用标记,来源去重后交给 DSH 管理数量限制。
|
|
88
|
-
|
|
89
|
-
搜索端点会收到密钥与查询,请仅配置可信服务,推荐 HTTPS。配置缺失、鉴权错误、上游协议错误、超时等明确失败,不静默切换 Provider,不返回伪造的空结果。不自动重试,不跟随携带认证的重定向。来源只取自结构化响应,不从自然语言猜测或编造链接。DSH 的 `truncated` 表示来源被裁剪,不表示答案字符裁剪。
|
|
90
|
-
|
|
91
|
-
网页与答案均属于不可信外部数据,不能作为系统指令或工具授权。排错时不要打印未脱敏请求头、真实密钥或完整远端错误正文。
|
|
69
|
+
授权是对整个网段的授权,不区分代理 Fake-IP 和该段内真实服务。默认不自动开启。CIDR 必须使用网络基址;支持 IPv4/IPv6,映射 IPv4 地址使用 IPv4 CIDR。校验整个 DNS 答案集,并将结果交给官方传输固定连接地址;保留 URL/协议检查、同源重定向、超时、响应上限与代理处理。抓取不会携带搜索密钥。不提供 JS 渲染、登录、PDF 转换或跨源自动跳转。
|
|
92
70
|
|
|
93
|
-
|
|
71
|
+
当配置 HTTP/SOCKS 代理时,沿用官方代理解析与连接策略;代理远端解析域名的行为不由本机 resolver 接管,因此该 allowlist 不是代理服务器的网络隔离策略。需要约束代理后方目的地址时,应在代理端配置策略。
|
|
94
72
|
|
|
95
|
-
|
|
73
|
+
## 扩展协议
|
|
96
74
|
|
|
97
|
-
|
|
75
|
+
`src/protocols.ts` 的 factory registry 负责按显式协议创建适配器,每次操作捕获当前配置;凭据由各适配器独立解析。新增协议时添加 factory、schema 专属字段与对应 UI 分支,不需要修改工具定义或替换工具执行链。现有 `codex` route id 为升级兼容保持稳定,其实际协议由 `protocol` 决定。
|
|
98
76
|
|
|
99
|
-
|
|
77
|
+
配置通过官方编辑器持久化,插件不改写 profile 文件。profile patch 替换整份 config;手写 patch 时保留所需字段。网页与搜索答案均为不可信外部数据。上游失败保持可见,不自动重试或用空结果掩盖错误。
|
|
100
78
|
|
|
101
79
|
## 验证
|
|
102
80
|
|
|
103
|
-
|
|
81
|
+
`pnpm check` 包含类型检查、构建、官方 Loader 集成、协议切换、凭据隔离、UI 与抓取安全测试。真实服务 E2E 需要显式开启现有 `DSH_SEARCH_E2E*` 环境选项,可能产生费用;本地通过不等同于在线凭据或当前 GUI 的验收。
|
package/cordis.patch.yml
CHANGED
package/icon.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="16" fill="#2563eb"/><g fill="none" stroke="white" stroke-width="3"><circle cx="28" cy="27" r="17"/><ellipse cx="28" cy="27" rx="7" ry="17"/><path d="M12 22h32M12 32h32M41 40l12 13" stroke-linecap="round"/></g></svg>
|
package/lib/client.js
CHANGED
|
@@ -30,11 +30,22 @@ 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
|
|
34
|
-
var
|
|
35
|
-
var
|
|
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", fetchAllowedCidrs: "", fetchTimeoutMs: "30000", fetchMaxResponseBytes: "5000000", fetchMaxContentChars: "100000" };
|
|
34
|
+
var fields = Object.keys(defaults);
|
|
35
|
+
var alphaLimits = ["timeoutMs", "maxResponseBytes", "maxAnswerChars"];
|
|
36
|
+
var fetchLimits = ["fetchTimeoutMs", "fetchMaxResponseBytes", "fetchMaxContentChars"];
|
|
37
|
+
var numeric = /* @__PURE__ */ new Set([...alphaLimits, ...fetchLimits, "deepseekCount"]);
|
|
38
|
+
var cidrsOf = (text) => text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
36
39
|
function valuesOf(value) {
|
|
37
|
-
return Object.fromEntries(fields.map((field) => [field, String(value[field] ?? defaults[field])]));
|
|
40
|
+
return Object.fromEntries(fields.map((field) => [field, field === "fetchAllowedCidrs" ? value[field]?.join("\n") ?? "" : String(value[field] ?? defaults[field])]));
|
|
41
|
+
}
|
|
42
|
+
function validEndpoint(value) {
|
|
43
|
+
try {
|
|
44
|
+
const url = new URL(value);
|
|
45
|
+
return ["http:", "https:"].includes(url.protocol) && !url.username && !url.password && !url.hash;
|
|
46
|
+
} catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
38
49
|
}
|
|
39
50
|
function SearchConfig(props) {
|
|
40
51
|
const { t } = props;
|
|
@@ -50,26 +61,25 @@ function SearchConfig(props) {
|
|
|
50
61
|
if (state.status !== "ready" || !state.value) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "status", children: t(state.status === "loading" ? "loading" : "unavailable") });
|
|
51
62
|
const accepted = valuesOf(state.value);
|
|
52
63
|
const values = draft?.values ?? accepted;
|
|
53
|
-
const
|
|
64
|
+
const deepseek = values.protocol === "deepseek-search-api";
|
|
65
|
+
const independent = deepseek || values.authMode === "credential";
|
|
66
|
+
const refField = deepseek ? "deepseekCredentialRef" : "credentialRef";
|
|
67
|
+
const ref = values[refField];
|
|
68
|
+
const keyText = key?.ref === ref ? key.text : "";
|
|
54
69
|
const conflict = draft !== null && draft.revision !== state.revision;
|
|
55
70
|
const disabled = !state.writable || busy;
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
const validRef = /^[A-Za-z_][A-Za-z0-9_]*$/.test(values.credentialRef);
|
|
59
|
-
const credentialCurrent = credential.ref === values.credentialRef;
|
|
71
|
+
const validRef = /^[A-Za-z_][A-Za-z0-9_]*$/.test(ref);
|
|
72
|
+
const credentialCurrent = credential.ref === ref;
|
|
60
73
|
const info = credentialCurrent ? credential.info : void 0;
|
|
61
|
-
const credentialWritable = info?.writable === true && !keyBusy;
|
|
74
|
+
const credentialWritable = validRef && info?.writable === true && !keyBusy;
|
|
62
75
|
const edit = (field, value) => {
|
|
63
|
-
|
|
76
|
+
const next = { ...values, [field]: value };
|
|
77
|
+
setDraft({ values: next, revision: draft ? draft.revision : state.revision });
|
|
64
78
|
setMessage(null);
|
|
65
|
-
if (field === "authMode") {
|
|
79
|
+
if (field === "protocol" || field === "authMode" || field === refField) {
|
|
66
80
|
setKey(null);
|
|
67
81
|
setKeyMessage(null);
|
|
68
|
-
|
|
69
|
-
if (field === "credentialRef") {
|
|
70
|
-
setKey(null);
|
|
71
|
-
setKeyMessage(null);
|
|
72
|
-
props.describeCredential(value);
|
|
82
|
+
props.describeCredential(next.protocol === "deepseek-search-api" ? next.deepseekCredentialRef : next.credentialRef);
|
|
73
83
|
}
|
|
74
84
|
};
|
|
75
85
|
const save = async (reset) => {
|
|
@@ -78,29 +88,22 @@ function SearchConfig(props) {
|
|
|
78
88
|
return;
|
|
79
89
|
}
|
|
80
90
|
if (!reset) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const endpoint = new URL(values.endpoint);
|
|
85
|
-
valid &&= validRef && ["http:", "https:"].includes(endpoint.protocol) && !endpoint.username && !endpoint.password && !endpoint.hash;
|
|
86
|
-
} catch {
|
|
87
|
-
valid = false;
|
|
88
|
-
}
|
|
89
|
-
} else valid &&= Boolean(values.openaiProvider.trim());
|
|
91
|
+
const activeNumeric = [...fetchLimits, ...deepseek ? ["deepseekCount"] : alphaLimits];
|
|
92
|
+
let valid = activeNumeric.every((field) => Number.isSafeInteger(Number(values[field])) && Number(values[field]) > 0);
|
|
93
|
+
valid &&= deepseek ? Number(values.deepseekCount) <= 50 && validRef && validEndpoint(values.deepseekEndpoint) : independent ? validRef && validEndpoint(values.endpoint) : Boolean(values.openaiProvider.trim());
|
|
90
94
|
if (!valid) {
|
|
91
95
|
setMessage("invalid");
|
|
92
96
|
return;
|
|
93
97
|
}
|
|
94
98
|
}
|
|
95
|
-
const
|
|
96
|
-
const savedFields = ["authMode", ...visibleFields, ...!independent ? ["endpoint"] : []];
|
|
97
|
-
const ops = reset ? fields.map((field) => ({ op: "unset", path: [field] })) : savedFields.filter((field) => savedValues[field] !== accepted[field]).map((field) => ({ op: "set", path: [field], value: numeric.has(field) ? Number(savedValues[field]) : savedValues[field] }));
|
|
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]) : field === "fetchAllowedCidrs" ? cidrsOf(values[field]) : values[field] }));
|
|
98
100
|
setBusy(true);
|
|
99
101
|
setMessage(null);
|
|
100
102
|
try {
|
|
101
103
|
if (await props.mutateConfig(ops, draft ? draft.revision : state.revision)) {
|
|
102
104
|
setDraft(null);
|
|
103
105
|
setMessage("saved");
|
|
106
|
+
setKey(null);
|
|
104
107
|
} else setMessage("failed");
|
|
105
108
|
} catch {
|
|
106
109
|
setMessage("failed");
|
|
@@ -109,7 +112,6 @@ function SearchConfig(props) {
|
|
|
109
112
|
}
|
|
110
113
|
};
|
|
111
114
|
const writeKey = async (remove) => {
|
|
112
|
-
const ref = values.credentialRef;
|
|
113
115
|
setKeyBusy(true);
|
|
114
116
|
setKeyMessage(null);
|
|
115
117
|
try {
|
|
@@ -123,68 +125,79 @@ function SearchConfig(props) {
|
|
|
123
125
|
setKeyBusy(false);
|
|
124
126
|
}
|
|
125
127
|
};
|
|
128
|
+
const input = (field) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
|
|
129
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t(field) }),
|
|
130
|
+
/* @__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
|
+
] }, field);
|
|
126
132
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-search-config", children: [
|
|
127
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dsh-search-hint", children: t("inherit") }),
|
|
128
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dsh-search-hint", children: t("trust") }),
|
|
129
133
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit: (event) => {
|
|
130
134
|
event.preventDefault();
|
|
131
135
|
void save(false);
|
|
132
136
|
}, children: [
|
|
133
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("fieldset", { disabled, children: [
|
|
137
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("fieldset", { disabled: disabled || keyBusy, children: [
|
|
138
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("legend", { children: t("searchSettings") }),
|
|
134
139
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
|
|
140
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("protocol") }),
|
|
141
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { name: "protocol", value: values.protocol, onChange: (event) => edit("protocol", event.target.value), children: [
|
|
142
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "codex-alpha", children: t("codexAlpha") }),
|
|
143
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "deepseek-search-api", children: t("deepseekSearchApi") })
|
|
144
|
+
] })
|
|
145
|
+
] }),
|
|
146
|
+
!deepseek && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
|
|
135
147
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("authMode") }),
|
|
136
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { value: values.authMode, onChange: (event) => edit("authMode", event.target.value), children: [
|
|
148
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { name: "authMode", value: values.authMode, onChange: (event) => edit("authMode", event.target.value), children: [
|
|
137
149
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "openai", children: t("openai") }),
|
|
138
150
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "credential", children: t("credential") })
|
|
139
151
|
] })
|
|
140
152
|
] }),
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
)
|
|
154
|
-
] },
|
|
153
|
+
(deepseek ? ["deepseekEndpoint", "deepseekCredentialRef", "deepseekCount"] : independent ? ["endpoint", "credentialRef"] : ["openaiProvider"]).map(input)
|
|
154
|
+
] }),
|
|
155
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dsh-search-hint", children: t(deepseek ? "deepseekHint" : "inherit") }),
|
|
156
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dsh-search-hint", children: t("trust") }),
|
|
157
|
+
!deepseek && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("fieldset", { disabled, children: [
|
|
158
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("legend", { children: t("searchLimits") }),
|
|
159
|
+
alphaLimits.map(input)
|
|
160
|
+
] }),
|
|
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)
|
|
155
170
|
] }),
|
|
156
171
|
!state.writable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "status", children: t("readOnly") }),
|
|
157
172
|
conflict && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "alert", children: t("conflict") }),
|
|
158
173
|
message && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "status", children: t(message) }),
|
|
159
174
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-search-actions", children: [
|
|
160
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "submit", disabled: disabled || conflict || !draft, children: t(busy ? "saving" : "save") }),
|
|
161
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: disabled || conflict, onClick: () => {
|
|
175
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "submit", disabled: disabled || keyBusy || conflict || !draft, children: t(busy ? "saving" : "save") }),
|
|
176
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: disabled || keyBusy || conflict, onClick: () => {
|
|
162
177
|
void save(true);
|
|
163
178
|
}, children: t("reset") }),
|
|
164
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: busy || !draft, onClick: () => {
|
|
179
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: busy || keyBusy || !draft, onClick: () => {
|
|
165
180
|
setDraft(null);
|
|
166
181
|
setMessage(null);
|
|
167
182
|
setKey(null);
|
|
168
|
-
|
|
183
|
+
setKeyMessage(null);
|
|
184
|
+
props.describeCredential(accepted.protocol === "deepseek-search-api" ? accepted.deepseekCredentialRef : accepted.credentialRef);
|
|
169
185
|
}, children: t("discard") })
|
|
170
186
|
] })
|
|
171
187
|
] }),
|
|
172
|
-
|
|
188
|
+
independent && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "dsh-search-secret", children: [
|
|
189
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: t("credentials") }),
|
|
190
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "dsh-search-hint", children: [
|
|
191
|
+
t("credentialTarget"),
|
|
192
|
+
": ",
|
|
193
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: ref })
|
|
194
|
+
] }),
|
|
173
195
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
|
|
174
196
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("key") }),
|
|
175
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
autoComplete: "new-password",
|
|
180
|
-
value: keyText,
|
|
181
|
-
disabled: !credentialWritable,
|
|
182
|
-
onChange: (event) => {
|
|
183
|
-
setKey({ ref: values.credentialRef, text: event.target.value });
|
|
184
|
-
setKeyMessage(null);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
)
|
|
197
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { name: "key", type: "password", autoComplete: "new-password", value: keyText, disabled: !credentialWritable, onChange: (event) => {
|
|
198
|
+
setKey({ ref, text: event.target.value });
|
|
199
|
+
setKeyMessage(null);
|
|
200
|
+
} })
|
|
188
201
|
] }),
|
|
189
202
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dsh-search-hint", children: t("keyHint") }),
|
|
190
203
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "status", children: t(!validRef ? "invalidRef" : credentialCurrent && credential.error ? "keyFailed" : !info ? "keyLoading" : info.configured ? "keySet" : "keyUnset") }),
|
|
@@ -197,7 +210,7 @@ function SearchConfig(props) {
|
|
|
197
210
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: !credentialWritable || !info?.configured, onClick: () => {
|
|
198
211
|
void writeKey(true);
|
|
199
212
|
}, children: t("keyRemove") }),
|
|
200
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: !validRef || keyBusy, onClick: () => props.describeCredential(
|
|
213
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: !validRef || keyBusy, onClick: () => props.describeCredential(ref), children: t("keyRefresh") })
|
|
201
214
|
] })
|
|
202
215
|
] })
|
|
203
216
|
] });
|
|
@@ -207,6 +220,24 @@ function SearchConfig(props) {
|
|
|
207
220
|
var en = {
|
|
208
221
|
title: "Web search",
|
|
209
222
|
description: "Give agents access to information from the web to inform their analysis and decisions.",
|
|
223
|
+
protocol: "Search protocol",
|
|
224
|
+
codexAlpha: "Codex Alpha",
|
|
225
|
+
deepseekSearchApi: "DeepSeek Search API",
|
|
226
|
+
searchSettings: "Search service",
|
|
227
|
+
searchLimits: "Alpha search limits",
|
|
228
|
+
fetchSettings: "Web fetch",
|
|
229
|
+
credentials: "Credential management",
|
|
230
|
+
credentialTarget: "Selected credential reference",
|
|
231
|
+
deepseekEndpoint: "DeepSeek API base URL",
|
|
232
|
+
deepseekCredentialRef: "DeepSeek credential reference",
|
|
233
|
+
deepseekCount: "Search results (1\u201350)",
|
|
234
|
+
deepseekHint: "Uses the official DeepSeek Search API adapter. The API base URL defaults to https://api.deepseek.com/v1/tools; the adapter appends /search. Alpha timeout and response limits do not apply to this adapter.",
|
|
235
|
+
fetchAllowedCidrs: "Additional allowed CIDR ranges (one per line)",
|
|
236
|
+
fetchTimeoutMs: "Fetch timeout (ms)",
|
|
237
|
+
fetchMaxResponseBytes: "Fetch maximum response bytes",
|
|
238
|
+
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. The shortcut only updates this draft; save configuration to apply it.",
|
|
240
|
+
cidrPreset: "Add 198.18.0.0/15 and 192.168.96.0/20 to draft",
|
|
210
241
|
authMode: "Authentication",
|
|
211
242
|
openai: "Reuse OpenAI provider (default)",
|
|
212
243
|
credential: "Independent credential",
|
|
@@ -240,11 +271,29 @@ var en = {
|
|
|
240
271
|
keySaved: "Credential updated. Environment or other source layers may still supply a value after removal.",
|
|
241
272
|
keyFailed: "Credential operation failed. Check the credential provider and retry.",
|
|
242
273
|
invalidRef: "Use an environment-style reference name (letters, digits, underscore; not starting with a digit).",
|
|
243
|
-
invalid: "Use positive integer limits. Reuse requires a provider route; independent authentication
|
|
274
|
+
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."
|
|
244
275
|
};
|
|
245
276
|
var zh = {
|
|
246
277
|
title: "\u8054\u7F51\u641C\u7D22",
|
|
247
278
|
description: "\u5C06\u4E92\u8054\u7F51\u4F5C\u4E3A\u667A\u80FD\u4F53\u7684\u4FE1\u606F\u6765\u6E90\uFF0C\u4E3A\u5206\u6790\u4E0E\u5224\u65AD\u8865\u5145\u5916\u90E8\u4F9D\u636E\u3002",
|
|
279
|
+
protocol: "\u641C\u7D22\u534F\u8BAE",
|
|
280
|
+
codexAlpha: "Codex Alpha",
|
|
281
|
+
deepseekSearchApi: "DeepSeek Search API",
|
|
282
|
+
searchSettings: "\u641C\u7D22\u670D\u52A1",
|
|
283
|
+
searchLimits: "Alpha \u641C\u7D22\u9650\u5236",
|
|
284
|
+
fetchSettings: "\u7F51\u9875\u6293\u53D6",
|
|
285
|
+
credentials: "\u51ED\u636E\u7BA1\u7406",
|
|
286
|
+
credentialTarget: "\u5F53\u524D\u51ED\u636E\u5F15\u7528",
|
|
287
|
+
deepseekEndpoint: "DeepSeek API \u57FA\u7840\u5730\u5740",
|
|
288
|
+
deepseekCredentialRef: "DeepSeek \u51ED\u636E\u5F15\u7528\u540D",
|
|
289
|
+
deepseekCount: "\u641C\u7D22\u7ED3\u679C\u6570\u91CF\uFF081\u201350\uFF09",
|
|
290
|
+
deepseekHint: "\u4F7F\u7528\u5B98\u65B9 DeepSeek Search API \u9002\u914D\u5668\u3002API \u57FA\u7840\u5730\u5740\u9ED8\u8BA4\u4E3A https://api.deepseek.com/v1/tools\uFF0C\u9002\u914D\u5668\u81EA\u52A8\u8FFD\u52A0 /search\u3002Alpha \u8D85\u65F6\u4E0E\u54CD\u5E94\u9650\u5236\u4E0D\u9002\u7528\u4E8E\u6B64\u9002\u914D\u5668\u3002",
|
|
291
|
+
fetchAllowedCidrs: "\u989D\u5916\u653E\u884C CIDR \u7F51\u6BB5\uFF08\u6BCF\u884C\u4E00\u4E2A\uFF09",
|
|
292
|
+
fetchTimeoutMs: "\u6293\u53D6\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",
|
|
293
|
+
fetchMaxResponseBytes: "\u6293\u53D6\u54CD\u5E94\u5B57\u8282\u4E0A\u9650",
|
|
294
|
+
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\u5FEB\u6377\u6309\u94AE\u4EC5\u4FEE\u6539\u8349\u7A3F\uFF0C\u4FDD\u5B58\u914D\u7F6E\u540E\u751F\u6548\u3002",
|
|
296
|
+
cidrPreset: "\u5C06 198.18.0.0/15 \u548C 192.168.96.0/20 \u52A0\u5165\u8349\u7A3F",
|
|
248
297
|
authMode: "\u8BA4\u8BC1\u65B9\u5F0F",
|
|
249
298
|
openai: "\u590D\u7528 OpenAI Provider\uFF08\u9ED8\u8BA4\uFF09",
|
|
250
299
|
credential: "\u72EC\u7ACB\u51ED\u636E",
|
|
@@ -278,11 +327,11 @@ var zh = {
|
|
|
278
327
|
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",
|
|
279
328
|
keyFailed: "\u51ED\u636E\u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u51ED\u636E\u63D0\u4F9B\u65B9\u540E\u91CD\u8BD5\u3002",
|
|
280
329
|
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",
|
|
281
|
-
invalid: "\u8BF7\u586B\u5199\u6B63\u6574\u6570\u4E0A\u9650\u3002\u590D\u7528\u6A21\u5F0F\u9700\u8981 Provider \u8DEF\u7531\uFF1B\u72EC\u7ACB\u8BA4\u8BC1\u9700\u8981\u6709\u6548\u51ED\u636E\u5F15\u7528\u540D\u4E0E\u5B8C\u6574 HTTP(S) \
|
|
330
|
+
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"
|
|
282
331
|
};
|
|
283
332
|
|
|
284
333
|
// src/client/style.css
|
|
285
|
-
var style_default = ".dsh-search-config { display: flex; flex-direction: column; gap: 16px; color: var(--dsw-alias-label-primary); }\n.dsh-search-config fieldset { border: 0; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr)); gap: 16px; }\n.dsh-search-config label { display: flex; flex-direction: column; gap: 8px; font-size: 14px; }\n.dsh-search-config input, .dsh-search-config select { box-sizing: border-box; width: 100%; min-height: 38px; padding: 8px 10px; border-radius: 8px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-1); color: inherit; font: inherit; }\n.dsh-search-config button { min-height: 36px; padding: 8px 12px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px; background: var(--dsw-alias-button-elevated-fill); color: inherit; cursor: pointer; font: inherit; }\n.dsh-search-config :disabled { opacity: .55; cursor: not-allowed; }\n.dsh-search-config :focus-visible { outline: 2px solid var(--dsw-alias-link); outline-offset: 2px; }\n.dsh-search-hint { margin: 0; color: var(--dsw-alias-label-tertiary); font-size: 13px; line-height: 1.6; }\n.dsh-search-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; }\n.dsh-search-secret { border-top: 1px solid var(--dsw-alias-border-l2); padding-top: 20px; }\n.dsh-search-secret .dsh-search-hint { margin-top: 8px; }\n";
|
|
334
|
+
var style_default = ".dsh-search-config { display: flex; flex-direction: column; gap: 16px; color: var(--dsw-alias-label-primary); }\n.dsh-search-config fieldset { border: 0; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr)); gap: 16px; }\n.dsh-search-config label { display: flex; flex-direction: column; gap: 8px; font-size: 14px; }\n.dsh-search-config input, .dsh-search-config select, .dsh-search-config textarea { box-sizing: border-box; width: 100%; min-height: 38px; padding: 8px 10px; border-radius: 8px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-1); color: inherit; font: inherit; }\n.dsh-search-config button { min-height: 36px; padding: 8px 12px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px; background: var(--dsw-alias-button-elevated-fill); color: inherit; cursor: pointer; font: inherit; }\n.dsh-search-config :disabled { opacity: .55; cursor: not-allowed; }\n.dsh-search-config :focus-visible { outline: 2px solid var(--dsw-alias-link); outline-offset: 2px; }\n.dsh-search-hint { margin: 0; color: var(--dsw-alias-label-tertiary); font-size: 13px; line-height: 1.6; }\n.dsh-search-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; }\n.dsh-search-secret { border-top: 1px solid var(--dsw-alias-border-l2); padding-top: 20px; }\n.dsh-search-secret .dsh-search-hint { margin-top: 8px; }\n.dsh-search-config fieldset { margin-bottom: 16px; }\n.dsh-search-config legend { padding: 16px 0; font-weight: 600; }\n.dsh-search-config textarea { resize: vertical; }\n.dsh-search-wide { grid-column: 1 / -1; }\n.dsh-search-secret h3 { margin: 0 0 12px; font-size: 15px; }\n";
|
|
286
335
|
|
|
287
336
|
// src/client/index.ts
|
|
288
337
|
var inject = ["slots", "locale", "remote", "remote.credentials", "configForms"];
|
|
@@ -319,7 +368,11 @@ function apply(ctx) {
|
|
|
319
368
|
const form = ctx.configForms.get("dsh-search-plugin");
|
|
320
369
|
const refresh = () => {
|
|
321
370
|
const value = form.getSnapshot().value;
|
|
322
|
-
if (value)
|
|
371
|
+
if (value) {
|
|
372
|
+
const deepseek = value.protocol === "deepseek-search-api";
|
|
373
|
+
const ref = deepseek ? value.deepseekCredentialRef : value.credentialRef;
|
|
374
|
+
describeCredential(typeof ref === "string" ? ref : deepseek ? "DEEPSEEK_API_KEY" : "DSH_CODEX_API_KEY");
|
|
375
|
+
}
|
|
323
376
|
};
|
|
324
377
|
ctx.effect(() => form.subscribe(refresh));
|
|
325
378
|
refresh();
|
package/lib/connection.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { credentialRef } from '@deepseek-ai/dsh-credentials';
|
|
2
2
|
import { WebError } from '@deepseek-ai/dsh-web';
|
|
3
|
-
import {
|
|
3
|
+
import { searchProtocols } from './protocols.js';
|
|
4
4
|
function fail(message) {
|
|
5
5
|
throw new WebError(`Web search: ${message}`, 'CODEX_CONFIGURATION_ERROR');
|
|
6
6
|
}
|
|
@@ -79,8 +79,8 @@ export class ConfiguredSearchProvider {
|
|
|
79
79
|
}
|
|
80
80
|
async search(request, signal) {
|
|
81
81
|
signal?.throwIfAborted();
|
|
82
|
-
const
|
|
82
|
+
const provider = await searchProtocols[this.config.protocol.get()](this.ctx, this.config, () => this.resolve());
|
|
83
83
|
signal?.throwIfAborted();
|
|
84
|
-
return
|
|
84
|
+
return provider.search(request, signal);
|
|
85
85
|
}
|
|
86
86
|
}
|
package/lib/fetch.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { lookup } from 'node:dns/promises';
|
|
2
|
+
import { isIP } from 'node:net';
|
|
3
|
+
import ipaddr from 'ipaddr.js';
|
|
4
|
+
import { WebError } from '@deepseek-ai/dsh-web';
|
|
5
|
+
import { HttpFetchProvider } from '@deepseek-ai/dsh-web-fetch-http';
|
|
6
|
+
const bareHost = (host) => host.startsWith('[') && host.endsWith(']') ? host.slice(1, -1) : host;
|
|
7
|
+
function parseCidr(rule) {
|
|
8
|
+
const [address, prefixText, ...extra] = rule.trim().split('/');
|
|
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
|
+
}
|
|
12
|
+
const bytes = ipaddr.parse(address).toByteArray();
|
|
13
|
+
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
|
+
return { bytes, prefix };
|
|
24
|
+
}
|
|
25
|
+
/** Validate explicit IPv4/IPv6 network CIDRs; no implicit private ranges are added. */
|
|
26
|
+
export function validateCidrs(cidrs) {
|
|
27
|
+
try {
|
|
28
|
+
cidrs.forEach(parseCidr);
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function matches(bytes, cidr) {
|
|
36
|
+
return bytes.length === cidr.bytes.length && bytes.every((byte, i) => {
|
|
37
|
+
const bits = Math.max(0, Math.min(8, cidr.prefix - i * 8));
|
|
38
|
+
return (byte >>> (8 - bits)) === (cidr.bytes[i] >>> (8 - bits));
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function isEmbeddedV4(bytes) {
|
|
42
|
+
return bytes.length === 16 && bytes.slice(0, 10).every(b => b === 0)
|
|
43
|
+
&& ((bytes[10] === 255 && bytes[11] === 255) || (bytes[10] === 0 && bytes[11] === 0));
|
|
44
|
+
}
|
|
45
|
+
function allows(address, cidrs) {
|
|
46
|
+
let ip = ipaddr.parse(address);
|
|
47
|
+
const bytes = ip.toByteArray();
|
|
48
|
+
if (isEmbeddedV4(bytes) && ip.range() !== 'loopback' && ip.range() !== 'unspecified') {
|
|
49
|
+
ip = ipaddr.parse(bytes.slice(12).join('.'));
|
|
50
|
+
}
|
|
51
|
+
return ip.range() === 'unicast' || cidrs.some(cidr => matches(ip.toByteArray(), cidr));
|
|
52
|
+
}
|
|
53
|
+
function blocked() {
|
|
54
|
+
throw new WebError('Destination is outside the configured CIDR allowlist', 'WEB_BLOCKED_URL');
|
|
55
|
+
}
|
|
56
|
+
/** Stop waiting for an OS DNS lookup when the official transport deadline expires. */
|
|
57
|
+
function lookupAnswers(host, signal, resolve) {
|
|
58
|
+
signal.throwIfAborted();
|
|
59
|
+
return new Promise((accept, reject) => {
|
|
60
|
+
const abort = () => reject(signal.reason);
|
|
61
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
62
|
+
resolve(host, { all: true, order: 'verbatim' }).then(answers => {
|
|
63
|
+
if (answers.some(a => (a.family !== 4 && a.family !== 6) || isIP(a.address) !== a.family)) {
|
|
64
|
+
reject(new WebError('DNS returned an invalid IP address', 'WEB_PROVIDER_ERROR'));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
accept(answers);
|
|
68
|
+
}
|
|
69
|
+
}, reject).finally(() => signal.removeEventListener('abort', abort));
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
const NAT64_LENGTHS = [32, 40, 48, 56, 64, 96];
|
|
73
|
+
function embeddedV4(bytes, length) {
|
|
74
|
+
if (length === 96)
|
|
75
|
+
return bytes.slice(12).join('.');
|
|
76
|
+
if (bytes[8] !== 0)
|
|
77
|
+
return undefined;
|
|
78
|
+
const start = length / 8;
|
|
79
|
+
return [...bytes.slice(start, 8), ...bytes.slice(9, 9 + 4 - (8 - start))].join('.');
|
|
80
|
+
}
|
|
81
|
+
/** Validate the entire DNS answer set once; the official transport pins these addresses. */
|
|
82
|
+
export async function resolveAllowedAddresses(hostname, signal, allowedCidrs, resolve = lookup) {
|
|
83
|
+
signal.throwIfAborted();
|
|
84
|
+
const cidrs = allowedCidrs.map(parseCidr);
|
|
85
|
+
const host = bareHost(hostname);
|
|
86
|
+
const family = isIP(host);
|
|
87
|
+
const answers = family ? [{ address: host, family: family }] : await lookupAnswers(host, signal, resolve);
|
|
88
|
+
if (!answers.length)
|
|
89
|
+
throw new WebError('DNS returned no addresses', 'WEB_PROVIDER_ERROR');
|
|
90
|
+
for (const answer of answers)
|
|
91
|
+
if (!allows(answer.address, cidrs))
|
|
92
|
+
blocked();
|
|
93
|
+
// RFC 7050: even a public-looking DNS64 prefix can embed a private IPv4 target.
|
|
94
|
+
if (answers.some(a => a.family === 6)) {
|
|
95
|
+
const discovery = await lookupAnswers('ipv4only.arpa', signal, resolve).catch((error) => {
|
|
96
|
+
// No DNS64 answer is normal on IPv4-only resolvers. Other failures stay visible.
|
|
97
|
+
if (!signal.aborted && (error.code === 'ENODATA' || error.code === 'ENOTFOUND'))
|
|
98
|
+
return [];
|
|
99
|
+
throw error;
|
|
100
|
+
});
|
|
101
|
+
for (const candidate of discovery.filter(a => a.family === 6)) {
|
|
102
|
+
const bytes = ipaddr.parse(candidate.address).toByteArray();
|
|
103
|
+
for (const length of NAT64_LENGTHS) {
|
|
104
|
+
if (!['192.0.0.170', '192.0.0.171'].includes(embeddedV4(bytes, length) ?? ''))
|
|
105
|
+
continue;
|
|
106
|
+
for (const answer of answers.filter(a => a.family === 6)) {
|
|
107
|
+
const target = ipaddr.parse(answer.address).toByteArray();
|
|
108
|
+
if (!bytes.slice(0, length / 8).every((b, i) => b === target[i]))
|
|
109
|
+
continue;
|
|
110
|
+
const translated = embeddedV4(target, length);
|
|
111
|
+
if (translated !== undefined && !allows(translated, cidrs))
|
|
112
|
+
blocked();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
signal.throwIfAborted();
|
|
118
|
+
return answers;
|
|
119
|
+
}
|
|
120
|
+
/** Current settings are snapshotted once for each request, including redirect hops. */
|
|
121
|
+
export class ConfiguredFetchProvider {
|
|
122
|
+
read;
|
|
123
|
+
id = 'search-http';
|
|
124
|
+
constructor(read) {
|
|
125
|
+
this.read = read;
|
|
126
|
+
}
|
|
127
|
+
available() { return true; }
|
|
128
|
+
async fetch(request, signal) {
|
|
129
|
+
if (signal?.aborted)
|
|
130
|
+
throw new WebError('web fetch aborted', 'WEB_ABORTED');
|
|
131
|
+
const current = this.read();
|
|
132
|
+
const settings = { ...current, allowedCidrs: [...current.allowedCidrs] };
|
|
133
|
+
const cidrs = settings.allowedCidrs.map(parseCidr);
|
|
134
|
+
const url = URL.parse(request.url);
|
|
135
|
+
// The official proxy path skips resolution for public literals. Check compatible
|
|
136
|
+
// IPv6 too, which its classifier otherwise treats as public (e.g. ::7f00:1).
|
|
137
|
+
if (url && ['http:', 'https:'].includes(url.protocol) && !url.username && !url.password) {
|
|
138
|
+
const host = bareHost(url.hostname);
|
|
139
|
+
if (isIP(host) && !allows(host, cidrs))
|
|
140
|
+
blocked();
|
|
141
|
+
}
|
|
142
|
+
return new HttpFetchProvider({ ...settings, maxBodyChars: settings.maxContentChars }, (host, abort) => resolveAllowedAddresses(host, abort, settings.allowedCidrs)).fetch(request, signal);
|
|
143
|
+
}
|
|
144
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import Schema from '@deepseek-ai/schemastery';
|
|
2
2
|
import { ConfiguredSearchProvider } from './connection.js';
|
|
3
|
+
import { ConfiguredFetchProvider, validateCidrs } from './fetch.js';
|
|
3
4
|
export const name = 'dsh-search-plugin';
|
|
4
5
|
export const inject = ['web'];
|
|
5
6
|
export const Config = Schema.object({
|
|
7
|
+
protocol: Schema.union(['codex-alpha', 'deepseek-search-api']).default('codex-alpha').volatile(),
|
|
8
|
+
deepseekEndpoint: Schema.string().pattern(/^https?:\/\/[^\s]+$/).default('https://api.deepseek.com/v1/tools').volatile(),
|
|
9
|
+
deepseekCredentialRef: Schema.string().pattern(/^[A-Za-z_][A-Za-z0-9_]*$/).default('DEEPSEEK_API_KEY').volatile(),
|
|
10
|
+
deepseekCount: Schema.number().step(1).min(1).max(50).default(10).volatile(),
|
|
11
|
+
fetchAllowedCidrs: Schema.transform(Schema.array(Schema.string()), value => {
|
|
12
|
+
if (!validateCidrs(value))
|
|
13
|
+
throw new Error('Use valid IPv4 or IPv6 network CIDRs');
|
|
14
|
+
return value;
|
|
15
|
+
}, true).default([]).volatile(),
|
|
16
|
+
fetchTimeoutMs: Schema.number().step(1).min(1).default(30000).volatile(),
|
|
17
|
+
fetchMaxResponseBytes: Schema.number().step(1).min(1).default(5000000).volatile(),
|
|
18
|
+
fetchMaxContentChars: Schema.number().step(1).min(1).default(100000).volatile(),
|
|
6
19
|
authMode: Schema.union(['openai', 'credential']).default('openai').volatile(),
|
|
7
20
|
openaiProvider: Schema.string().min(1).default('openai').volatile(),
|
|
8
21
|
credentialRef: Schema.string().pattern(/^[A-Za-z_][A-Za-z0-9_]*$/).default('DSH_CODEX_API_KEY').volatile(),
|
|
@@ -15,6 +28,11 @@ export const Config = Schema.object({
|
|
|
15
28
|
/** Register the provider; Cordis owns its registration and optional settings policy. */
|
|
16
29
|
export function apply(ctx, config) {
|
|
17
30
|
ctx.web.registerSearchProvider(new ConfiguredSearchProvider(ctx, config));
|
|
31
|
+
ctx.web.registerFetchProvider(new ConfiguredFetchProvider(() => ({
|
|
32
|
+
allowedCidrs: [...config.fetchAllowedCidrs.get()], timeoutMs: config.fetchTimeoutMs.get(),
|
|
33
|
+
maxResponseBytes: config.fetchMaxResponseBytes.get(), maxContentChars: config.fetchMaxContentChars.get(),
|
|
34
|
+
maxRedirects: 5, userAgent: 'dsh-search-plugin',
|
|
35
|
+
})));
|
|
18
36
|
ctx.inject(['settings'], child => {
|
|
19
37
|
child.effect(() => child.settings.configure({ auto: false }, ctx.fiber));
|
|
20
38
|
});
|
package/lib/protocols.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { credentialRef } from '@deepseek-ai/dsh-credentials';
|
|
2
|
+
import { DeepSeekSearchApiProvider } from '@deepseek-ai/dsh-web-deepseek-api';
|
|
3
|
+
import { CodexSearchProvider } from './provider.js';
|
|
4
|
+
export const searchProtocols = {
|
|
5
|
+
'codex-alpha': async (_ctx, _config, alpha) => new CodexSearchProvider(await alpha()),
|
|
6
|
+
'deepseek-search-api': async (ctx, config) => {
|
|
7
|
+
const baseURL = config.deepseekEndpoint.get();
|
|
8
|
+
const count = config.deepseekCount.get();
|
|
9
|
+
const ref = credentialRef(config.deepseekCredentialRef.get());
|
|
10
|
+
return new DeepSeekSearchApiProvider(() => ({
|
|
11
|
+
baseURL, count, apiKeyEnv: ref,
|
|
12
|
+
resolveApiKey: async () => (await ctx.get('credentials')?.resolve(ref))?.value,
|
|
13
|
+
}));
|
|
14
|
+
},
|
|
15
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Context } from '@deepseek-ai/cordis';
|
|
2
2
|
import { type WebSearchProvider, type WebSearchRequest, type WebSearchResult } from '@deepseek-ai/dsh-web';
|
|
3
3
|
import type { Config } from './index.js';
|
|
4
|
-
import {
|
|
4
|
+
import type { AlphaSearchConfig } from './provider.js';
|
|
5
5
|
/** Resolve configuration and credentials for each search, without retaining secrets between operations. */
|
|
6
6
|
export declare class ConfiguredSearchProvider implements WebSearchProvider {
|
|
7
7
|
private readonly ctx;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { lookup } from 'node:dns/promises';
|
|
2
|
+
import { type WebFetchProvider, type WebFetchRequest } from '@deepseek-ai/dsh-web';
|
|
3
|
+
import { type HttpFetchResolver } from '@deepseek-ai/dsh-web-fetch-http';
|
|
4
|
+
export interface FetchSettings {
|
|
5
|
+
allowedCidrs: readonly string[];
|
|
6
|
+
timeoutMs: number;
|
|
7
|
+
maxResponseBytes: number;
|
|
8
|
+
maxContentChars: number;
|
|
9
|
+
maxRedirects: number;
|
|
10
|
+
userAgent: string;
|
|
11
|
+
}
|
|
12
|
+
/** Validate explicit IPv4/IPv6 network CIDRs; no implicit private ranges are added. */
|
|
13
|
+
export declare function validateCidrs(cidrs: readonly string[]): boolean;
|
|
14
|
+
/** Validate the entire DNS answer set once; the official transport pins these addresses. */
|
|
15
|
+
export declare function resolveAllowedAddresses(hostname: string, signal: AbortSignal, allowedCidrs: readonly string[], resolve?: typeof lookup): ReturnType<HttpFetchResolver>;
|
|
16
|
+
/** Current settings are snapshotted once for each request, including redirect hops. */
|
|
17
|
+
export declare class ConfiguredFetchProvider implements WebFetchProvider {
|
|
18
|
+
private readonly read;
|
|
19
|
+
readonly id = "search-http";
|
|
20
|
+
constructor(read: () => FetchSettings);
|
|
21
|
+
available(): boolean;
|
|
22
|
+
fetch(request: WebFetchRequest, signal?: AbortSignal): Promise<import("@deepseek-ai/dsh-web").WebFetchResult>;
|
|
23
|
+
}
|
package/lib/types/index.d.ts
CHANGED
|
@@ -4,6 +4,14 @@ export declare const name = "dsh-search-plugin";
|
|
|
4
4
|
export declare const inject: string[];
|
|
5
5
|
/** Live search settings. Credentials are resolved by reference for each operation. */
|
|
6
6
|
export interface Config {
|
|
7
|
+
protocol: Volatile<'codex-alpha' | 'deepseek-search-api'>;
|
|
8
|
+
deepseekEndpoint: Volatile<string>;
|
|
9
|
+
deepseekCredentialRef: Volatile<string>;
|
|
10
|
+
deepseekCount: Volatile<number>;
|
|
11
|
+
fetchAllowedCidrs: Volatile<string[]>;
|
|
12
|
+
fetchTimeoutMs: Volatile<number>;
|
|
13
|
+
fetchMaxResponseBytes: Volatile<number>;
|
|
14
|
+
fetchMaxContentChars: Volatile<number>;
|
|
7
15
|
authMode: Volatile<'openai' | 'credential'>;
|
|
8
16
|
openaiProvider: Volatile<string>;
|
|
9
17
|
credentialRef: Volatile<string>;
|
|
@@ -14,6 +22,14 @@ export interface Config {
|
|
|
14
22
|
maxAnswerChars: Volatile<number>;
|
|
15
23
|
}
|
|
16
24
|
export declare const Config: Schema<Schemastery.ObjectS<NoInfer<{
|
|
25
|
+
protocol: Schema<"codex-alpha" | "deepseek-search-api", "codex-alpha" | "deepseek-search-api", "volatile-defined">;
|
|
26
|
+
deepseekEndpoint: Schema<string, string, "volatile-defined">;
|
|
27
|
+
deepseekCredentialRef: Schema<string, string, "volatile-defined">;
|
|
28
|
+
deepseekCount: Schema<number, number, "volatile-defined">;
|
|
29
|
+
fetchAllowedCidrs: Schema<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
|
|
30
|
+
fetchTimeoutMs: Schema<number, number, "volatile-defined">;
|
|
31
|
+
fetchMaxResponseBytes: Schema<number, number, "volatile-defined">;
|
|
32
|
+
fetchMaxContentChars: Schema<number, number, "volatile-defined">;
|
|
17
33
|
authMode: Schema<"openai" | "credential", "openai" | "credential", "volatile-defined">;
|
|
18
34
|
openaiProvider: Schema<string, string, "volatile-defined">;
|
|
19
35
|
credentialRef: Schema<string, string, "volatile-defined">;
|
|
@@ -23,6 +39,14 @@ export declare const Config: Schema<Schemastery.ObjectS<NoInfer<{
|
|
|
23
39
|
maxResponseBytes: Schema<number, number, "volatile-defined">;
|
|
24
40
|
maxAnswerChars: Schema<number, number, "volatile-defined">;
|
|
25
41
|
}>>, Schemastery.ObjectT<NoInfer<{
|
|
42
|
+
protocol: Schema<"codex-alpha" | "deepseek-search-api", "codex-alpha" | "deepseek-search-api", "volatile-defined">;
|
|
43
|
+
deepseekEndpoint: Schema<string, string, "volatile-defined">;
|
|
44
|
+
deepseekCredentialRef: Schema<string, string, "volatile-defined">;
|
|
45
|
+
deepseekCount: Schema<number, number, "volatile-defined">;
|
|
46
|
+
fetchAllowedCidrs: Schema<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
|
|
47
|
+
fetchTimeoutMs: Schema<number, number, "volatile-defined">;
|
|
48
|
+
fetchMaxResponseBytes: Schema<number, number, "volatile-defined">;
|
|
49
|
+
fetchMaxContentChars: Schema<number, number, "volatile-defined">;
|
|
26
50
|
authMode: Schema<"openai" | "credential", "openai" | "credential", "volatile-defined">;
|
|
27
51
|
openaiProvider: Schema<string, string, "volatile-defined">;
|
|
28
52
|
credentialRef: Schema<string, string, "volatile-defined">;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Protocol factories are the extension point; each captures one operation's settings. */
|
|
2
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
3
|
+
import type { WebSearchProvider } from '@deepseek-ai/dsh-web';
|
|
4
|
+
import type { Config } from './index.js';
|
|
5
|
+
import { type AlphaSearchConfig } from './provider.js';
|
|
6
|
+
export type SearchProtocol = 'codex-alpha' | 'deepseek-search-api';
|
|
7
|
+
type Factory = (ctx: Context, config: Config, alpha: () => Promise<AlphaSearchConfig>) => Promise<WebSearchProvider>;
|
|
8
|
+
export declare const searchProtocols: Record<SearchProtocol, Factory>;
|
|
9
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-search-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/urzz/dsh-search-plugin.git"
|
|
@@ -16,10 +16,12 @@
|
|
|
16
16
|
},
|
|
17
17
|
"./client": "./lib/client.js",
|
|
18
18
|
"./package.json": "./package.json",
|
|
19
|
-
"./locale/*.json": "./locale/*.json"
|
|
19
|
+
"./locale/*.json": "./locale/*.json",
|
|
20
|
+
"./icon": "./icon.svg"
|
|
20
21
|
},
|
|
21
22
|
"files": [
|
|
22
23
|
"lib",
|
|
24
|
+
"icon.svg",
|
|
23
25
|
"locale/*.json",
|
|
24
26
|
"cordis.patch.yml",
|
|
25
27
|
"README.md"
|
|
@@ -50,12 +52,15 @@
|
|
|
50
52
|
"prepack": "pnpm build"
|
|
51
53
|
},
|
|
52
54
|
"dependencies": {
|
|
53
|
-
"@deepseek-ai/
|
|
55
|
+
"@deepseek-ai/dsh-web-deepseek-api": "0.2.1-rc.4",
|
|
56
|
+
"@deepseek-ai/schemastery": "~3.18.4",
|
|
57
|
+
"ipaddr.js": "^2.5.0"
|
|
54
58
|
},
|
|
55
59
|
"peerDependencies": {
|
|
56
60
|
"@deepseek-ai/cordis": "^4.0.0",
|
|
57
61
|
"@deepseek-ai/dsh-credentials": "0.2.1-rc.4",
|
|
58
|
-
"@deepseek-ai/dsh-web": "0.2.1-rc.4"
|
|
62
|
+
"@deepseek-ai/dsh-web": "0.2.1-rc.4",
|
|
63
|
+
"@deepseek-ai/dsh-web-fetch-http": "0.2.1-rc.4"
|
|
59
64
|
},
|
|
60
65
|
"devDependencies": {
|
|
61
66
|
"@deepseek-ai/cordis": "^4.0.0",
|