dsh-search-enhance 0.1.2 → 0.1.4
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 +4 -0
- package/client/client.js +74 -5
- package/client/types/client/locales.d.ts +3 -1
- package/client/types/web-config/contracts.d.ts +12 -1
- package/lib/config.d.ts +2 -0
- package/lib/config.js +8 -6
- package/lib/documentation/service.d.ts +2 -2
- package/lib/documentation/service.js +8 -5
- package/lib/index.js +5 -3
- package/lib/providers/context7.js +24 -8
- package/lib/search/parse.js +4 -4
- package/lib/tools/context7.d.ts +1 -1
- package/lib/tools/docs-search.d.ts +1 -1
- package/lib/tools/search-sources.d.ts +1 -1
- package/lib/tools/web-extract.d.ts +1 -1
- package/lib/tools/web-map.d.ts +1 -1
- package/lib/tools/web-search.d.ts +1 -1
- package/lib/web-config/contracts.d.ts +12 -1
- package/lib/web-config/contracts.js +15 -0
- package/lib/web-config/host.js +8 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -142,6 +142,8 @@ dsh web
|
|
|
142
142
|
|
|
143
143
|
在“补充 Provider”中填写服务地址,在“凭据”中填写对应密钥。没有配置的服务会被跳过,不影响已经配置好的搜索来源。
|
|
144
144
|
|
|
145
|
+
Tavily 和 Firecrawl 参与普通搜索的数量由“每次搜索的补充来源数量”控制,默认值为 `0`,不会自动发起补充搜索。需要时按搜索类型显式设置非零数量;只配置了其中一个服务时,该服务获得全部数量。
|
|
146
|
+
|
|
145
147
|
### 3. 配置搜索方式
|
|
146
148
|
|
|
147
149
|
页面可以设置默认搜索类型和搜索深度:
|
|
@@ -152,6 +154,8 @@ dsh web
|
|
|
152
154
|
|
|
153
155
|
不确定时保持默认值即可,也可以在单次搜索中临时选择其他设置。
|
|
154
156
|
|
|
157
|
+
同一页面还可以设置“每次搜索的补充来源数量”,逐个搜索类型控制 Tavily/Firecrawl 的补充数量。
|
|
158
|
+
|
|
155
159
|
“工具披露模式”建议保持 `progressive`,让延迟 operation 按需披露并从下一模型 step 激活;`all` 只让所有延迟 operation 从一开始处于 active 状态。两种模式都保留同一组五个模型工具及相同 schema,不会显示额外的独立工具。
|
|
156
160
|
|
|
157
161
|
### 4. 配置网页代理
|
package/client/client.js
CHANGED
|
@@ -20,11 +20,26 @@ window.__ModuleLoader__.load({
|
|
|
20
20
|
"tavily",
|
|
21
21
|
"firecrawl"
|
|
22
22
|
];
|
|
23
|
+
/**
|
|
24
|
+
* Search profiles that carry an editable supplementary discovery budget. The
|
|
25
|
+
* list is spelled here rather than imported: this contract is also the browser
|
|
26
|
+
* half, and a client module must not depend on the Host configuration module.
|
|
27
|
+
* The Host contract test keeps this list aligned with the configuration schema.
|
|
28
|
+
*/
|
|
29
|
+
const WEB_EXTRA_DISCOVERY_PROFILES = [
|
|
30
|
+
"auto",
|
|
31
|
+
"coding_docs",
|
|
32
|
+
"code_examples",
|
|
33
|
+
"project_research",
|
|
34
|
+
"academic",
|
|
35
|
+
"fact_check"
|
|
36
|
+
];
|
|
23
37
|
const WEB_EDITABLE_PATHS = [
|
|
24
38
|
["defaultProfile"],
|
|
25
39
|
["defaultDepth"],
|
|
26
40
|
["toolTimeoutMs"],
|
|
27
41
|
["toolDiscovery", "mode"],
|
|
42
|
+
...WEB_EXTRA_DISCOVERY_PROFILES.map((profile) => ["extraDiscoverySources", profile]),
|
|
28
43
|
["searchApi", "baseUrl"],
|
|
29
44
|
["searchApi", "protocol"],
|
|
30
45
|
["searchApi", "model"],
|
|
@@ -401,6 +416,7 @@ window.__ModuleLoader__.load({
|
|
|
401
416
|
defaultDepth: config.defaultDepth,
|
|
402
417
|
toolTimeoutMs: config.toolTimeoutMs,
|
|
403
418
|
toolDiscovery: { ...config.toolDiscovery },
|
|
419
|
+
extraDiscoverySources: { ...config.extraDiscoverySources },
|
|
404
420
|
searchApi: { ...config.searchApi },
|
|
405
421
|
providers: {
|
|
406
422
|
context7: { ...config.providers.context7 },
|
|
@@ -450,6 +466,12 @@ window.__ModuleLoader__.load({
|
|
|
450
466
|
...PROVIDERS.map((provider) => config.providers[provider].timeoutMs)
|
|
451
467
|
].every(positiveInteger);
|
|
452
468
|
}
|
|
469
|
+
function validExtraDiscoverySources(config, maximum) {
|
|
470
|
+
return WEB_EXTRA_DISCOVERY_PROFILES.every((profile) => {
|
|
471
|
+
const value = config.extraDiscoverySources[profile];
|
|
472
|
+
return Number.isInteger(value) && value >= 0 && value <= maximum;
|
|
473
|
+
});
|
|
474
|
+
}
|
|
453
475
|
function credentialRef(config, slot) {
|
|
454
476
|
return slot === "searchApi" ? config.searchApi.credentialRef : config.providers[slot].credentialRef;
|
|
455
477
|
}
|
|
@@ -489,12 +511,13 @@ window.__ModuleLoader__.load({
|
|
|
489
511
|
}, option))
|
|
490
512
|
});
|
|
491
513
|
}
|
|
492
|
-
function NumberInput({ value, onChange, disabled, label }) {
|
|
514
|
+
function NumberInput({ value, onChange, disabled, label, min = 1, max }) {
|
|
493
515
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
494
516
|
"aria-label": label,
|
|
495
517
|
style: controlStyle,
|
|
496
518
|
type: "number",
|
|
497
|
-
min
|
|
519
|
+
min,
|
|
520
|
+
max,
|
|
498
521
|
step: 1,
|
|
499
522
|
value: Number.isFinite(value) ? value : "",
|
|
500
523
|
disabled,
|
|
@@ -617,7 +640,7 @@ window.__ModuleLoader__.load({
|
|
|
617
640
|
]);
|
|
618
641
|
const mutations = (0, react.useMemo)(() => snapshot === void 0 || draft === void 0 ? [] : mutationsFor(snapshot.value, draft), [draft, snapshot]);
|
|
619
642
|
const dirty = mutations.length > 0;
|
|
620
|
-
const valid = draft !== void 0 && validTimeouts(draft);
|
|
643
|
+
const valid = draft !== void 0 && snapshot !== void 0 && validTimeouts(draft) && validExtraDiscoverySources(draft, snapshot.options.extraDiscoveryMaxSources);
|
|
621
644
|
const editable = phase === "ready" && snapshot?.writable === true && !saving;
|
|
622
645
|
const updateProvider = (provider, value) => {
|
|
623
646
|
setDraft((current) => current === void 0 ? current : {
|
|
@@ -629,6 +652,16 @@ window.__ModuleLoader__.load({
|
|
|
629
652
|
});
|
|
630
653
|
setFeedback("idle");
|
|
631
654
|
};
|
|
655
|
+
const updateExtraDiscovery = (profile, value) => {
|
|
656
|
+
setDraft((current) => current === void 0 ? current : {
|
|
657
|
+
...current,
|
|
658
|
+
extraDiscoverySources: {
|
|
659
|
+
...current.extraDiscoverySources,
|
|
660
|
+
[profile]: value
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
setFeedback("idle");
|
|
664
|
+
};
|
|
632
665
|
const updateProxy = (route, proxyUrl) => {
|
|
633
666
|
setDraft((current) => current === void 0 ? current : {
|
|
634
667
|
...current,
|
|
@@ -1007,6 +1040,38 @@ window.__ModuleLoader__.load({
|
|
|
1007
1040
|
]
|
|
1008
1041
|
})]
|
|
1009
1042
|
}),
|
|
1043
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1044
|
+
style: sectionDividerStyle,
|
|
1045
|
+
"aria-labelledby": "search-enhance-extra-sources-heading",
|
|
1046
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1047
|
+
id: "search-enhance-extra-sources-heading",
|
|
1048
|
+
style: headingStyle,
|
|
1049
|
+
children: t("extraSourcesHeading")
|
|
1050
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1051
|
+
style: {
|
|
1052
|
+
...bodyStyle,
|
|
1053
|
+
marginTop: 4
|
|
1054
|
+
},
|
|
1055
|
+
children: t("extraSourcesIntro")
|
|
1056
|
+
})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1057
|
+
style: gridStyle,
|
|
1058
|
+
children: WEB_EXTRA_DISCOVERY_PROFILES.map((profile) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
1059
|
+
label: profile,
|
|
1060
|
+
overridden: isOverridden(snapshot.user, ["extraDiscoverySources", profile]),
|
|
1061
|
+
t,
|
|
1062
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumberInput, {
|
|
1063
|
+
label: `${t("extraSourcesHeading")} ${profile}`,
|
|
1064
|
+
value: draft.extraDiscoverySources[profile],
|
|
1065
|
+
min: 0,
|
|
1066
|
+
max: snapshot.options.extraDiscoveryMaxSources,
|
|
1067
|
+
disabled: !editable,
|
|
1068
|
+
onChange: (value) => {
|
|
1069
|
+
updateExtraDiscovery(profile, value);
|
|
1070
|
+
}
|
|
1071
|
+
})
|
|
1072
|
+
}, profile))
|
|
1073
|
+
})]
|
|
1074
|
+
}),
|
|
1010
1075
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1011
1076
|
style: sectionDividerStyle,
|
|
1012
1077
|
"aria-labelledby": "search-enhance-providers-heading",
|
|
@@ -1324,6 +1389,8 @@ window.__ModuleLoader__.load({
|
|
|
1324
1389
|
defaultDepth: "Default depth",
|
|
1325
1390
|
toolDiscoveryMode: "Tool discovery",
|
|
1326
1391
|
toolTimeoutMs: "Whole tool timeout (ms)",
|
|
1392
|
+
extraSourcesHeading: "Supplementary sources per search",
|
|
1393
|
+
extraSourcesIntro: "How many extra Tavily/Firecrawl sources one search may collect for each profile. 0 disables supplementary search for that profile; an unconfigured provider is skipped without a request.",
|
|
1327
1394
|
providersHeading: "Supplemental providers",
|
|
1328
1395
|
providersIntro: "Endpoints and timeouts for documentation, fallback discovery, extraction, and site mapping.",
|
|
1329
1396
|
providersSummary: "Advanced provider settings",
|
|
@@ -1369,7 +1436,7 @@ window.__ModuleLoader__.load({
|
|
|
1369
1436
|
conflict: "The Settings document changed elsewhere. Reload before saving again.",
|
|
1370
1437
|
saveFailed: "The configuration could not be saved.",
|
|
1371
1438
|
readOnly: "The Settings document is read-only.",
|
|
1372
|
-
invalidNumber: "Timeouts must be positive whole numbers.",
|
|
1439
|
+
invalidNumber: "Timeouts must be positive whole numbers, and supplementary source counts must be whole numbers within their allowed range.",
|
|
1373
1440
|
overridden: "User override",
|
|
1374
1441
|
inherited: "Inherited"
|
|
1375
1442
|
};
|
|
@@ -1397,6 +1464,8 @@ window.__ModuleLoader__.load({
|
|
|
1397
1464
|
defaultDepth: "默认深度",
|
|
1398
1465
|
toolDiscoveryMode: "工具披露模式",
|
|
1399
1466
|
toolTimeoutMs: "工具总超时(毫秒)",
|
|
1467
|
+
extraSourcesHeading: "每次搜索的补充来源数量",
|
|
1468
|
+
extraSourcesIntro: "每个 profile 单次搜索最多补充多少条 Tavily/Firecrawl 来源。填 0 表示该 profile 不做补充搜索;未配置密钥的 Provider 会直接跳过、不发请求。",
|
|
1400
1469
|
providersHeading: "补充 Provider",
|
|
1401
1470
|
providersIntro: "配置文档检索、补充发现、网页提取与站点映射所用的端点和超时。",
|
|
1402
1471
|
providersSummary: "高级 Provider 设置",
|
|
@@ -1442,7 +1511,7 @@ window.__ModuleLoader__.load({
|
|
|
1442
1511
|
conflict: "Settings 已在其他位置发生变化。请重新读取后再保存。",
|
|
1443
1512
|
saveFailed: "无法保存配置。",
|
|
1444
1513
|
readOnly: "Settings 文档为只读。",
|
|
1445
|
-
invalidNumber: "
|
|
1514
|
+
invalidNumber: "超时必须是正整数;补充来源数量必须是允许范围内的整数。",
|
|
1446
1515
|
overridden: "用户覆盖",
|
|
1447
1516
|
inherited: "继承值"
|
|
1448
1517
|
};
|
|
@@ -22,6 +22,8 @@ export declare const en: {
|
|
|
22
22
|
readonly defaultDepth: "Default depth";
|
|
23
23
|
readonly toolDiscoveryMode: "Tool discovery";
|
|
24
24
|
readonly toolTimeoutMs: "Whole tool timeout (ms)";
|
|
25
|
+
readonly extraSourcesHeading: "Supplementary sources per search";
|
|
26
|
+
readonly extraSourcesIntro: "How many extra Tavily/Firecrawl sources one search may collect for each profile. 0 disables supplementary search for that profile; an unconfigured provider is skipped without a request.";
|
|
25
27
|
readonly providersHeading: "Supplemental providers";
|
|
26
28
|
readonly providersIntro: "Endpoints and timeouts for documentation, fallback discovery, extraction, and site mapping.";
|
|
27
29
|
readonly providersSummary: "Advanced provider settings";
|
|
@@ -67,7 +69,7 @@ export declare const en: {
|
|
|
67
69
|
readonly conflict: "The Settings document changed elsewhere. Reload before saving again.";
|
|
68
70
|
readonly saveFailed: "The configuration could not be saved.";
|
|
69
71
|
readonly readOnly: "The Settings document is read-only.";
|
|
70
|
-
readonly invalidNumber: "Timeouts must be positive whole numbers.";
|
|
72
|
+
readonly invalidNumber: "Timeouts must be positive whole numbers, and supplementary source counts must be whole numbers within their allowed range.";
|
|
71
73
|
readonly overridden: "User override";
|
|
72
74
|
readonly inherited: "Inherited";
|
|
73
75
|
};
|
|
@@ -6,7 +6,16 @@ export declare const WEB_CREDENTIAL_REF_MAX_CHARACTERS = 256;
|
|
|
6
6
|
export declare const WEB_CREDENTIAL_VALUE_MAX_CHARACTERS: number;
|
|
7
7
|
export declare const WEB_CREDENTIAL_SLOTS: readonly ["searchApi", "context7", "exa", "tavily", "firecrawl"];
|
|
8
8
|
export type WebCredentialSlot = (typeof WEB_CREDENTIAL_SLOTS)[number];
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Search profiles that carry an editable supplementary discovery budget. The
|
|
11
|
+
* list is spelled here rather than imported: this contract is also the browser
|
|
12
|
+
* half, and a client module must not depend on the Host configuration module.
|
|
13
|
+
* The Host contract test keeps this list aligned with the configuration schema.
|
|
14
|
+
*/
|
|
15
|
+
export declare const WEB_EXTRA_DISCOVERY_PROFILES: readonly ["auto", "coding_docs", "code_examples", "project_research", "academic", "fact_check"];
|
|
16
|
+
export type WebExtraDiscoveryProfile = (typeof WEB_EXTRA_DISCOVERY_PROFILES)[number];
|
|
17
|
+
export type WebExtraDiscoverySources = Record<WebExtraDiscoveryProfile, number>;
|
|
18
|
+
export declare const WEB_EDITABLE_PATHS: readonly (readonly string[])[];
|
|
10
19
|
export interface WebSearchApiConfig {
|
|
11
20
|
baseUrl: string;
|
|
12
21
|
protocol: string;
|
|
@@ -30,6 +39,7 @@ export interface WebEditableConfig {
|
|
|
30
39
|
toolDiscovery: {
|
|
31
40
|
mode: string;
|
|
32
41
|
};
|
|
42
|
+
extraDiscoverySources: WebExtraDiscoverySources;
|
|
33
43
|
searchApi: WebSearchApiConfig;
|
|
34
44
|
providers: {
|
|
35
45
|
context7: WebDiscoveryProviderConfig;
|
|
@@ -56,6 +66,7 @@ export interface WebConfigOptions {
|
|
|
56
66
|
thinkingLevels: readonly string[];
|
|
57
67
|
toolDiscoveryModes: readonly string[];
|
|
58
68
|
proxyUrlMaxCharacters: number;
|
|
69
|
+
extraDiscoveryMaxSources: number;
|
|
59
70
|
}
|
|
60
71
|
export interface WebCredentialState {
|
|
61
72
|
ref: string;
|
package/lib/config.d.ts
CHANGED
|
@@ -307,6 +307,8 @@ export interface Config {
|
|
|
307
307
|
}
|
|
308
308
|
export declare const DEFAULT_CREDENTIAL_REFS: CredentialReferences;
|
|
309
309
|
export declare const DEFAULT_SEARCH_BUDGETS: SearchBudgets;
|
|
310
|
+
/** Deployment ceiling for one profile's supplementary discovery-source budget. */
|
|
311
|
+
export declare const EXTRA_DISCOVERY_SOURCES_MAX = 100;
|
|
310
312
|
export declare const SEARCH_ENHANCE_SETTINGS_NAMESPACE: import("@deepseek-ai/dsh-settings").SettingsNamespace;
|
|
311
313
|
export declare function validateWebExtractProxyUrl(value: string): string;
|
|
312
314
|
export declare const Config: Schema<Config>;
|
package/lib/config.js
CHANGED
|
@@ -77,6 +77,8 @@ export const DEFAULT_SEARCH_BUDGETS = {
|
|
|
77
77
|
deep: { maxAnswerCharacters: 22000, maxVisibleSources: 28, maxModelTextBytes: 36 * 1024 },
|
|
78
78
|
},
|
|
79
79
|
};
|
|
80
|
+
/** Deployment ceiling for one profile's supplementary discovery-source budget. */
|
|
81
|
+
export const EXTRA_DISCOVERY_SOURCES_MAX = 100;
|
|
80
82
|
export const SEARCH_ENHANCE_SETTINGS_NAMESPACE = settingsNamespace('search-enhance');
|
|
81
83
|
const httpUrl = (defaultValue) => Schema.string()
|
|
82
84
|
.pattern(/^https?:\/\/(?![^/\s?#]*@)[^\s?#]+\/?$/)
|
|
@@ -174,12 +176,12 @@ export const Config = Schema.object({
|
|
|
174
176
|
fact_check: profileBudgets('fact_check'),
|
|
175
177
|
}),
|
|
176
178
|
extraDiscoverySources: Schema.object({
|
|
177
|
-
auto: nonNegativeInteger(0,
|
|
178
|
-
coding_docs: nonNegativeInteger(0,
|
|
179
|
-
code_examples: nonNegativeInteger(0,
|
|
180
|
-
project_research: nonNegativeInteger(0,
|
|
181
|
-
academic: nonNegativeInteger(0,
|
|
182
|
-
fact_check: nonNegativeInteger(0,
|
|
179
|
+
auto: nonNegativeInteger(0, EXTRA_DISCOVERY_SOURCES_MAX),
|
|
180
|
+
coding_docs: nonNegativeInteger(0, EXTRA_DISCOVERY_SOURCES_MAX),
|
|
181
|
+
code_examples: nonNegativeInteger(0, EXTRA_DISCOVERY_SOURCES_MAX),
|
|
182
|
+
project_research: nonNegativeInteger(0, EXTRA_DISCOVERY_SOURCES_MAX),
|
|
183
|
+
academic: nonNegativeInteger(0, EXTRA_DISCOVERY_SOURCES_MAX),
|
|
184
|
+
fact_check: nonNegativeInteger(0, EXTRA_DISCOVERY_SOURCES_MAX),
|
|
183
185
|
}),
|
|
184
186
|
retention: Schema.object({
|
|
185
187
|
searchQueryMaxCharacters: positiveInteger(32_000, 1_000_000),
|
|
@@ -2,7 +2,7 @@ import { Service, type Context } from '@deepseek-ai/cordis';
|
|
|
2
2
|
import type { Config } from '../config.js';
|
|
3
3
|
import type { CanonicalSource, SourceRecordCandidate } from '../contracts/index.js';
|
|
4
4
|
import { type ProviderAttemptRecord, type ProviderErrorKind } from '../provider-runtime/index.js';
|
|
5
|
-
import { Context7RemoteClient, type Context7Library } from '../providers/context7.js';
|
|
5
|
+
import { type Context7RemoteClient, type Context7Library } from '../providers/context7.js';
|
|
6
6
|
import type { BoundedSourceProvider, DocumentationSnippet, SourceProviderSearchInput, SourceProviderSearchOutcome } from '../providers/types.js';
|
|
7
7
|
import type { Context7DocsCacheEntry, Context7DocRef } from './cache-domain.js';
|
|
8
8
|
import { type Context7CachedDocLookup, type Context7CachedDocMatch } from './cache.js';
|
|
@@ -125,7 +125,7 @@ export declare class DocumentationSearchInfrastructureError extends Error {
|
|
|
125
125
|
constructor(attempts: readonly ProviderAttemptRecord[], warnings: readonly DocumentationWarning[], cache: Readonly<DocumentationCacheReport>, cause: unknown);
|
|
126
126
|
}
|
|
127
127
|
export interface DocumentationSearchDependencies {
|
|
128
|
-
readonly context7: Context7RemoteClient
|
|
128
|
+
readonly context7: Pick<Context7RemoteClient, 'resolve' | 'docs'>;
|
|
129
129
|
readonly context7Cache: Context7CachedOperations;
|
|
130
130
|
readonly exa: BoundedSourceProvider;
|
|
131
131
|
readonly getConfig: () => Config;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Service } from '@deepseek-ai/cordis';
|
|
2
2
|
import { createProviderAttemptRecord, isAbortError, isProviderError, ProviderError, throwIfAborted, } from '../provider-runtime/index.js';
|
|
3
3
|
import { boundSourceProviderResult } from '../providers/bounded-result.js';
|
|
4
|
-
import {
|
|
4
|
+
import { context7LibrarySource, selectContext7Library, } from '../providers/context7.js';
|
|
5
5
|
import { applySourceQuality } from '../search/index.js';
|
|
6
6
|
import { CONTEXT7_CACHE_QUERY_MAX_SCAN_RECORDS, } from './cache.js';
|
|
7
7
|
import { CONTEXT7_CACHE_STATES, Context7CachedOperations, Context7OperationFailure, isContext7LibraryId, normalizeContext7LibraryId, } from './context7-cache.js';
|
|
8
|
+
const CONTEXT7_RESOLVE_CANDIDATE_LIMIT = 30;
|
|
8
9
|
export const DOCUMENTATION_SEARCH_SERVICE_KEY = 'searchEnhanceDocumentation';
|
|
9
10
|
export const DOCUMENTATION_SEARCH_PROVIDERS = ['auto', 'context7', 'exa', 'all'];
|
|
10
11
|
export const DOCUMENTATION_RESULT_PROVIDERS = ['context7', 'exa'];
|
|
@@ -347,6 +348,7 @@ export class DocumentationSearchService extends Service {
|
|
|
347
348
|
await Promise.allSettled([...this.active]);
|
|
348
349
|
}
|
|
349
350
|
async executeContext7Resolve(input) {
|
|
351
|
+
const candidateLimit = input.candidateLimit ?? input.maxResults;
|
|
350
352
|
const startedAt = safeClockValue(this.now);
|
|
351
353
|
let dispatches = 0;
|
|
352
354
|
const resolved = await this.context7Cache.resolve({
|
|
@@ -354,7 +356,7 @@ export class DocumentationSearchService extends Service {
|
|
|
354
356
|
forceRefresh: input.forceRefresh,
|
|
355
357
|
load: () => this.context7.resolve({
|
|
356
358
|
config: input.config,
|
|
357
|
-
limit:
|
|
359
|
+
limit: candidateLimit,
|
|
358
360
|
onDispatch: () => {
|
|
359
361
|
dispatches += 1;
|
|
360
362
|
input.onDispatch?.();
|
|
@@ -362,12 +364,12 @@ export class DocumentationSearchService extends Service {
|
|
|
362
364
|
query: input.libraryName,
|
|
363
365
|
signal: input.signal,
|
|
364
366
|
}),
|
|
365
|
-
maxResults:
|
|
367
|
+
maxResults: candidateLimit,
|
|
366
368
|
query: input.libraryName,
|
|
367
369
|
signal: input.signal,
|
|
368
370
|
});
|
|
369
|
-
const candidates = Object.freeze(
|
|
370
|
-
const selected = selectContext7Library(
|
|
371
|
+
const candidates = Object.freeze(resolved.entry.libraries.slice(0, input.maxResults));
|
|
372
|
+
const selected = selectContext7Library(resolved.entry.libraries, input.libraryName, input.query);
|
|
371
373
|
return Object.freeze({
|
|
372
374
|
attempts: successfulCacheAttempts('resolve', resolved, dispatches, elapsed(startedAt, this.now)),
|
|
373
375
|
cache: cachePath(resolved.cache, resolved.evictedEntries),
|
|
@@ -638,6 +640,7 @@ export class DocumentationSearchService extends Service {
|
|
|
638
640
|
forceRefresh: input.forceRefresh,
|
|
639
641
|
libraryName: input.query,
|
|
640
642
|
maxResults: input.maxResults,
|
|
643
|
+
candidateLimit: Math.min(CONTEXT7_RESOLVE_CANDIDATE_LIMIT, input.config.retention.providerMaxSources),
|
|
641
644
|
onDispatch: () => { resolveDispatches += 1; },
|
|
642
645
|
query: input.query,
|
|
643
646
|
signal: input.signal,
|
package/lib/index.js
CHANGED
|
@@ -22,8 +22,7 @@ export const name = 'search-enhance';
|
|
|
22
22
|
export const inject = ['agents', 'credentials', 'settings', 'storageDomain', 'systemPrompt', 'tools'];
|
|
23
23
|
export const Config = SearchEnhanceConfig;
|
|
24
24
|
export async function apply(ctx, config) {
|
|
25
|
-
const
|
|
26
|
-
const effective = settings.get();
|
|
25
|
+
const effective = ctx.settings.register(SEARCH_ENHANCE_SETTINGS_NAMESPACE, SearchEnhanceConfig, { applies: 'restart', base: config }).get();
|
|
27
26
|
const domain = await ctx.storageDomain.open(SOURCE_RECORD_DOMAIN_SPEC);
|
|
28
27
|
const store = new SourceRecordStore(domain.table(SOURCE_RECORD_TABLE_NAME), {
|
|
29
28
|
maxBytes: effective.retention.sourceEventMaxBytes,
|
|
@@ -34,7 +33,10 @@ export async function apply(ctx, config) {
|
|
|
34
33
|
maxSnippetCharacters: effective.retention.searchSourcesSnippetMaxCharacters,
|
|
35
34
|
});
|
|
36
35
|
const operations = new ForegroundOperationScope();
|
|
37
|
-
|
|
36
|
+
// This namespace applies on restart, so every consumer reads the one value
|
|
37
|
+
// resolved here. Reading Settings live would make some fields take effect
|
|
38
|
+
// immediately while constructor-bound limits waited for a restart.
|
|
39
|
+
const getConfig = () => effective;
|
|
38
40
|
const providerDependencies = { credentials: ctx.credentials };
|
|
39
41
|
const context7Cache = new PersistentContext7Cache(ctx.storageDomain, {
|
|
40
42
|
maxEntries: effective.cache.maxEntries,
|
|
@@ -121,17 +121,33 @@ export function parseContext7Libraries(body) {
|
|
|
121
121
|
function normalizedMatchText(value) {
|
|
122
122
|
return (value ?? '').toLowerCase().replace(/^@/, '').replace(/[^a-z0-9]+/g, '');
|
|
123
123
|
}
|
|
124
|
-
function
|
|
124
|
+
function normalizedTerms(value) {
|
|
125
|
+
return value.toLowerCase()
|
|
126
|
+
.split(/[^a-z0-9@._-]+/)
|
|
127
|
+
.map(normalizedMatchText)
|
|
128
|
+
.filter(term => term.length > 0);
|
|
129
|
+
}
|
|
130
|
+
function queryMentionsName(queryTerms, value) {
|
|
131
|
+
const nameTerms = normalizedTerms(value ?? '');
|
|
132
|
+
if (nameTerms.length === 0)
|
|
133
|
+
return false;
|
|
134
|
+
return queryTerms.some((_term, index) => nameTerms.every((nameTerm, offset) => queryTerms[index + offset] === nameTerm));
|
|
135
|
+
}
|
|
136
|
+
function context7LibraryScore(item, libraryName, queryTerms) {
|
|
125
137
|
const wanted = normalizedMatchText(libraryName);
|
|
126
138
|
const rawText = `${item.id ?? ''} ${item.title ?? ''} ${item.description ?? ''}`.toLowerCase();
|
|
127
139
|
const title = normalizedMatchText(item.title);
|
|
128
140
|
const description = normalizedMatchText(item.description);
|
|
129
141
|
const idSegments = (item.id ?? '').split('/').filter(Boolean).map(normalizedMatchText);
|
|
130
142
|
let score = 0;
|
|
131
|
-
if (
|
|
143
|
+
if (queryMentionsName(queryTerms, item.title))
|
|
144
|
+
score += 240;
|
|
145
|
+
for (const segment of (item.id ?? '').split('/').filter(Boolean)) {
|
|
146
|
+
if (queryMentionsName(queryTerms, segment))
|
|
147
|
+
score += 220;
|
|
148
|
+
}
|
|
149
|
+
if (/official.*documentation|documentation.*official|official docs/.test(rawText))
|
|
132
150
|
score += 260;
|
|
133
|
-
if (rawText.includes('react.dev'))
|
|
134
|
-
score += 120;
|
|
135
151
|
if (wanted.length > 0) {
|
|
136
152
|
if (title === wanted)
|
|
137
153
|
score += 240;
|
|
@@ -146,9 +162,8 @@ function context7LibraryScore(item, libraryName, query) {
|
|
|
146
162
|
if (description.includes(wanted))
|
|
147
163
|
score += 8;
|
|
148
164
|
}
|
|
149
|
-
for (const
|
|
150
|
-
|
|
151
|
-
if (normalizedTerm.length === 0)
|
|
165
|
+
for (const normalizedTerm of queryTerms) {
|
|
166
|
+
if (normalizedTerm.length <= 1)
|
|
152
167
|
continue;
|
|
153
168
|
if (title === normalizedTerm)
|
|
154
169
|
score += 24;
|
|
@@ -174,11 +189,12 @@ function context7LibraryScore(item, libraryName, query) {
|
|
|
174
189
|
}
|
|
175
190
|
/** Stable selection by exact name, description relevance, trust, benchmark, and snippet coverage. */
|
|
176
191
|
export function selectContext7Library(libraries, libraryName, query = libraryName) {
|
|
192
|
+
const queryTerms = normalizedTerms(query);
|
|
177
193
|
return libraries
|
|
178
194
|
.map((library, index) => ({
|
|
179
195
|
index,
|
|
180
196
|
library,
|
|
181
|
-
score: context7LibraryScore(library, libraryName,
|
|
197
|
+
score: context7LibraryScore(library, libraryName, queryTerms),
|
|
182
198
|
}))
|
|
183
199
|
.filter(candidate => isContext7LibraryId(candidate.library.id))
|
|
184
200
|
.sort((left, right) => right.score - left.score || left.index - right.index)[0]?.library;
|
package/lib/search/parse.js
CHANGED
|
@@ -29,8 +29,7 @@ export const DEFAULT_SEARCH_RESPONSE_PARSE_LIMITS = Object.freeze({
|
|
|
29
29
|
maxUrlCharacters: 8192,
|
|
30
30
|
});
|
|
31
31
|
const URL_PATTERN = /https?:\/\/[^\s<>"'`,。、;:!?》)】)]+/g;
|
|
32
|
-
const MARKDOWN_LINK_PATTERN =
|
|
33
|
-
const MARKDOWN_LINK_LINE_PATTERN = /\[[^\]]+]\(https?:\/\/[^)]+\)/;
|
|
32
|
+
const MARKDOWN_LINK_PATTERN = /(?<!\[)\[([^\[\]]+|\[\d+\])]\((https?:\/\/[^)]+)\)/g;
|
|
34
33
|
const SOURCES_HEADING_PATTERN = /(?:^|\n)(?:#{1,6}\s*)?(?:\*\*|__)?\s*(?:sources?|references?|citations?|信源|参考资料|参考|引用|来源列表|来源)(?:\s*[((][^)\n]*[))])?\s*(?:(?:\*\*|__)\s*)?[::]?\s*(?:(?:\*\*|__)\s*)?$/gim;
|
|
35
34
|
const SOURCES_FUNCTION_PATTERN = /(^|\n)\s*(sources|source|citations|citation|references|reference|citation_card|source_cards|source_card)\s*\(/gim;
|
|
36
35
|
function isRecord(value) {
|
|
@@ -152,7 +151,8 @@ export function extractMarkdownCitationUrls(text, maximumUrlCharacters = DEFAULT
|
|
|
152
151
|
}
|
|
153
152
|
function addTextSources(text, state, limits) {
|
|
154
153
|
addMarkdownSources(text, state, limits);
|
|
155
|
-
|
|
154
|
+
const bareText = text.replace(MARKDOWN_LINK_PATTERN, ' ');
|
|
155
|
+
for (const url of extractUrls(bareText))
|
|
156
156
|
addSource(state, { url }, limits);
|
|
157
157
|
}
|
|
158
158
|
function normalizeSourceItem(item, state, limits) {
|
|
@@ -336,7 +336,7 @@ function isLinkOnlyLine(line) {
|
|
|
336
336
|
const stripped = line.replace(/^\s*(?:[-*]|\d+\.)\s*/, '').trim();
|
|
337
337
|
return stripped.length > 0 && (stripped.startsWith('http://')
|
|
338
338
|
|| stripped.startsWith('https://')
|
|
339
|
-
||
|
|
339
|
+
|| stripped.search(MARKDOWN_LINK_PATTERN) !== -1);
|
|
340
340
|
}
|
|
341
341
|
function splitTailLinkBlock(text, state, limits) {
|
|
342
342
|
const lines = text.split(/\r?\n/);
|
package/lib/tools/context7.d.ts
CHANGED
|
@@ -1273,7 +1273,7 @@ export declare const CONTEXT7_GET_CACHED_DOC_RAW_OUTPUT_SCHEMA: {
|
|
|
1273
1273
|
readonly additionalProperties: false;
|
|
1274
1274
|
};
|
|
1275
1275
|
export interface Context7ToolsDependencies {
|
|
1276
|
-
/** Read
|
|
1276
|
+
/** Read the restart-scoped resolved Settings value this plugin instance was loaded with. */
|
|
1277
1277
|
readonly getConfig: () => Config;
|
|
1278
1278
|
readonly documentation: Pick<DocumentationSearchService, 'resolveContext7' | 'queryContext7Docs' | 'lookupDoc' | 'findContext7Doc'>;
|
|
1279
1279
|
readonly operations: ForegroundOperationScope;
|
|
@@ -5,7 +5,7 @@ import { type SearchEnhanceSourceService, type SourceRecordCommit } from '../sou
|
|
|
5
5
|
import type { ForegroundOperationScope } from './operations.js';
|
|
6
6
|
import { type DocsSearchOutput } from './schemas.js';
|
|
7
7
|
export interface DocsSearchToolDependencies {
|
|
8
|
-
/** Read
|
|
8
|
+
/** Read the restart-scoped resolved Settings value this plugin instance was loaded with. */
|
|
9
9
|
readonly getConfig: () => Config;
|
|
10
10
|
readonly documentation: Pick<DocumentationSearchService, 'search'>;
|
|
11
11
|
readonly operations: ForegroundOperationScope;
|
|
@@ -4,7 +4,7 @@ import { type SearchEnhanceSourceService, type SourcePageResult } from '../sourc
|
|
|
4
4
|
import type { ForegroundOperationScope } from './operations.js';
|
|
5
5
|
import { type SearchSourcesOutput } from './schemas.js';
|
|
6
6
|
export interface SearchSourcesToolDependencies {
|
|
7
|
-
/** Read
|
|
7
|
+
/** Read the restart-scoped resolved Settings value this plugin instance was loaded with. */
|
|
8
8
|
readonly getConfig: () => Config;
|
|
9
9
|
readonly operations: ForegroundOperationScope;
|
|
10
10
|
readonly sources: Pick<SearchEnhanceSourceService, 'page'>;
|
|
@@ -5,7 +5,7 @@ import { type WebExtractOrchestrator, type WebExtractResult, type WebExtractRout
|
|
|
5
5
|
import type { ForegroundOperationScope } from './operations.js';
|
|
6
6
|
import { type WebExtractOutput, type WebExtractOutputAttempt } from './schemas.js';
|
|
7
7
|
export interface WebExtractToolDependencies {
|
|
8
|
-
/** Read
|
|
8
|
+
/** Read the restart-scoped resolved Settings value this plugin instance was loaded with. */
|
|
9
9
|
readonly getConfig: () => Config;
|
|
10
10
|
readonly operations: ForegroundOperationScope;
|
|
11
11
|
readonly orchestrator: Pick<WebExtractOrchestrator, 'extract'>;
|
package/lib/tools/web-map.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { type SiteMapProvider, type TavilyMapResult } from '../site-map/index.js
|
|
|
4
4
|
import type { ForegroundOperationScope } from './operations.js';
|
|
5
5
|
import { type WebMapOutput } from './schemas.js';
|
|
6
6
|
export interface WebMapToolDependencies {
|
|
7
|
-
/** Read
|
|
7
|
+
/** Read the restart-scoped resolved Settings value this plugin instance was loaded with. */
|
|
8
8
|
readonly getConfig: () => Config;
|
|
9
9
|
readonly operations: ForegroundOperationScope;
|
|
10
10
|
readonly provider: SiteMapProvider;
|
|
@@ -6,7 +6,7 @@ import { type SearchEnhanceSourceService, type SourceRecordCommit } from '../sou
|
|
|
6
6
|
import type { ForegroundOperationScope } from './operations.js';
|
|
7
7
|
import { type WebSearchOutput } from './schemas.js';
|
|
8
8
|
export interface WebSearchToolDependencies {
|
|
9
|
-
/** Read
|
|
9
|
+
/** Read the restart-scoped resolved Settings value this plugin instance was loaded with. */
|
|
10
10
|
readonly getConfig: () => Config;
|
|
11
11
|
readonly orchestrator: Pick<SearchOrchestrator, 'search'>;
|
|
12
12
|
readonly operations: ForegroundOperationScope;
|
|
@@ -6,7 +6,16 @@ export declare const WEB_CREDENTIAL_REF_MAX_CHARACTERS = 256;
|
|
|
6
6
|
export declare const WEB_CREDENTIAL_VALUE_MAX_CHARACTERS: number;
|
|
7
7
|
export declare const WEB_CREDENTIAL_SLOTS: readonly ["searchApi", "context7", "exa", "tavily", "firecrawl"];
|
|
8
8
|
export type WebCredentialSlot = (typeof WEB_CREDENTIAL_SLOTS)[number];
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Search profiles that carry an editable supplementary discovery budget. The
|
|
11
|
+
* list is spelled here rather than imported: this contract is also the browser
|
|
12
|
+
* half, and a client module must not depend on the Host configuration module.
|
|
13
|
+
* The Host contract test keeps this list aligned with the configuration schema.
|
|
14
|
+
*/
|
|
15
|
+
export declare const WEB_EXTRA_DISCOVERY_PROFILES: readonly ["auto", "coding_docs", "code_examples", "project_research", "academic", "fact_check"];
|
|
16
|
+
export type WebExtraDiscoveryProfile = (typeof WEB_EXTRA_DISCOVERY_PROFILES)[number];
|
|
17
|
+
export type WebExtraDiscoverySources = Record<WebExtraDiscoveryProfile, number>;
|
|
18
|
+
export declare const WEB_EDITABLE_PATHS: readonly (readonly string[])[];
|
|
10
19
|
export interface WebSearchApiConfig {
|
|
11
20
|
baseUrl: string;
|
|
12
21
|
protocol: string;
|
|
@@ -30,6 +39,7 @@ export interface WebEditableConfig {
|
|
|
30
39
|
toolDiscovery: {
|
|
31
40
|
mode: string;
|
|
32
41
|
};
|
|
42
|
+
extraDiscoverySources: WebExtraDiscoverySources;
|
|
33
43
|
searchApi: WebSearchApiConfig;
|
|
34
44
|
providers: {
|
|
35
45
|
context7: WebDiscoveryProviderConfig;
|
|
@@ -56,6 +66,7 @@ export interface WebConfigOptions {
|
|
|
56
66
|
thinkingLevels: readonly string[];
|
|
57
67
|
toolDiscoveryModes: readonly string[];
|
|
58
68
|
proxyUrlMaxCharacters: number;
|
|
69
|
+
extraDiscoveryMaxSources: number;
|
|
59
70
|
}
|
|
60
71
|
export interface WebCredentialState {
|
|
61
72
|
ref: string;
|
|
@@ -11,11 +11,26 @@ export const WEB_CREDENTIAL_SLOTS = [
|
|
|
11
11
|
'tavily',
|
|
12
12
|
'firecrawl',
|
|
13
13
|
];
|
|
14
|
+
/**
|
|
15
|
+
* Search profiles that carry an editable supplementary discovery budget. The
|
|
16
|
+
* list is spelled here rather than imported: this contract is also the browser
|
|
17
|
+
* half, and a client module must not depend on the Host configuration module.
|
|
18
|
+
* The Host contract test keeps this list aligned with the configuration schema.
|
|
19
|
+
*/
|
|
20
|
+
export const WEB_EXTRA_DISCOVERY_PROFILES = [
|
|
21
|
+
'auto',
|
|
22
|
+
'coding_docs',
|
|
23
|
+
'code_examples',
|
|
24
|
+
'project_research',
|
|
25
|
+
'academic',
|
|
26
|
+
'fact_check',
|
|
27
|
+
];
|
|
14
28
|
export const WEB_EDITABLE_PATHS = [
|
|
15
29
|
['defaultProfile'],
|
|
16
30
|
['defaultDepth'],
|
|
17
31
|
['toolTimeoutMs'],
|
|
18
32
|
['toolDiscovery', 'mode'],
|
|
33
|
+
...WEB_EXTRA_DISCOVERY_PROFILES.map(profile => ['extraDiscoverySources', profile]),
|
|
19
34
|
['searchApi', 'baseUrl'],
|
|
20
35
|
['searchApi', 'protocol'],
|
|
21
36
|
['searchApi', 'model'],
|
package/lib/web-config/host.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import Schema from '@deepseek-ai/schemastery';
|
|
2
|
-
import { Config as SearchEnhanceConfig, SEARCH_API_PROTOCOLS, SEARCH_DEPTHS, SEARCH_ENHANCE_SETTINGS_NAMESPACE, SEARCH_PROFILES, THINKING_LEVELS, TOOL_DISCOVERY_MODES, WEB_EXTRACT_PROXY_URL_MAX_CHARACTERS, validateWebExtractProxyUrl, } from '../config.js';
|
|
2
|
+
import { Config as SearchEnhanceConfig, EXTRA_DISCOVERY_SOURCES_MAX, SEARCH_API_PROTOCOLS, SEARCH_DEPTHS, SEARCH_ENHANCE_SETTINGS_NAMESPACE, SEARCH_PROFILES, THINKING_LEVELS, TOOL_DISCOVERY_MODES, WEB_EXTRACT_PROXY_URL_MAX_CHARACTERS, validateWebExtractProxyUrl, } from '../config.js';
|
|
3
3
|
import { inspectDiagnosticStatus } from '../diagnostics/status.js';
|
|
4
4
|
import { WEB_BASE_URL_MAX_CHARACTERS, WEB_CONFIG_PATH, WEB_CREDENTIAL_REF_MAX_CHARACTERS, WEB_CREDENTIAL_SLOTS, WEB_CREDENTIAL_VALUE_MAX_CHARACTERS, WEB_CREDENTIALS_PATH, WEB_EDITABLE_PATHS, WEB_MODEL_MAX_CHARACTERS, } from './contracts.js';
|
|
5
5
|
export const WEB_CONFIG_REQUEST_MAX_BYTES = 64 * 1024;
|
|
6
6
|
export const WEB_CONFIG_RESPONSE_MAX_BYTES = 128 * 1024;
|
|
7
7
|
const MAX_CREDENTIAL_SOURCE_CHARACTERS = 128;
|
|
8
|
-
|
|
8
|
+
/** One save may touch each editable field at most once. */
|
|
9
|
+
const MAX_MUTATIONS = WEB_EDITABLE_PATHS.length;
|
|
9
10
|
const FORWARDING_HEADERS = [
|
|
10
11
|
'forwarded',
|
|
11
12
|
'x-forwarded-for',
|
|
@@ -65,6 +66,7 @@ function projectConfig(config) {
|
|
|
65
66
|
defaultDepth: config.defaultDepth,
|
|
66
67
|
toolTimeoutMs: config.toolTimeoutMs,
|
|
67
68
|
toolDiscovery: { mode: config.toolDiscovery.mode },
|
|
69
|
+
extraDiscoverySources: { ...config.extraDiscoverySources },
|
|
68
70
|
searchApi: {
|
|
69
71
|
baseUrl: boundedString(config.searchApi.baseUrl, WEB_BASE_URL_MAX_CHARACTERS, 'Grok base URL'),
|
|
70
72
|
protocol: config.searchApi.protocol,
|
|
@@ -213,6 +215,7 @@ export async function readWebConfigSnapshot(ctx, signal) {
|
|
|
213
215
|
thinkingLevels: [...THINKING_LEVELS],
|
|
214
216
|
toolDiscoveryModes: [...TOOL_DISCOVERY_MODES],
|
|
215
217
|
proxyUrlMaxCharacters: WEB_EXTRACT_PROXY_URL_MAX_CHARACTERS,
|
|
218
|
+
extraDiscoveryMaxSources: EXTRA_DISCOVERY_SOURCES_MAX,
|
|
216
219
|
},
|
|
217
220
|
credentials: Object.fromEntries(credentialEntries),
|
|
218
221
|
diagnostics: {
|
|
@@ -527,6 +530,9 @@ async function currentConfig(ctx) {
|
|
|
527
530
|
if (current === undefined) {
|
|
528
531
|
throw new BridgeHttpError(503, 'settings-unavailable', 'Search Enhance settings are not available.');
|
|
529
532
|
}
|
|
533
|
+
// The configuration surface reads Settings live on purpose: it must write a
|
|
534
|
+
// credential to the reference the user just saved, while the search plane keeps
|
|
535
|
+
// using the restart-scoped value its plugin instance loaded.
|
|
530
536
|
// Settings owns schema validation and returns a deeply frozen resolved snapshot.
|
|
531
537
|
// Re-running Schemastery here mutates optional fields and fails on that snapshot.
|
|
532
538
|
return current;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-search-enhance",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Bounded search, documentation, extraction, progressively disclosed site mapping/research planning, and read-only diagnostics for DeepSeek Harness",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|