feat: add pluggable search service registry

This commit is contained in:
john
2026-07-23 21:38:36 +08:00
parent 48e9f68211
commit 4e36b98c9e
7 changed files with 506 additions and 21 deletions
+46
View File
@@ -1,4 +1,5 @@
import { isSafeHttpUrl, normalizeSearchResult } from './search-capability.mjs';
import { isValidSearchServiceEndpoint } from './mindsearch-config.mjs';
export async function searchSearxng(query, { limit = 10, endpoint = process.env.TKMIND_SEARCH_SEARXNG_URL, fetchImpl = fetch } = {}) {
if (!endpoint) throw new Error('SearXNG endpoint is not configured');
@@ -29,3 +30,48 @@ export async function searchGithubCode(query, { limit = 10, token = process.env.
const body = await response.json();
return (Array.isArray(body.items) ? body.items : []).slice(0, limit).map((item, index) => normalizeSearchResult({ title: `${item.repository?.full_name ?? ''}:${item.path ?? item.name ?? ''}`, url: item.html_url, snippet: item.repository?.description ?? '', source: 'github' }, index));
}
function serviceUrl(endpoint, path) {
if (!isValidSearchServiceEndpoint(endpoint, { allowEmpty: false })) throw new Error('Search service endpoint is not configured safely');
const base = endpoint.endsWith('/') ? endpoint : `${endpoint}/`;
return new URL(path.replace(/^\//, ''), base);
}
async function postServiceJson(endpoint, path, payload, { timeoutMs = 30000, fetchImpl = fetch } = {}) {
const response = await fetchImpl(serviceUrl(endpoint, path), {
method: 'POST',
signal: AbortSignal.timeout(timeoutMs),
headers: { accept: 'application/json', 'content-type': 'application/json' },
body: JSON.stringify(payload),
});
if (!response.ok) throw new Error(`Search service returned ${response.status}`);
return response.json();
}
export async function searchResearchService(query, {
endpoint,
type = 'web',
limit = 10,
timeoutMs = 30000,
fetchImpl = fetch,
} = {}) {
const body = await postServiceJson(endpoint, '/v1/search', { query, type, limit }, { timeoutMs, fetchImpl });
return (Array.isArray(body.results) ? body.results : [])
.slice(0, limit)
.map((item, index) => normalizeSearchResult({ ...item, source: item.source || 'research-service' }, index));
}
export async function startResearchTask(question, {
endpoint,
depth = 'standard',
timeoutMs = 30000,
fetchImpl = fetch,
} = {}) {
const body = await postServiceJson(endpoint, '/v1/research', { question, depth }, { timeoutMs, fetchImpl });
if (!body || typeof body !== 'object' || !body.task_id) throw new Error('Research service returned an invalid task');
return {
taskId: String(body.task_id),
status: String(body.status ?? 'running'),
service: String(body.service ?? 'research-service'),
};
}