dsh-search-enhance 0.1.3 → 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.
|
@@ -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,
|
|
@@ -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) {
|
|
@@ -337,7 +336,7 @@ function isLinkOnlyLine(line) {
|
|
|
337
336
|
const stripped = line.replace(/^\s*(?:[-*]|\d+\.)\s*/, '').trim();
|
|
338
337
|
return stripped.length > 0 && (stripped.startsWith('http://')
|
|
339
338
|
|| stripped.startsWith('https://')
|
|
340
|
-
||
|
|
339
|
+
|| stripped.search(MARKDOWN_LINK_PATTERN) !== -1);
|
|
341
340
|
}
|
|
342
341
|
function splitTailLinkBlock(text, state, limits) {
|
|
343
342
|
const lines = text.split(/\r?\n/);
|
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",
|