fix: normalize websearch hook output for local providers

This commit is contained in:
Tam Nhu Tran committed 2026-03-30 15:10:30 -04:00
1 parent 39ba295d5f
commit a5d71e3560
2 files changed
+189 -33

No files matched your search

+44 -30
View File
@@ -183,23 +183,58 @@ function extractDuckDuckGoResults(html, count) {
}
function formatStructuredSearchResults(query, providerName, results) {
const lines = [
'CCS local WebSearch evidence',
`Provider: ${providerName}`,
`Query: "${query}"`,
`Result count: ${results.length}`,
'',
];
if (!results.length) {
return `No search results found for "${query}" via ${providerName}.`;
lines.push('No results found.');
return lines.join('\n');
}
const lines = [`Search results for "${query}" via ${providerName}:`, ''];
for (const [index, result] of results.entries()) {
lines.push(`${index + 1}. ${result.title}`);
lines.push(` ${result.url}`);
lines.push(` URL: ${result.url}`);
if (result.description) {
lines.push(` ${result.description}`);
lines.push(` Snippet: ${result.description}`);
}
lines.push('');
}
lines.push('Use these results to answer the user directly.');
return lines.join('\n');
}
function buildSuccessHookOutput(query, providerName, content) {
return {
hookSpecificOutput: {
hookEventName: 'PreToolUse',
permissionDecision: 'deny',
permissionDecisionReason: `CCS already retrieved WebSearch results locally via ${providerName}. Use the provided context instead of calling native WebSearch for "${query}".`,
additionalContext: content,
},
};
}
function buildFailureHookOutput(query, errors) {
const detail = errors.map((entry) => `${entry.provider}: ${entry.error}`).join(' | ');
return {
hookSpecificOutput: {
hookEventName: 'PreToolUse',
permissionDecision: 'deny',
permissionDecisionReason: `CCS could not complete local WebSearch for "${query}". Native WebSearch is unavailable for this profile.`,
additionalContext: `CCS local WebSearch failed for "${query}". Attempted providers: ${detail}`,
},
};
}
function emitHookOutput(output) {
console.log(JSON.stringify(output));
process.exit(0);
}
async function fetchWithTimeout(url, options, timeoutMs) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -534,34 +569,11 @@ function tryGrokSearch(query, timeoutSec = DEFAULT_TIMEOUT_SEC) {
}
function outputSuccess(query, content, providerName) {
const output = {
decision: 'block',
reason: `WebSearch handled via ${providerName}`,
hookSpecificOutput: {
hookEventName: 'PreToolUse',
permissionDecision: 'deny',
permissionDecisionReason: `[WebSearch Result via ${providerName}]\n\nQuery: "${query}"\n\n${content}`,
},
};
console.log(JSON.stringify(output));
process.exit(2);
emitHookOutput(buildSuccessHookOutput(query, providerName, content));
}
function outputAllFailedMessage(query, errors) {
const detail = errors.map((entry) => `${entry.provider}: ${entry.error}`).join(' | ');
const output = {
decision: 'block',
reason: 'WebSearch fallback failed',
hookSpecificOutput: {
hookEventName: 'PreToolUse',
permissionDecision: 'deny',
permissionDecisionReason: `WebSearch could not be completed for "${query}". ${detail}`,
},
};
console.log(JSON.stringify(output));
process.exit(2);
emitHookOutput(buildFailureHookOutput(query, errors));
}
async function processHook(input) {
@@ -675,6 +687,8 @@ if (require.main === module) {
}
module.exports = {
buildFailureHookOutput,
buildSuccessHookOutput,
extractDuckDuckGoResults,
formatStructuredSearchResults,
tryExaSearch,