mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-17 10:17:05 +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_GEMINI=1 - Enable Gemini CLI provider
|
||||||
* CCS_WEBSEARCH_OPENCODE=1 - Enable OpenCode provider
|
* CCS_WEBSEARCH_OPENCODE=1 - Enable OpenCode provider
|
||||||
* CCS_WEBSEARCH_GROK=1 - Enable Grok CLI 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
|
* CCS_DEBUG=1 - Enable debug output
|
||||||
*
|
*
|
||||||
* Exit codes:
|
* Exit codes:
|
||||||
@@ -61,8 +61,8 @@ const PROVIDER_CONFIG = {
|
|||||||
|
|
||||||
opencode: {
|
opencode: {
|
||||||
// Model to use (can be overridden via CCS_WEBSEARCH_OPENCODE_MODEL env var)
|
// Model to use (can be overridden via CCS_WEBSEARCH_OPENCODE_MODEL env var)
|
||||||
model: 'opencode/gpt-5-nano',
|
model: 'opencode/grok-code',
|
||||||
// Alternative models: opencode/gpt-4o, opencode/claude-3.5-sonnet
|
// Alternative models: opencode/gpt-4o, opencode/claude-3.5-sonnet, opencode/gpt-5-nano
|
||||||
|
|
||||||
// Provider-specific: OpenCode has built-in web search via Zen
|
// Provider-specific: OpenCode has built-in web search via Zen
|
||||||
toolInstruction: 'Search the web using your built-in capabilities.',
|
toolInstruction: 'Search the web using your built-in capabilities.',
|
||||||
|
|||||||
@@ -144,8 +144,8 @@ function mergeWithDefaults(partial: Partial<UnifiedConfig>): UnifiedConfig {
|
|||||||
},
|
},
|
||||||
opencode: {
|
opencode: {
|
||||||
enabled: partial.websearch?.providers?.opencode?.enabled ?? false,
|
enabled: partial.websearch?.providers?.opencode?.enabled ?? false,
|
||||||
model: partial.websearch?.providers?.opencode?.model ?? 'opencode/gpt-5-nano',
|
model: partial.websearch?.providers?.opencode?.model ?? 'opencode/grok-code',
|
||||||
timeout: partial.websearch?.providers?.opencode?.timeout ?? 60,
|
timeout: partial.websearch?.providers?.opencode?.timeout ?? 90,
|
||||||
},
|
},
|
||||||
grok: {
|
grok: {
|
||||||
enabled: partial.websearch?.providers?.grok?.enabled ?? false,
|
enabled: partial.websearch?.providers?.grok?.enabled ?? false,
|
||||||
@@ -392,8 +392,8 @@ export function getWebSearchConfig(): {
|
|||||||
|
|
||||||
const opencodeConfig = {
|
const opencodeConfig = {
|
||||||
enabled: config.websearch?.providers?.opencode?.enabled ?? false,
|
enabled: config.websearch?.providers?.opencode?.enabled ?? false,
|
||||||
model: config.websearch?.providers?.opencode?.model ?? 'opencode/gpt-5-nano',
|
model: config.websearch?.providers?.opencode?.model ?? 'opencode/grok-code',
|
||||||
timeout: config.websearch?.providers?.opencode?.timeout ?? 60,
|
timeout: config.websearch?.providers?.opencode?.timeout ?? 90,
|
||||||
};
|
};
|
||||||
|
|
||||||
const grokConfig = {
|
const grokConfig = {
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ export interface GrokWebSearchConfig {
|
|||||||
export interface OpenCodeWebSearchConfig {
|
export interface OpenCodeWebSearchConfig {
|
||||||
/** Enable OpenCode CLI for WebSearch (default: false) */
|
/** Enable OpenCode CLI for WebSearch (default: false) */
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
/** Model to use (default: opencode/gpt-5-nano) */
|
/** Model to use (default: opencode/grok-code) */
|
||||||
model?: string;
|
model?: string;
|
||||||
/** Timeout in seconds (default: 60) */
|
/** Timeout in seconds (default: 60) */
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
|
|||||||
@@ -26,8 +26,11 @@ const CCS_HOOKS_DIR = path.join(os.homedir(), '.ccs', 'hooks');
|
|||||||
// Hook file name
|
// Hook file name
|
||||||
const WEBSEARCH_HOOK = 'websearch-transformer.cjs';
|
const WEBSEARCH_HOOK = 'websearch-transformer.cjs';
|
||||||
|
|
||||||
// Default hook timeout in seconds (Gemini CLI needs time)
|
// Buffer time added to max provider timeout for hook timeout (seconds)
|
||||||
const HOOK_TIMEOUT_SECONDS = 60;
|
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
|
// Path to Claude settings.json
|
||||||
const CLAUDE_SETTINGS_PATH = path.join(os.homedir(), '.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
|
* 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
|
* Install: curl -fsSL https://opencode.ai/install | bash
|
||||||
*
|
*
|
||||||
* @returns OpenCode CLI status with path and version
|
* @returns OpenCode CLI status with path and version
|
||||||
@@ -482,9 +485,27 @@ export function hasWebSearchHook(): boolean {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get WebSearch hook configuration for settings.json
|
* Get WebSearch hook configuration for settings.json
|
||||||
|
* Timeout is computed from max provider timeout in config.yaml + buffer
|
||||||
*/
|
*/
|
||||||
export function getWebSearchHookConfig(): Record<string, unknown> {
|
export function getWebSearchHookConfig(): Record<string, unknown> {
|
||||||
const hookPath = path.join(CCS_HOOKS_DIR, WEBSEARCH_HOOK);
|
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 {
|
return {
|
||||||
PreToolUse: [
|
PreToolUse: [
|
||||||
@@ -494,7 +515,7 @@ export function getWebSearchHookConfig(): Record<string, unknown> {
|
|||||||
{
|
{
|
||||||
type: 'command',
|
type: 'command',
|
||||||
command: `node "${hookPath}"`,
|
command: `node "${hookPath}"`,
|
||||||
timeout: HOOK_TIMEOUT_SECONDS,
|
timeout: hookTimeout,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -538,16 +559,30 @@ function ensureHookConfig(): boolean {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (webSearchHookIndex !== -1) {
|
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 existingHook = hooks.PreToolUse[webSearchHookIndex] as Record<string, unknown>;
|
||||||
const existingHooks = existingHook.hooks as Array<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) {
|
if (existingHooks?.[0]?.command !== expectedCommand) {
|
||||||
// Update the hook command to point to new file
|
|
||||||
existingHooks[0].command = expectedCommand;
|
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');
|
fs.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2), 'utf8');
|
||||||
if (process.env.CCS_DEBUG) {
|
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;
|
return true;
|
||||||
@@ -629,7 +664,7 @@ export function getWebSearchHookEnv(): Record<string, string> {
|
|||||||
}
|
}
|
||||||
// Use opencode timeout if no gemini timeout set
|
// Use opencode timeout if no gemini timeout set
|
||||||
if (!env.CCS_WEBSEARCH_TIMEOUT) {
|
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:
|
model:
|
||||||
providers.opencode?.model ??
|
providers.opencode?.model ??
|
||||||
existingConfig.websearch?.providers?.opencode?.model ??
|
existingConfig.websearch?.providers?.opencode?.model ??
|
||||||
'opencode/gpt-5-nano',
|
'opencode/grok-code',
|
||||||
timeout:
|
timeout:
|
||||||
providers.opencode?.timeout ??
|
providers.opencode?.timeout ??
|
||||||
existingConfig.websearch?.providers?.opencode?.timeout ??
|
existingConfig.websearch?.providers?.opencode?.timeout ??
|
||||||
|
|||||||
Reference in New Issue
Block a user