mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-15 08:16:50 +00:00
feat(websearch): add real provider chain
This commit is contained in:
1 parent
84fd2b001c
commit
6c7d215ecc
18 files changed
+2105
-1203
No files matched your search
@@ -0,0 +1,54 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
|
||||
const hook = require('../../../lib/hooks/websearch-transformer.cjs') as {
|
||||
extractDuckDuckGoResults: (html: string, count: number) => Array<{
|
||||
title: string;
|
||||
url: string;
|
||||
description: string;
|
||||
}>;
|
||||
formatStructuredSearchResults: (
|
||||
query: string,
|
||||
providerName: string,
|
||||
results: Array<{ title: string; url: string; description: string }>
|
||||
) => string;
|
||||
};
|
||||
|
||||
describe('websearch-transformer hook helpers', () => {
|
||||
it('extracts DuckDuckGo results and unwraps uddg redirect URLs', () => {
|
||||
const html = `
|
||||
<a class="result__a" href="/l/?uddg=https%3A%2F%2Fexample.com%2Farticle">Example title</a>
|
||||
<a class="result__snippet">Example snippet</a>
|
||||
<a class="result__a" href="https://second.example.com/post">Second title</a>
|
||||
<a class="result__snippet">Second snippet</a>
|
||||
`;
|
||||
|
||||
const results = hook.extractDuckDuckGoResults(html, 2);
|
||||
|
||||
expect(results).toHaveLength(2);
|
||||
expect(results[0]).toEqual({
|
||||
title: 'Example title',
|
||||
url: 'https://example.com/article',
|
||||
description: 'Example snippet',
|
||||
});
|
||||
expect(results[1]).toEqual({
|
||||
title: 'Second title',
|
||||
url: 'https://second.example.com/post',
|
||||
description: 'Second snippet',
|
||||
});
|
||||
});
|
||||
|
||||
it('formats structured search results for hook deny output', () => {
|
||||
const formatted = hook.formatStructuredSearchResults('ccs websearch', 'DuckDuckGo', [
|
||||
{
|
||||
title: 'Result title',
|
||||
url: 'https://example.com',
|
||||
description: 'Result snippet',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(formatted).toContain('Search results for "ccs websearch" via DuckDuckGo');
|
||||
expect(formatted).toContain('1. Result title');
|
||||
expect(formatted).toContain('https://example.com');
|
||||
expect(formatted).toContain('Result snippet');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,127 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { getWebSearchReadiness } from '../../../../src/utils/websearch/status';
|
||||
|
||||
function writeWebSearchConfig(tempRoot: string, lines: string[]): void {
|
||||
fs.writeFileSync(path.join(tempRoot, '.ccs', 'config.yaml'), lines.join('\n'), 'utf8');
|
||||
}
|
||||
|
||||
describe('websearch readiness', () => {
|
||||
const originalCcsHome = process.env.CCS_HOME;
|
||||
const originalBraveKey = process.env.BRAVE_API_KEY;
|
||||
const originalExaKey = process.env.EXA_API_KEY;
|
||||
const originalTavilyKey = process.env.TAVILY_API_KEY;
|
||||
let tempRoot = '';
|
||||
|
||||
beforeEach(() => {
|
||||
tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-websearch-status-'));
|
||||
process.env.CCS_HOME = tempRoot;
|
||||
delete process.env.BRAVE_API_KEY;
|
||||
delete process.env.EXA_API_KEY;
|
||||
delete process.env.TAVILY_API_KEY;
|
||||
fs.mkdirSync(path.join(tempRoot, '.ccs'), { recursive: true });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (originalCcsHome !== undefined) process.env.CCS_HOME = originalCcsHome;
|
||||
else delete process.env.CCS_HOME;
|
||||
|
||||
if (originalBraveKey !== undefined) process.env.BRAVE_API_KEY = originalBraveKey;
|
||||
else delete process.env.BRAVE_API_KEY;
|
||||
|
||||
if (originalExaKey !== undefined) process.env.EXA_API_KEY = originalExaKey;
|
||||
else delete process.env.EXA_API_KEY;
|
||||
|
||||
if (originalTavilyKey !== undefined) process.env.TAVILY_API_KEY = originalTavilyKey;
|
||||
else delete process.env.TAVILY_API_KEY;
|
||||
|
||||
fs.rmSync(tempRoot, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('is ready by default because DuckDuckGo is enabled', () => {
|
||||
const readiness = getWebSearchReadiness();
|
||||
|
||||
expect(readiness.readiness).toBe('ready');
|
||||
expect(readiness.message).toContain('DuckDuckGo');
|
||||
});
|
||||
|
||||
it('reports setup required when only Tavily is enabled without an API key', () => {
|
||||
writeWebSearchConfig(tempRoot, [
|
||||
'version: 10',
|
||||
'websearch:',
|
||||
' enabled: true',
|
||||
' providers:',
|
||||
' exa:',
|
||||
' enabled: false',
|
||||
' max_results: 5',
|
||||
' tavily:',
|
||||
' enabled: true',
|
||||
' max_results: 5',
|
||||
' duckduckgo:',
|
||||
' enabled: false',
|
||||
' max_results: 5',
|
||||
' brave:',
|
||||
' enabled: false',
|
||||
' max_results: 5',
|
||||
' gemini:',
|
||||
' enabled: false',
|
||||
' model: "gemini-2.5-flash"',
|
||||
' timeout: 55',
|
||||
' opencode:',
|
||||
' enabled: false',
|
||||
' model: "opencode/grok-code"',
|
||||
' timeout: 90',
|
||||
' grok:',
|
||||
' enabled: false',
|
||||
' timeout: 55',
|
||||
'',
|
||||
]);
|
||||
|
||||
const readiness = getWebSearchReadiness();
|
||||
|
||||
expect(readiness.readiness).toBe('needs_setup');
|
||||
expect(readiness.message).toContain('Tavily');
|
||||
expect(readiness.message).toContain('TAVILY_API_KEY');
|
||||
});
|
||||
|
||||
it('prefers API-backed readiness when Exa is enabled and configured', () => {
|
||||
process.env.EXA_API_KEY = 'exa-test-key';
|
||||
writeWebSearchConfig(tempRoot, [
|
||||
'version: 10',
|
||||
'websearch:',
|
||||
' enabled: true',
|
||||
' providers:',
|
||||
' exa:',
|
||||
' enabled: true',
|
||||
' max_results: 5',
|
||||
' tavily:',
|
||||
' enabled: false',
|
||||
' max_results: 5',
|
||||
' duckduckgo:',
|
||||
' enabled: false',
|
||||
' max_results: 5',
|
||||
' brave:',
|
||||
' enabled: false',
|
||||
' max_results: 5',
|
||||
' gemini:',
|
||||
' enabled: false',
|
||||
' model: "gemini-2.5-flash"',
|
||||
' timeout: 55',
|
||||
' opencode:',
|
||||
' enabled: false',
|
||||
' model: "opencode/grok-code"',
|
||||
' timeout: 90',
|
||||
' grok:',
|
||||
' enabled: false',
|
||||
' timeout: 55',
|
||||
'',
|
||||
]);
|
||||
|
||||
const readiness = getWebSearchReadiness();
|
||||
|
||||
expect(readiness.readiness).toBe('ready');
|
||||
expect(readiness.message).toContain('Exa');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user