@dsh-plus/web-search-services 0.1.19 → 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/lib/client.js +1203 -178
- package/lib/index.d.ts +10 -83
- package/lib/index.js +16 -649
- package/package.json +15 -15
- package/src/client/card.tsx +130 -72
- package/src/client/client.ts +19 -7
- package/src/client/draft.ts +29 -89
- package/src/client/i18n.ts +109 -52
- package/src/client/styles.ts +10 -7
- package/src/config.ts +24 -74
- package/src/index.ts +29 -14
- package/scripts/README.md +0 -34
- package/scripts/context7.py +0 -259
- package/scripts/env_loader.py +0 -46
- package/scripts/exa.py +0 -251
- package/scripts/fetch.py +0 -938
- package/scripts/fetch_playwright.py +0 -525
- package/scripts/openai_chat.py +0 -570
- package/scripts/search.py +0 -962
- package/scripts/tavily.py +0 -609
- package/src/args.ts +0 -30
- package/src/env-file.ts +0 -49
- package/src/normalize.ts +0 -173
- package/src/provider.ts +0 -188
- package/src/python.ts +0 -193
- package/src/runner.ts +0 -140
package/src/client/i18n.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 配置卡片文案(zh/en
|
|
2
|
+
* 配置卡片文案(zh/en):经 ctx.locale.register 注册、bind 取用。
|
|
3
3
|
* 公共键(save/discard/unsaved 等)来自 shared 的 common 字典,本文件只维护业务键。
|
|
4
4
|
* @module web-search-services/client/i18n
|
|
5
5
|
*/
|
|
@@ -10,65 +10,122 @@ export const NS = 'dsh-plus-web-search-services'
|
|
|
10
10
|
const ownZh = {
|
|
11
11
|
title: '搜索后端聚合',
|
|
12
12
|
description:
|
|
13
|
-
'把
|
|
14
|
-
summaryLine: 'web_search
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
'
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
13
|
+
'把 web_search 接到多个搜索后端:按条目顺序尝试,失败自动回退到下一个;官方工具、渲染与 Sources 格式零修改。',
|
|
14
|
+
summaryLine: 'web_search 多后端聚合:顺序尝试 + 失败自动回退(密钥走官方 credentials)。',
|
|
15
|
+
enabled: '启用本插件',
|
|
16
|
+
enabledHint: '关闭后 web_search 不再选用本插件注册的 provider;官方工具与提示词不变。',
|
|
17
|
+
exposeEntries: '暴露每条目 provider',
|
|
18
|
+
exposeEntriesHint:
|
|
19
|
+
'额外注册 dsh-plus-search-<条目名>,便于在 profile 里用 searchProvider 钉住单个后端;改动条目列表后需 /reload 生效。',
|
|
20
|
+
proxy: 'HTTP(S) 代理',
|
|
21
|
+
proxyHint:
|
|
22
|
+
'全部后端共用(Tavily/Exa/OpenAI/Context7 的基址都走这里);留空 = 读环境变量 HTTP_PROXY/HTTPS_PROXY/NO_PROXY。',
|
|
23
|
+
entriesTitle: '后端链',
|
|
24
|
+
entriesHint:
|
|
25
|
+
'顺序即优先级:靠前的先试,失败自动回退到下一个。条目内的密钥引用在下方「密钥」小节填写。',
|
|
26
|
+
addEntry: '新增条目',
|
|
27
|
+
entryId: '条目名',
|
|
28
|
+
entryIdHint: '链内标识,同时是 provider id 的后缀(dsh-plus-search-<条目名>),需唯一。',
|
|
29
|
+
entryEnabled: '参与链',
|
|
30
|
+
moveUp: '上移',
|
|
31
|
+
moveDown: '下移',
|
|
32
|
+
remove: '删除',
|
|
33
|
+
type: '类型',
|
|
34
|
+
unsetOption: '(不设置)',
|
|
35
|
+
csvHint: '多项用逗号分隔,留空表示不限。',
|
|
36
|
+
librariesHint:
|
|
37
|
+
'JSON 数组:[{"name":"react","id":"/facebook/react","keywords":["react"]}](id 可留空,运行时按名称解析)。',
|
|
38
|
+
headersHint: 'JSON 对象:{"Accept-Language":"zh-CN"}。',
|
|
39
|
+
entriesEmpty: '至少保留一个条目,否则本 provider 不可用。',
|
|
40
|
+
unknownType: '未知条目类型(来自更早版本的配置),保存前请删除或改类型。',
|
|
41
|
+
issueIdEmpty: '条目名不能为空。',
|
|
42
|
+
issueRequired: '必填:留空会让该条目永不可用。',
|
|
43
|
+
issueIdDuplicate: '条目名重复。',
|
|
44
|
+
issueTypeUnknown: '未知条目类型。',
|
|
45
|
+
issueInt: '需为整数。',
|
|
46
|
+
issueBelowMin: '低于允许的最小值(见后端 schema)。',
|
|
47
|
+
issueAboveMax: '高于允许的最大值(见后端 schema)。',
|
|
48
|
+
issueOption: '取值不在候选内。',
|
|
49
|
+
issueJson: '需为合法 JSON。',
|
|
50
|
+
issueJsonArray: '需为 JSON 数组。',
|
|
51
|
+
issueJsonObject: '需为 JSON 对象。',
|
|
52
|
+
issueJsonStrings: '值需为字符串。',
|
|
53
|
+
issueLibraryShape: '每项需含 name 字段(可选 id 与 keywords)。',
|
|
54
|
+
secretsTitle: '密钥',
|
|
55
|
+
secretsBody:
|
|
56
|
+
'值经官方 credentials 接口写入 $DSH_HOME/.credentials.yaml,卡片不回显;引用名由上面的条目决定。也可用环境变量提供同名值。',
|
|
57
|
+
secretConfigured: '已配置',
|
|
58
|
+
secretMissing: '未配置',
|
|
59
|
+
secretShadowed: '该引用被进程环境(或只读来源)提供,卡片不能改写。',
|
|
60
|
+
secretFailed: '凭据写入失败:',
|
|
61
|
+
secretClear: '清除',
|
|
62
|
+
reloadHint:
|
|
63
|
+
'条目增删后需 /reload 才会重新注册 provider;超时、密钥、顺序等字段改动下一次调用即生效。',
|
|
35
64
|
} as const
|
|
36
65
|
|
|
37
66
|
const ownEn = {
|
|
38
67
|
title: 'Search backend aggregation',
|
|
39
68
|
description:
|
|
40
|
-
'
|
|
69
|
+
'Routes web_search to several search backends: entries are tried in order with automatic fallback. Official tool, rendering and Sources formatting are untouched.',
|
|
41
70
|
summaryLine:
|
|
42
|
-
'web_search backend
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
71
|
+
'web_search backend chain: ordered attempts with automatic fallback (keys via official credentials).',
|
|
72
|
+
enabled: 'Enable this plugin',
|
|
73
|
+
enabledHint:
|
|
74
|
+
'When off, web_search no longer selects the providers registered here; the official tool and prompt are unchanged.',
|
|
75
|
+
exposeEntries: 'Expose per-entry providers',
|
|
76
|
+
exposeEntriesHint:
|
|
77
|
+
'Also registers dsh-plus-search-<entry id> so a profile can pin one backend via searchProvider; changing the entry list needs /reload.',
|
|
78
|
+
proxy: 'HTTP(S) proxy',
|
|
79
|
+
proxyHint:
|
|
80
|
+
'Shared by every backend (Tavily/Exa/OpenAI/Context7 base URLs); empty reads HTTP_PROXY/HTTPS_PROXY/NO_PROXY.',
|
|
81
|
+
entriesTitle: 'Backend chain',
|
|
82
|
+
entriesHint:
|
|
83
|
+
'Order is priority: the first entry is tried first and failures fall through to the next. Credential references are edited in the Secrets section below.',
|
|
84
|
+
addEntry: 'Add entry',
|
|
85
|
+
entryId: 'Entry name',
|
|
86
|
+
entryIdHint:
|
|
87
|
+
'Identifies the entry inside the chain and suffixes the provider id (dsh-plus-search-<entry>); must be unique.',
|
|
88
|
+
entryEnabled: 'In chain',
|
|
89
|
+
moveUp: 'Move up',
|
|
90
|
+
moveDown: 'Move down',
|
|
91
|
+
remove: 'Remove',
|
|
92
|
+
type: 'Type',
|
|
93
|
+
unsetOption: '(unset)',
|
|
94
|
+
csvHint: 'Separate multiple values with commas; empty means no restriction.',
|
|
95
|
+
librariesHint:
|
|
96
|
+
'JSON array: [{"name":"react","id":"/facebook/react","keywords":["react"]}] (id optional; resolved by name at runtime).',
|
|
97
|
+
headersHint: 'JSON object: {"Accept-Language":"zh-CN"}.',
|
|
98
|
+
entriesEmpty: 'Keep at least one entry, otherwise this provider is unavailable.',
|
|
99
|
+
unknownType:
|
|
100
|
+
'Unknown entry type (from an older configuration); remove it or change its type before saving.',
|
|
101
|
+
issueIdEmpty: 'Entry name is required.',
|
|
102
|
+
issueRequired: 'Required: leaving it empty makes the entry permanently unusable.',
|
|
103
|
+
issueIdDuplicate: 'Entry name is duplicated.',
|
|
104
|
+
issueTypeUnknown: 'Unknown entry type.',
|
|
105
|
+
issueInt: 'Enter an integer.',
|
|
106
|
+
issueBelowMin: 'Below the minimum allowed by the backend schema.',
|
|
107
|
+
issueAboveMax: 'Above the maximum allowed by the backend schema.',
|
|
108
|
+
issueOption: 'Value is not one of the options.',
|
|
109
|
+
issueJson: 'Enter valid JSON.',
|
|
110
|
+
issueJsonArray: 'Enter a JSON array.',
|
|
111
|
+
issueJsonObject: 'Enter a JSON object.',
|
|
112
|
+
issueJsonStrings: 'Values must be strings.',
|
|
113
|
+
issueLibraryShape: 'Each item needs a name (optional id and keywords).',
|
|
114
|
+
secretsTitle: 'Credentials',
|
|
115
|
+
secretsBody:
|
|
116
|
+
'Values are written through the official credentials API into $DSH_HOME/.credentials.yaml and are never echoed back; references come from the entries above. Environment variables with the same name also work.',
|
|
117
|
+
secretConfigured: 'Configured',
|
|
118
|
+
secretMissing: 'Not configured',
|
|
119
|
+
secretShadowed:
|
|
120
|
+
'This reference comes from the process environment (or a read-only source); the card cannot change it.',
|
|
121
|
+
secretFailed: 'Credential write failed: ',
|
|
122
|
+
secretClear: 'Clear',
|
|
63
123
|
reloadHint:
|
|
64
|
-
'
|
|
124
|
+
'Adding or removing entries needs /reload to re-register providers; timeouts, keys and order take effect on the next call.',
|
|
65
125
|
} as const
|
|
66
126
|
|
|
67
|
-
export
|
|
127
|
+
export const zh = mergeDict(commonZh, ownZh)
|
|
128
|
+
export const en: Record<keyof typeof zh, string> = mergeDict(commonEn, ownEn)
|
|
68
129
|
|
|
69
|
-
|
|
130
|
+
export type DictKey = keyof typeof zh
|
|
70
131
|
export type Translate = (key: DictKey) => string
|
|
71
|
-
|
|
72
|
-
// 标注为 Record<DictKey, string>:en 缺任一键即编译期报错(中英强制对齐)。
|
|
73
|
-
export const zh: Record<DictKey, string> = mergeDict(commonZh, ownZh)
|
|
74
|
-
export const en: Record<DictKey, string> = mergeDict(commonEn, ownEn)
|
package/src/client/styles.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 配置卡片样式:基础规则走 @dsh-plus/shared/client 的 cardCss('wsv'),
|
|
3
|
-
*
|
|
3
|
+
* 本文件只补插件特有规则(条目块、JSON 文本域、条目操作按钮组)。
|
|
4
4
|
* 沿用官方 data-plugin / data-plugin-css 约定(HMR 据此卸载)。
|
|
5
5
|
* @module web-search-services/client/styles
|
|
6
6
|
*/
|
|
@@ -9,13 +9,16 @@ import { cardCss, injectCardStyle } from '@dsh-plus/shared/client'
|
|
|
9
9
|
export const PLUGIN_ID = '@dsh-plus/web-search-services'
|
|
10
10
|
|
|
11
11
|
const extraCss = `
|
|
12
|
-
.wsv-
|
|
13
|
-
.wsv-
|
|
14
|
-
.wsv-
|
|
15
|
-
.wsv-
|
|
16
|
-
.wsv-
|
|
12
|
+
.wsv-entry{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;margin:12px 0 0;padding:4px 12px 12px}
|
|
13
|
+
.wsv-entryHead{align-items:center;gap:8px;display:flex;flex-wrap:wrap;padding:12px 0 4px}
|
|
14
|
+
.wsv-entryIndex{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
|
|
15
|
+
.wsv-entryActions{margin-left:auto;gap:6px;display:flex;flex-wrap:wrap}
|
|
16
|
+
.wsv-json{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:8px 12px;font-size:13px;line-height:1.6;resize:vertical;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
|
|
17
|
+
.wsv-json:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}
|
|
18
|
+
.wsv-json:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}
|
|
17
19
|
@media (max-width:767px){
|
|
18
|
-
.wsv-
|
|
20
|
+
.wsv-json{font-size:16px}
|
|
21
|
+
.wsv-entryActions{width:100%;margin-left:0}
|
|
19
22
|
}
|
|
20
23
|
`
|
|
21
24
|
|
package/src/config.ts
CHANGED
|
@@ -1,98 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 配置单一事实源:cordis 行级 Config(组合默认值,patch 层可覆盖)与 settings
|
|
3
|
-
* namespace(用户层=profile 行级覆盖层 cordis.patch.yml
|
|
2
|
+
* 配置单一事实源:cordis 行级 Config(组合默认值,profile patch 层可覆盖)与 settings
|
|
3
|
+
* namespace(用户层=profile 行级覆盖层 cordis.patch.yml,热生效)共用同一份
|
|
4
|
+
* schemastery schema。
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* 条目 schema 来自 `@dsh-plus/web-providers-core`(与后端实现同源,改后端不动插件);
|
|
7
|
+
* 密钥全部走官方 credentials seam(`$DSH_HOME/.credentials.yaml`),**不进 settings**
|
|
8
|
+
* ——卡片只展示引用名,值经 `remote.credentials` 写入。
|
|
8
9
|
* @module web-search-services/config
|
|
9
10
|
*/
|
|
11
|
+
|
|
10
12
|
import z from '@deepseek-ai/schemastery'
|
|
11
13
|
import type { UnwrapVolatile } from '@dsh-plus/shared'
|
|
14
|
+
import { DEFAULT_SEARCH_ENTRIES, SearchEntrySchema } from '@dsh-plus/web-providers-core'
|
|
12
15
|
|
|
13
16
|
import { SETTINGS_NS as NS_LITERAL } from './ns.ts'
|
|
14
17
|
|
|
15
18
|
export const SETTINGS_NS = NS_LITERAL
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
export const DEFAULT_ENV_FILE = '~/.config/search-services/env'
|
|
20
|
-
export const DEFAULT_TIMEOUT_MS = 55_000
|
|
21
|
-
export const DEFAULT_PRIORITY: readonly SearchBackend[] = ['tavily', 'exa', 'openai-chat']
|
|
22
|
-
|
|
23
|
-
export type SearchBackend = 'tavily' | 'exa' | 'openai-chat'
|
|
24
|
-
|
|
25
|
-
const PRIORITY_ENTRY = z.union(['tavily', 'exa', 'openai-chat'])
|
|
26
|
-
|
|
27
|
-
const KeysSchema = z.object({
|
|
28
|
-
tavily: z
|
|
29
|
-
.string()
|
|
30
|
-
.role('secret')
|
|
31
|
-
.description('Tavily API key(覆盖 envFile 的 TAVILY_API_KEY;行级注入专用)')
|
|
32
|
-
.default(''),
|
|
33
|
-
exa: z
|
|
34
|
-
.string()
|
|
35
|
-
.role('secret')
|
|
36
|
-
.description('Exa API key(覆盖 envFile 的 EXA_API_KEY;行级注入专用)')
|
|
37
|
-
.default(''),
|
|
38
|
-
openaiApiKey: z
|
|
39
|
-
.string()
|
|
40
|
-
.role('secret')
|
|
41
|
-
.description('OpenAI-compatible API key(覆盖 SEARCH_OPENAI_API_KEY;行级注入专用)')
|
|
42
|
-
.default(''),
|
|
43
|
-
openaiBaseUrl: z
|
|
44
|
-
.string()
|
|
45
|
-
.description('OpenAI-compatible base URL(覆盖 SEARCH_OPENAI_BASE_URL)')
|
|
46
|
-
.default(''),
|
|
47
|
-
openaiModel: z
|
|
48
|
-
.string()
|
|
49
|
-
.description('OpenAI-compatible 搜索模型名(覆盖 SEARCH_OPENAI_MODEL)')
|
|
50
|
-
.default(''),
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
// 全字段 `.volatile()`——条目进入 settings describe 视图(卡片可读写;
|
|
54
|
-
// keys 内 role('secret') 字段由 describe/write 层做密文遮蔽,用户层勿填),
|
|
55
|
-
// loader 原位提交活动引用,current() 现取即热。
|
|
20
|
+
// 顶层字段全部 .volatile():进入 settings describe 视图(卡片可读写),
|
|
21
|
+
// loader 原位提交活动引用,消费端 unwrapVolatile 现取即热(改配置不用重启)。
|
|
56
22
|
export const Config = z.object({
|
|
57
|
-
|
|
58
|
-
.
|
|
59
|
-
.description('
|
|
60
|
-
.default(
|
|
23
|
+
enabled: z
|
|
24
|
+
.boolean()
|
|
25
|
+
.description('总开关:关闭后 web_search 不再选用本 provider(官方工具与提示词不变)')
|
|
26
|
+
.default(true)
|
|
61
27
|
.volatile(),
|
|
62
|
-
|
|
63
|
-
.
|
|
64
|
-
.description('
|
|
65
|
-
.default(
|
|
28
|
+
exposeEntries: z
|
|
29
|
+
.boolean()
|
|
30
|
+
.description('是否额外注册每条目 provider(dsh-plus-search-<条目名>),便于按 id 钉住单个后端')
|
|
31
|
+
.default(false)
|
|
66
32
|
.volatile(),
|
|
67
|
-
|
|
33
|
+
proxy: z
|
|
68
34
|
.string()
|
|
69
|
-
.description(
|
|
70
|
-
'python 解释器(可含参数,如 `py -3`);空串 = 自动发现(配置外按 桌面捆绑运行时 → PATH → 平台兜底名 解析)',
|
|
71
|
-
)
|
|
35
|
+
.description('全部后端共用的 HTTP(S) 代理;空 = 读环境变量 HTTP_PROXY/HTTPS_PROXY/NO_PROXY')
|
|
72
36
|
.default('')
|
|
73
37
|
.volatile(),
|
|
74
|
-
|
|
75
|
-
.array(
|
|
76
|
-
.description('
|
|
77
|
-
.default(
|
|
78
|
-
.volatile(),
|
|
79
|
-
timeoutMs: z
|
|
80
|
-
.natural()
|
|
81
|
-
.min(1000)
|
|
82
|
-
.description('单次搜索子进程超时(毫秒),应低于 dsh-tool-web 的 searchTimeoutMs')
|
|
83
|
-
.default(DEFAULT_TIMEOUT_MS)
|
|
38
|
+
entries: z
|
|
39
|
+
.array(SearchEntrySchema)
|
|
40
|
+
.description('后端链:按顺序尝试,失败自动回退到下一个(条目内 schema 见卡片)')
|
|
41
|
+
.default(DEFAULT_SEARCH_ENTRIES)
|
|
84
42
|
.volatile(),
|
|
85
|
-
keys: KeysSchema.default({
|
|
86
|
-
tavily: '',
|
|
87
|
-
exa: '',
|
|
88
|
-
openaiApiKey: '',
|
|
89
|
-
openaiBaseUrl: '',
|
|
90
|
-
openaiModel: '',
|
|
91
|
-
}).volatile(),
|
|
92
43
|
})
|
|
93
44
|
|
|
94
45
|
/** 活动字段形态(loader 解析产物:volatile 字段为活动引用)。 */
|
|
95
46
|
export type WebSearchServicesConfigFields = Schemastery.TypeT<typeof Config>
|
|
96
47
|
/** 平面配置形态(消费面的读取形态,由活动引用解包得到)。 */
|
|
97
48
|
export type WebSearchServicesConfig = UnwrapVolatile<WebSearchServicesConfigFields>
|
|
98
|
-
export type KeysConfig = WebSearchServicesConfig['keys']
|
package/src/index.ts
CHANGED
|
@@ -1,37 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* dsh-plus 插件:web_search
|
|
2
|
+
* dsh-plus 插件:web_search 多后端聚合。
|
|
3
3
|
*
|
|
4
|
-
* 经官方 ctx.web provider
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* 经官方 `ctx.web` provider 扩展点注册搜索后端:官方 `web_search` 工具、渲染层、
|
|
5
|
+
* 提示词与 `Sources:` 格式零修改,模型可见输出与官方 provider 逐字节一致。后端链、
|
|
6
|
+
* 回退与各供应商细节(Tavily 多 key 轮换、Exa、OpenAI 兼容、Context7)在
|
|
7
|
+
* `@dsh-plus/web-providers-core`;本插件只做「读配置 → 建 provider → 注册」。
|
|
8
|
+
* DeepSeek 官方 Search/Fetch API 由官方 `@deepseek-ai/dsh-web-deepseek-api` 提供,
|
|
9
|
+
* 本包不重复实现(provider id:deepseek-search-api / deepseek-fetch-api)。
|
|
8
10
|
* @module @dsh-plus/web-search-services
|
|
9
11
|
*/
|
|
12
|
+
|
|
10
13
|
import type { Context } from '@deepseek-ai/cordis'
|
|
11
14
|
import type {} from '@deepseek-ai/dsh-settings'
|
|
12
15
|
import type {} from '@deepseek-ai/dsh-web'
|
|
13
16
|
import { unwrapVolatile } from '@dsh-plus/shared'
|
|
17
|
+
import {
|
|
18
|
+
createBackendHost,
|
|
19
|
+
probeCredentialsFace,
|
|
20
|
+
searchProvidersOf,
|
|
21
|
+
} from '@dsh-plus/web-providers-core'
|
|
14
22
|
|
|
15
23
|
import type { WebSearchServicesConfig, WebSearchServicesConfigFields } from './config.ts'
|
|
16
|
-
import { SearchServicesProvider } from './provider.ts'
|
|
17
24
|
|
|
18
25
|
export const name = 'dsh-plus-web-search-services'
|
|
19
26
|
|
|
20
27
|
/** 行级 inject:dsh-web 服务激活后才应用本行(provider 注册即需 ctx.web)。 */
|
|
21
28
|
export const inject = ['web'] as const
|
|
22
29
|
|
|
30
|
+
export {
|
|
31
|
+
SEARCH_ENTRY_PROVIDER_PREFIX as ENTRY_PROVIDER_PREFIX,
|
|
32
|
+
SEARCH_PROVIDER_ID as PROVIDER_ID,
|
|
33
|
+
type SearchEntry,
|
|
34
|
+
searchEntryAvailable,
|
|
35
|
+
} from '@dsh-plus/web-providers-core'
|
|
23
36
|
export { Config, SETTINGS_NS } from './config.ts'
|
|
24
|
-
export { PROVIDER_ID, SearchServicesProvider } from './provider.ts'
|
|
25
37
|
|
|
26
|
-
// config 运行期为 loader 解析的 volatile 活动字段(用户层 override 并入行级
|
|
27
|
-
// config,写入原位提交)——每次读现取平面快照,替代已删除的 installSection/setSource。
|
|
28
38
|
export function apply(
|
|
29
39
|
ctx: Context,
|
|
30
40
|
config: WebSearchServicesConfig | WebSearchServicesConfigFields,
|
|
31
41
|
): void {
|
|
32
|
-
const
|
|
33
|
-
ctx
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
)
|
|
42
|
+
const current = (): WebSearchServicesConfig => unwrapVolatile(config)
|
|
43
|
+
const host = createBackendHost({ credentials: probeCredentialsFace(ctx) })
|
|
44
|
+
ctx.effect(() => () => host.dispose(), 'dsh-plus-web-search-services: transport pool')
|
|
45
|
+
// 注册集在 apply 时定型(条目列表增删需 /reload);字段改动每次调用现读,即时生效。
|
|
46
|
+
for (const provider of searchProvidersOf(current, host)) {
|
|
47
|
+
ctx.effect(
|
|
48
|
+
() => ctx.web.registerSearchProvider(provider),
|
|
49
|
+
`dsh-plus-web-search-services: register ${provider.id}`,
|
|
50
|
+
)
|
|
51
|
+
}
|
|
37
52
|
}
|
package/scripts/README.md
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# scripts/:search-services skill Python 脚本的项目维护副本
|
|
2
|
-
|
|
3
|
-
本目录是从 `~/.dsh/skills/search-services/scripts/` **原样复制**的单向副本
|
|
4
|
-
(标准库实现,无第三方依赖),供 `@dsh-plus/web-search-services` 插件经
|
|
5
|
-
`python3 search.py search ...` 子进程桥接调用。**skill 本体不受本仓维护影响。**
|
|
6
|
-
|
|
7
|
-
## 为什么是 Python 桥接(特例声明)
|
|
8
|
-
|
|
9
|
-
> **本插件是特例,不是先例。** dsh-plus 集成外部 HTTP 服务(provider、工具、
|
|
10
|
-
> webhook 等)**默认直接用 TS 原生实现**(进程内 fetch、编译期契约、无进程边界)。
|
|
11
|
-
> 仅当需求明确要求复用一套已存在、持续维护、且逻辑非平凡的外部编排
|
|
12
|
-
> (本例:search-services skill 的链内 fallback、`TAVILY_API_KEYS` 多 key
|
|
13
|
-
> 轮转、intent 服务链、per-service 参数细节)时,才允许整体桥接其脚本,
|
|
14
|
-
> 把外部实现当作单一事实源,本仓只维护「dsh-web 契约 ↔ CLI」薄适配层。
|
|
15
|
-
|
|
16
|
-
判定标准(新增集成时自检):
|
|
17
|
-
|
|
18
|
-
1. 外部逻辑是否已在维护中且有独立演进(skill/CLI 工具)?——否则 TS 原生重写更便宜。
|
|
19
|
-
2. 桥接节省的是否是**编排逻辑**而非一个 POST 请求?——单接口直调一律 TS 原生。
|
|
20
|
-
3. 失败域、超时、密钥注入是否都能在薄适配层兜住?——兜不住则不桥接。
|
|
21
|
-
|
|
22
|
-
## 同步策略
|
|
23
|
-
|
|
24
|
-
- **单向**:skill → 本目录。skill 升级后人工比对同步(diff 后整体覆盖),
|
|
25
|
-
不做自动同步、不做双向回写;本目录的定向修改(如有)须注释 `dsh-plus:`
|
|
26
|
-
标记并记录在下表。
|
|
27
|
-
- 同步时复核文件清单:`search.py fetch.py fetch_playwright.py env_loader.py
|
|
28
|
-
openai_chat.py context7.py exa.py tavily.py`(`__pycache__` 不入仓)。
|
|
29
|
-
|
|
30
|
-
## 本仓定向修改记录
|
|
31
|
-
|
|
32
|
-
| 日期 | 文件 | 修改 | 原因 |
|
|
33
|
-
|---|---|---|---|
|
|
34
|
-
| — | — | 暂无(verbatim 副本) | — |
|