mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-17 06:17:09 +00:00
feat(websearch): dynamic hook timeout from config + grok-code default
- Hook timeout now computed from max provider timeout + 30s buffer - Removes hardcoded HOOK_TIMEOUT_SECONDS constant - ensureHookConfig() now updates timeout when it changes - Default OpenCode model changed from gpt-5-nano to grok-code - Default OpenCode timeout changed from 60s to 90s - Single source of truth: config.yaml controls all timeouts
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
* CCS_WEBSEARCH_GEMINI=1 - Enable Gemini CLI provider
|
||||
* CCS_WEBSEARCH_OPENCODE=1 - Enable OpenCode provider
|
||||
* CCS_WEBSEARCH_GROK=1 - Enable Grok CLI provider
|
||||
* CCS_WEBSEARCH_OPENCODE_MODEL - OpenCode model (default: opencode/gpt-5-nano)
|
||||
* CCS_WEBSEARCH_OPENCODE_MODEL - OpenCode model (default: opencode/grok-code)
|
||||
* CCS_DEBUG=1 - Enable debug output
|
||||
*
|
||||
* Exit codes:
|
||||
@@ -61,8 +61,8 @@ const PROVIDER_CONFIG = {
|
||||
|
||||
opencode: {
|
||||
// Model to use (can be overridden via CCS_WEBSEARCH_OPENCODE_MODEL env var)
|
||||
model: 'opencode/gpt-5-nano',
|
||||
// Alternative models: opencode/gpt-4o, opencode/claude-3.5-sonnet
|
||||
model: 'opencode/grok-code',
|
||||
// Alternative models: opencode/gpt-4o, opencode/claude-3.5-sonnet, opencode/gpt-5-nano
|
||||
|
||||
// Provider-specific: OpenCode has built-in web search via Zen
|
||||
toolInstruction: 'Search the web using your built-in capabilities.',
|
||||
|
||||
@@ -144,8 +144,8 @@ function mergeWithDefaults(partial: Partial<UnifiedConfig>): UnifiedConfig {
|
||||
},
|
||||
opencode: {
|
||||
enabled: partial.websearch?.providers?.opencode?.enabled ?? false,
|
||||
model: partial.websearch?.providers?.opencode?.model ?? 'opencode/gpt-5-nano',
|
||||
timeout: partial.websearch?.providers?.opencode?.timeout ?? 60,
|
||||
model: partial.websearch?.providers?.opencode?.model ?? 'opencode/grok-code',
|
||||
timeout: partial.websearch?.providers?.opencode?.timeout ?? 90,
|
||||
},
|
||||
grok: {
|
||||
enabled: partial.websearch?.providers?.grok?.enabled ?? false,
|
||||
@@ -392,8 +392,8 @@ export function getWebSearchConfig(): {
|
||||
|
||||
const opencodeConfig = {
|
||||
enabled: config.websearch?.providers?.opencode?.enabled ?? false,
|
||||
model: config.websearch?.providers?.opencode?.model ?? 'opencode/gpt-5-nano',
|
||||
timeout: config.websearch?.providers?.opencode?.timeout ?? 60,
|
||||
model: config.websearch?.providers?.opencode?.model ?? 'opencode/grok-code',
|
||||
timeout: config.websearch?.providers?.opencode?.timeout ?? 90,
|
||||
};
|
||||
|
||||
const grokConfig = {
|
||||
|
||||
@@ -127,7 +127,7 @@ export interface GrokWebSearchConfig {
|
||||
export interface OpenCodeWebSearchConfig {
|
||||
/** Enable OpenCode CLI for WebSearch (default: false) */
|
||||
enabled?: boolean;
|
||||
/** Model to use (default: opencode/gpt-5-nano) */
|
||||
/** Model to use (default: opencode/grok-code) */
|
||||
model?: string;
|
||||
/** Timeout in seconds (default: 60) */
|
||||
timeout?: number;
|
||||
|
||||
@@ -26,8 +26,11 @@ const CCS_HOOKS_DIR = path.join(os.homedir(), '.ccs', 'hooks');
|
||||
// Hook file name
|
||||
const WEBSEARCH_HOOK = 'websearch-transformer.cjs';
|
||||
|
||||
// Default hook timeout in seconds (Gemini CLI needs time)
|
||||
const HOOK_TIMEOUT_SECONDS = 60;
|
||||
// Buffer time added to max provider timeout for hook timeout (seconds)
|
||||
const HOOK_TIMEOUT_BUFFER = 30;
|
||||
|
||||
// Minimum hook timeout in seconds (fallback if no providers configured)
|
||||
const MIN_HOOK_TIMEOUT = 60;
|
||||
|
||||
// Path to Claude settings.json
|
||||
const CLAUDE_SETTINGS_PATH = path.join(os.homedir(), '.claude', 'settings.json');
|
||||
@@ -221,7 +224,7 @@ let opencodeCliCache: OpenCodeCliStatus | null = null;
|
||||
/**
|
||||
* Check if OpenCode CLI is installed globally
|
||||
*
|
||||
* OpenCode provides built-in web search via opencode/gpt-5-nano model.
|
||||
* OpenCode provides built-in web search via opencode/grok-code model.
|
||||
* Install: curl -fsSL https://opencode.ai/install | bash
|
||||
*
|
||||
* @returns OpenCode CLI status with path and version
|
||||
@@ -482,9 +485,27 @@ export function hasWebSearchHook(): boolean {
|
||||
|
||||
/**
|
||||
* Get WebSearch hook configuration for settings.json
|
||||
* Timeout is computed from max provider timeout in config.yaml + buffer
|
||||
*/
|
||||
export function getWebSearchHookConfig(): Record<string, unknown> {
|
||||
const hookPath = path.join(CCS_HOOKS_DIR, WEBSEARCH_HOOK);
|
||||
const wsConfig = getWebSearchConfig();
|
||||
|
||||
// Compute max timeout from enabled providers
|
||||
const timeouts: number[] = [];
|
||||
if (wsConfig.providers?.gemini?.enabled && wsConfig.providers.gemini.timeout) {
|
||||
timeouts.push(wsConfig.providers.gemini.timeout);
|
||||
}
|
||||
if (wsConfig.providers?.opencode?.enabled && wsConfig.providers.opencode.timeout) {
|
||||
timeouts.push(wsConfig.providers.opencode.timeout);
|
||||
}
|
||||
if (wsConfig.providers?.grok?.enabled && wsConfig.providers.grok.timeout) {
|
||||
timeouts.push(wsConfig.providers.grok.timeout);
|
||||
}
|
||||
|
||||
// Hook timeout = max provider timeout + buffer (or minimum if none configured)
|
||||
const maxProviderTimeout = timeouts.length > 0 ? Math.max(...timeouts) : MIN_HOOK_TIMEOUT;
|
||||
const hookTimeout = maxProviderTimeout + HOOK_TIMEOUT_BUFFER;
|
||||
|
||||
return {
|
||||
PreToolUse: [
|
||||
@@ -494,7 +515,7 @@ export function getWebSearchHookConfig(): Record<string, unknown> {
|
||||
{
|
||||
type: 'command',
|
||||
command: `node "${hookPath}"`,
|
||||
timeout: HOOK_TIMEOUT_SECONDS,
|
||||
timeout: hookTimeout,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -538,16 +559,30 @@ function ensureHookConfig(): boolean {
|
||||
});
|
||||
|
||||
if (webSearchHookIndex !== -1) {
|
||||
// Hook exists - check if it needs updating (different command path)
|
||||
// Hook exists - check if it needs updating (different command path or timeout)
|
||||
const existingHook = hooks.PreToolUse[webSearchHookIndex] as Record<string, unknown>;
|
||||
const existingHooks = existingHook.hooks as Array<Record<string, unknown>>;
|
||||
const currentHookConfig = getWebSearchHookConfig();
|
||||
const expectedHooks = (currentHookConfig.PreToolUse as Array<Record<string, unknown>>)[0]
|
||||
.hooks as Array<Record<string, unknown>>;
|
||||
const expectedTimeout = expectedHooks[0].timeout as number;
|
||||
|
||||
let needsUpdate = false;
|
||||
|
||||
if (existingHooks?.[0]?.command !== expectedCommand) {
|
||||
// Update the hook command to point to new file
|
||||
existingHooks[0].command = expectedCommand;
|
||||
needsUpdate = true;
|
||||
}
|
||||
|
||||
if (existingHooks?.[0]?.timeout !== expectedTimeout) {
|
||||
existingHooks[0].timeout = expectedTimeout;
|
||||
needsUpdate = true;
|
||||
}
|
||||
|
||||
if (needsUpdate) {
|
||||
fs.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2), 'utf8');
|
||||
if (process.env.CCS_DEBUG) {
|
||||
console.error(info('Updated WebSearch hook command in settings.json'));
|
||||
console.error(info('Updated WebSearch hook config in settings.json'));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -629,7 +664,7 @@ export function getWebSearchHookEnv(): Record<string, string> {
|
||||
}
|
||||
// Use opencode timeout if no gemini timeout set
|
||||
if (!env.CCS_WEBSEARCH_TIMEOUT) {
|
||||
env.CCS_WEBSEARCH_TIMEOUT = String(wsConfig.providers.opencode.timeout || 60);
|
||||
env.CCS_WEBSEARCH_TIMEOUT = String(wsConfig.providers.opencode.timeout || 90);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1498,7 +1498,7 @@ apiRoutes.put('/websearch', (req: Request, res: Response): void => {
|
||||
model:
|
||||
providers.opencode?.model ??
|
||||
existingConfig.websearch?.providers?.opencode?.model ??
|
||||
'opencode/gpt-5-nano',
|
||||
'opencode/grok-code',
|
||||
timeout:
|
||||
providers.opencode?.timeout ??
|
||||
existingConfig.websearch?.providers?.opencode?.timeout ??
|
||||
|
||||
Reference in New Issue
Block a user