feat(websearch): add model config + improve hook UX

- Add model field to Gemini/OpenCode WebSearch providers
- Config.yaml now explicitly stores model (gemini-2.5-flash, opencode/grok-code)
- UI: blur-to-save for model inputs (no save on every keystroke)
- UI: collapsible install hints inside provider cards
- UI: left accent border for enabled providers (better light theme contrast)
- Hook: use systemMessage for info-styled confirmation
- Hook: cleaner result format without defensive "NOT an error" language
This commit is contained in:
kaitranntt committed 2025-12-17 00:16:03 -05:00
1 parent 6d69379ead
commit 14c53d575f
6 files changed
+342 -184

No files matched your search

+22 -7
View File
@@ -11,6 +11,7 @@
* CCS_WEBSEARCH_ENABLED=1 - Enable WebSearch (default: 1)
* CCS_WEBSEARCH_TIMEOUT=55 - Timeout in seconds (default: 55)
* CCS_WEBSEARCH_GEMINI=1 - Enable Gemini CLI provider
* CCS_WEBSEARCH_GEMINI_MODEL - Gemini model (default: gemini-2.5-flash)
* CCS_WEBSEARCH_OPENCODE=1 - Enable OpenCode provider
* CCS_WEBSEARCH_GROK=1 - Enable Grok CLI provider
* CCS_WEBSEARCH_OPENCODE_MODEL - OpenCode model (default: opencode/grok-code)
@@ -258,13 +259,16 @@ function tryGeminiSearch(query, timeoutSec = DEFAULT_TIMEOUT_SEC) {
const config = PROVIDER_CONFIG.gemini;
const prompt = buildPrompt('gemini', query);
// Allow model override via env var
const model = process.env.CCS_WEBSEARCH_GEMINI_MODEL || config.model;
if (process.env.CCS_DEBUG) {
console.error(`[CCS Hook] Executing: gemini --model ${config.model} --yolo -p "..."`);
console.error(`[CCS Hook] Executing: gemini --model ${model} --yolo -p "..."`);
}
const spawnResult = spawnSync(
'gemini',
['--model', config.model, '--yolo', '-p', prompt],
['--model', model, '--yolo', '-p', prompt],
{
encoding: 'utf8',
timeout: timeoutMs,
@@ -441,29 +445,40 @@ function tryGrokSearch(query, timeoutSec = DEFAULT_TIMEOUT_SEC) {
*/
function formatSearchResults(query, content, providerName) {
return [
'=== WEBSEARCH COMPLETED SUCCESSFULLY ===',
`(via ${providerName} - this is NOT an error)`,
`[WebSearch Result via ${providerName}]`,
'',
`Query: "${query}"`,
'',
content,
'',
'=========================================',
'Use this information to answer the user. Search again if needed.',
'---',
'Use this information to answer the user.',
].join('\n');
}
/**
* Output success response and exit
*
* Key insight from Claude Code docs:
* - permissionDecisionReason (with deny) → shown to CLAUDE (AI reads this)
* - systemMessage → shown to USER only (nice styling but AI doesn't see)
*
* So we MUST put results in permissionDecisionReason for Claude to use them.
* systemMessage provides the nice UI for the user.
*/
function outputSuccess(query, content, providerName) {
const formattedResults = formatSearchResults(query, content, providerName);
const output = {
decision: 'block',
reason: `WebSearch completed via ${providerName}`,
// Nice message for user (shows as "says:" - info style)
systemMessage: `[WebSearch via ${providerName}] Results retrieved successfully. See below.`,
hookSpecificOutput: {
hookEventName: 'PreToolUse',
permissionDecision: 'deny',
permissionDecisionReason: formatSearchResults(query, content, providerName),
// Full results here - Claude reads this
permissionDecisionReason: formattedResults,
},
};