feat: add pluggable search service registry
This commit is contained in:
+81
-3
@@ -1,13 +1,74 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { buildCitations, isSafeHttpUrl, validateSearchRequest } from './search-capability.mjs';
|
||||
import { MINDSEARCH_DEFAULT_CONFIG, normalizeMindSearchConfig } from './mindsearch-config.mjs';
|
||||
import {
|
||||
MINDSEARCH_DEFAULT_CONFIG,
|
||||
normalizeMindSearchConfig,
|
||||
probeMindSearchService,
|
||||
resolveMindSearchService,
|
||||
} from './mindsearch-config.mjs';
|
||||
import { buildAgentExtensionPolicy, DEFAULT_USER_CAPABILITIES } from './capabilities.mjs';
|
||||
import { searchGithubCode, searchSearxng } from './mindsearch-providers.mjs';
|
||||
import {
|
||||
searchGithubCode,
|
||||
searchResearchService,
|
||||
searchSearxng,
|
||||
startResearchTask,
|
||||
} from './mindsearch-providers.mjs';
|
||||
|
||||
test('MindSearch defaults to disabled and normalizes unsafe modes', () => {
|
||||
assert.equal(MINDSEARCH_DEFAULT_CONFIG.enabled, false);
|
||||
assert.deepEqual(normalizeMindSearchConfig({ enabled: true, mode: 'invalid', providers: { github: true } }), { enabled: false, mode: 'off', providers: { searxng: false, github: true, reader: false }, settings: { searxngEndpoint: '', maxResults: 10, timeoutMs: 8000, readerMaxChars: 12000 } });
|
||||
const config = normalizeMindSearchConfig({ enabled: true, mode: 'invalid', providers: { github: true } });
|
||||
assert.equal(config.enabled, false);
|
||||
assert.equal(config.mode, 'off');
|
||||
assert.deepEqual(config.providers, { searxng: false, github: true, reader: false });
|
||||
assert.equal(config.services.find((service) => service.id === 'github').enabled, true);
|
||||
assert.deepEqual(config.routes, { web: 'searxng', news: 'searxng', code: 'github', read: 'reader', research: '' });
|
||||
});
|
||||
|
||||
test('MindSearch service registry accepts research orchestrators and rejects unsafe endpoints', () => {
|
||||
const config = normalizeMindSearchConfig({
|
||||
enabled: true,
|
||||
mode: 'assist',
|
||||
services: [
|
||||
{
|
||||
id: 'research-main',
|
||||
name: 'Research',
|
||||
kind: 'orchestrator',
|
||||
adapter: 'research-http',
|
||||
endpoint: 'http://127.0.0.1:20100',
|
||||
healthPath: '/health',
|
||||
capabilities: ['search.web', 'research.execute', 'research.execute', 'INVALID CAPABILITY'],
|
||||
enabled: true,
|
||||
timeoutMs: 500000,
|
||||
},
|
||||
{ id: 'metadata', adapter: 'research-http', endpoint: 'http://169.254.169.254/latest', enabled: true },
|
||||
],
|
||||
routes: { research: 'research-main', web: 'research-main' },
|
||||
});
|
||||
assert.equal(config.services.some((service) => service.id === 'metadata'), false);
|
||||
assert.deepEqual(config.services.find((service) => service.id === 'research-main').capabilities, ['search.web', 'research.execute']);
|
||||
assert.equal(config.services.find((service) => service.id === 'research-main').timeoutMs, 120000);
|
||||
assert.equal(resolveMindSearchService(config, 'research').id, 'research-main');
|
||||
});
|
||||
|
||||
test('MindSearch service probe supports loopback SearXNG without exposing arbitrary response data', async () => {
|
||||
let requested;
|
||||
const result = await probeMindSearchService({
|
||||
id: 'searxng-local',
|
||||
name: 'Local',
|
||||
kind: 'provider',
|
||||
adapter: 'searxng',
|
||||
endpoint: 'http://127.0.0.1:8080/search',
|
||||
enabled: true,
|
||||
}, {
|
||||
fetchImpl: async (url) => {
|
||||
requested = String(url);
|
||||
return { ok: true, status: 200, json: async () => ({ results: [{ title: 'A' }] }) };
|
||||
},
|
||||
});
|
||||
assert.match(requested, /q=OpenAI/);
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(result.resultCount, 1);
|
||||
});
|
||||
|
||||
test('search request validation and citations are deterministic', () => {
|
||||
@@ -42,3 +103,20 @@ test('GitHub code adapter sends bounded queries and normalizes results', async (
|
||||
assert.equal(requested.options.headers.accept, 'application/vnd.github+json');
|
||||
assert.equal(result[0].source, 'github');
|
||||
});
|
||||
|
||||
test('Research service adapter uses the stable HTTP contract', async () => {
|
||||
const requests = [];
|
||||
const fetchImpl = async (url, options) => {
|
||||
requests.push({ url: String(url), options });
|
||||
if (String(url).endsWith('/v1/research')) {
|
||||
return { ok: true, json: async () => ({ task_id: 'task-1', status: 'running' }) };
|
||||
}
|
||||
return { ok: true, json: async () => ({ results: [{ title: 'Report', url: 'https://example.com/report', snippet: 'evidence' }] }) };
|
||||
};
|
||||
const results = await searchResearchService('AI Agent', { endpoint: 'http://research.local', limit: 5, fetchImpl });
|
||||
const task = await startResearchTask('Analyze AI Agent', { endpoint: 'http://research.local', depth: 'deep', fetchImpl });
|
||||
assert.equal(results[0].source, 'research-service');
|
||||
assert.deepEqual(task, { taskId: 'task-1', status: 'running', service: 'research-service' });
|
||||
assert.equal(JSON.parse(requests[0].options.body).query, 'AI Agent');
|
||||
assert.deepEqual(JSON.parse(requests[1].options.body), { question: 'Analyze AI Agent', depth: 'deep' });
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user