mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-03 13:12:09 +00:00
fix(websearch): filter disabled legacy CLI probes and skip unused version fetching
Closes #1716
This commit is contained in:
1 parent
65dc902299
commit
f4911694e3
6 files changed
+267
-101
No files matched your search
+35
-23
@@ -22,35 +22,47 @@ let agyCliCache: AgyCliStatus | null = null;
|
||||
*
|
||||
* @returns Antigravity CLI status with path and version
|
||||
*/
|
||||
export function getAgyCliStatus(): AgyCliStatus {
|
||||
// Return cached result if available
|
||||
if (agyCliCache) {
|
||||
export function getAgyCliStatus(options?: { fetchVersion?: boolean }): AgyCliStatus {
|
||||
const fetchVersion = options?.fetchVersion !== false;
|
||||
|
||||
if (
|
||||
agyCliCache &&
|
||||
(!fetchVersion || agyCliCache.version !== undefined || !agyCliCache.installed)
|
||||
) {
|
||||
if (!fetchVersion) {
|
||||
return { ...agyCliCache, version: undefined };
|
||||
}
|
||||
return agyCliCache;
|
||||
}
|
||||
|
||||
const result: AgyCliStatus = {
|
||||
installed: false,
|
||||
path: undefined,
|
||||
version: undefined,
|
||||
};
|
||||
const result: AgyCliStatus = agyCliCache
|
||||
? { ...agyCliCache }
|
||||
: {
|
||||
installed: false,
|
||||
path: undefined,
|
||||
version: undefined,
|
||||
};
|
||||
|
||||
try {
|
||||
const isWindows = process.platform === 'win32';
|
||||
const whichCmd = isWindows ? 'where agy' : 'which agy';
|
||||
if (!agyCliCache) {
|
||||
const isWindows = process.platform === 'win32';
|
||||
const whichCmd = isWindows ? 'where agy' : 'which agy';
|
||||
|
||||
const pathResult = execSync(whichCmd, {
|
||||
encoding: 'utf8',
|
||||
timeout: 5000,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
const pathResult = execSync(whichCmd, {
|
||||
encoding: 'utf8',
|
||||
timeout: 5000,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
|
||||
const agyPath = pathResult.trim().split('\n')[0]; // First result on Windows
|
||||
const agyPath = pathResult.trim().split('\n')[0];
|
||||
|
||||
if (agyPath) {
|
||||
result.installed = true;
|
||||
result.path = agyPath;
|
||||
if (agyPath) {
|
||||
result.installed = true;
|
||||
result.path = agyPath;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to get version
|
||||
if (result.installed && fetchVersion && result.version === undefined) {
|
||||
try {
|
||||
const versionResult = execSync('agy --version', {
|
||||
encoding: 'utf8',
|
||||
@@ -59,17 +71,17 @@ export function getAgyCliStatus(): AgyCliStatus {
|
||||
});
|
||||
result.version = versionResult.trim();
|
||||
} catch {
|
||||
// Version check failed, but CLI is installed
|
||||
result.version = 'unknown';
|
||||
}
|
||||
} else if (!fetchVersion) {
|
||||
result.version = undefined;
|
||||
}
|
||||
} catch {
|
||||
// Command not found - Antigravity CLI not installed
|
||||
}
|
||||
|
||||
// Cache result
|
||||
agyCliCache = result;
|
||||
return result;
|
||||
return fetchVersion ? result : { ...result, version: undefined };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,35 +23,47 @@ let geminiCliCache: GeminiCliStatus | null = null;
|
||||
*
|
||||
* @returns Gemini CLI status with path and version
|
||||
*/
|
||||
export function getGeminiCliStatus(): GeminiCliStatus {
|
||||
// Return cached result if available
|
||||
if (geminiCliCache) {
|
||||
export function getGeminiCliStatus(options?: { fetchVersion?: boolean }): GeminiCliStatus {
|
||||
const fetchVersion = options?.fetchVersion !== false;
|
||||
|
||||
if (
|
||||
geminiCliCache &&
|
||||
(!fetchVersion || geminiCliCache.version !== undefined || !geminiCliCache.installed)
|
||||
) {
|
||||
if (!fetchVersion) {
|
||||
return { ...geminiCliCache, version: undefined };
|
||||
}
|
||||
return geminiCliCache;
|
||||
}
|
||||
|
||||
const result: GeminiCliStatus = {
|
||||
installed: false,
|
||||
path: undefined,
|
||||
version: undefined,
|
||||
};
|
||||
const result: GeminiCliStatus = geminiCliCache
|
||||
? { ...geminiCliCache }
|
||||
: {
|
||||
installed: false,
|
||||
path: undefined,
|
||||
version: undefined,
|
||||
};
|
||||
|
||||
try {
|
||||
const isWindows = process.platform === 'win32';
|
||||
const whichCmd = isWindows ? 'where gemini' : 'which gemini';
|
||||
if (!geminiCliCache) {
|
||||
const isWindows = process.platform === 'win32';
|
||||
const whichCmd = isWindows ? 'where gemini' : 'which gemini';
|
||||
|
||||
const pathResult = execSync(whichCmd, {
|
||||
encoding: 'utf8',
|
||||
timeout: 5000,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
const pathResult = execSync(whichCmd, {
|
||||
encoding: 'utf8',
|
||||
timeout: 5000,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
|
||||
const geminiPath = pathResult.trim().split('\n')[0]; // First result on Windows
|
||||
const geminiPath = pathResult.trim().split('\n')[0];
|
||||
|
||||
if (geminiPath) {
|
||||
result.installed = true;
|
||||
result.path = geminiPath;
|
||||
if (geminiPath) {
|
||||
result.installed = true;
|
||||
result.path = geminiPath;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to get version
|
||||
if (result.installed && fetchVersion && result.version === undefined) {
|
||||
try {
|
||||
const versionResult = execSync('gemini --version', {
|
||||
encoding: 'utf8',
|
||||
@@ -60,17 +72,17 @@ export function getGeminiCliStatus(): GeminiCliStatus {
|
||||
});
|
||||
result.version = versionResult.trim();
|
||||
} catch {
|
||||
// Version check failed, but CLI is installed
|
||||
result.version = 'unknown';
|
||||
}
|
||||
} else if (!fetchVersion) {
|
||||
result.version = undefined;
|
||||
}
|
||||
} catch {
|
||||
// Command not found - Gemini CLI not installed
|
||||
}
|
||||
|
||||
// Cache result
|
||||
geminiCliCache = result;
|
||||
return result;
|
||||
return fetchVersion ? result : { ...result, version: undefined };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,35 +20,47 @@ let grokCliCache: GrokCliStatus | null = null;
|
||||
*
|
||||
* @returns Grok CLI status with path and version
|
||||
*/
|
||||
export function getGrokCliStatus(): GrokCliStatus {
|
||||
// Return cached result if available
|
||||
if (grokCliCache) {
|
||||
export function getGrokCliStatus(options?: { fetchVersion?: boolean }): GrokCliStatus {
|
||||
const fetchVersion = options?.fetchVersion !== false;
|
||||
|
||||
if (
|
||||
grokCliCache &&
|
||||
(!fetchVersion || grokCliCache.version !== undefined || !grokCliCache.installed)
|
||||
) {
|
||||
if (!fetchVersion) {
|
||||
return { ...grokCliCache, version: undefined };
|
||||
}
|
||||
return grokCliCache;
|
||||
}
|
||||
|
||||
const result: GrokCliStatus = {
|
||||
installed: false,
|
||||
path: undefined,
|
||||
version: undefined,
|
||||
};
|
||||
const result: GrokCliStatus = grokCliCache
|
||||
? { ...grokCliCache }
|
||||
: {
|
||||
installed: false,
|
||||
path: undefined,
|
||||
version: undefined,
|
||||
};
|
||||
|
||||
try {
|
||||
const isWindows = process.platform === 'win32';
|
||||
const whichCmd = isWindows ? 'where grok' : 'which grok';
|
||||
if (!grokCliCache) {
|
||||
const isWindows = process.platform === 'win32';
|
||||
const whichCmd = isWindows ? 'where grok' : 'which grok';
|
||||
|
||||
const pathResult = execSync(whichCmd, {
|
||||
encoding: 'utf8',
|
||||
timeout: 5000,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
const pathResult = execSync(whichCmd, {
|
||||
encoding: 'utf8',
|
||||
timeout: 5000,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
|
||||
const grokPath = pathResult.trim().split('\n')[0]; // First result on Windows
|
||||
const grokPath = pathResult.trim().split('\n')[0];
|
||||
|
||||
if (grokPath) {
|
||||
result.installed = true;
|
||||
result.path = grokPath;
|
||||
if (grokPath) {
|
||||
result.installed = true;
|
||||
result.path = grokPath;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to get version
|
||||
if (result.installed && fetchVersion && result.version === undefined) {
|
||||
try {
|
||||
const versionResult = execSync('grok --version', {
|
||||
encoding: 'utf8',
|
||||
@@ -57,17 +69,17 @@ export function getGrokCliStatus(): GrokCliStatus {
|
||||
});
|
||||
result.version = versionResult.trim();
|
||||
} catch {
|
||||
// Version check failed, but CLI is installed
|
||||
result.version = 'unknown';
|
||||
}
|
||||
} else if (!fetchVersion) {
|
||||
result.version = undefined;
|
||||
}
|
||||
} catch {
|
||||
// Command not found - Grok CLI not installed
|
||||
}
|
||||
|
||||
// Cache result
|
||||
grokCliCache = result;
|
||||
return result;
|
||||
return fetchVersion ? result : { ...result, version: undefined };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,35 +20,47 @@ let opencodeCliCache: OpenCodeCliStatus | null = null;
|
||||
*
|
||||
* @returns OpenCode CLI status with path and version
|
||||
*/
|
||||
export function getOpenCodeCliStatus(): OpenCodeCliStatus {
|
||||
// Return cached result if available
|
||||
if (opencodeCliCache) {
|
||||
export function getOpenCodeCliStatus(options?: { fetchVersion?: boolean }): OpenCodeCliStatus {
|
||||
const fetchVersion = options?.fetchVersion !== false;
|
||||
|
||||
if (
|
||||
opencodeCliCache &&
|
||||
(!fetchVersion || opencodeCliCache.version !== undefined || !opencodeCliCache.installed)
|
||||
) {
|
||||
if (!fetchVersion) {
|
||||
return { ...opencodeCliCache, version: undefined };
|
||||
}
|
||||
return opencodeCliCache;
|
||||
}
|
||||
|
||||
const result: OpenCodeCliStatus = {
|
||||
installed: false,
|
||||
path: undefined,
|
||||
version: undefined,
|
||||
};
|
||||
const result: OpenCodeCliStatus = opencodeCliCache
|
||||
? { ...opencodeCliCache }
|
||||
: {
|
||||
installed: false,
|
||||
path: undefined,
|
||||
version: undefined,
|
||||
};
|
||||
|
||||
try {
|
||||
const isWindows = process.platform === 'win32';
|
||||
const whichCmd = isWindows ? 'where opencode' : 'which opencode';
|
||||
if (!opencodeCliCache) {
|
||||
const isWindows = process.platform === 'win32';
|
||||
const whichCmd = isWindows ? 'where opencode' : 'which opencode';
|
||||
|
||||
const pathResult = execSync(whichCmd, {
|
||||
encoding: 'utf8',
|
||||
timeout: 5000,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
const pathResult = execSync(whichCmd, {
|
||||
encoding: 'utf8',
|
||||
timeout: 5000,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
|
||||
const opencodePath = pathResult.trim().split('\n')[0]; // First result on Windows
|
||||
const opencodePath = pathResult.trim().split('\n')[0];
|
||||
|
||||
if (opencodePath) {
|
||||
result.installed = true;
|
||||
result.path = opencodePath;
|
||||
if (opencodePath) {
|
||||
result.installed = true;
|
||||
result.path = opencodePath;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to get version
|
||||
if (result.installed && fetchVersion && result.version === undefined) {
|
||||
try {
|
||||
const versionResult = execSync('opencode --version', {
|
||||
encoding: 'utf8',
|
||||
@@ -57,17 +69,17 @@ export function getOpenCodeCliStatus(): OpenCodeCliStatus {
|
||||
});
|
||||
result.version = versionResult.trim();
|
||||
} catch {
|
||||
// Version check failed, but CLI is installed
|
||||
result.version = 'unknown';
|
||||
}
|
||||
} else if (!fetchVersion) {
|
||||
result.version = undefined;
|
||||
}
|
||||
} catch {
|
||||
// Command not found - OpenCode CLI not installed
|
||||
}
|
||||
|
||||
// Cache result
|
||||
opencodeCliCache = result;
|
||||
return result;
|
||||
return fetchVersion ? result : { ...result, version: undefined };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -113,13 +113,23 @@ function applyCooldownStatus(
|
||||
};
|
||||
}
|
||||
|
||||
function getLegacyProviderStatuses(wsConfig: WebSearchConfigSnapshot): WebSearchCliInfo[] {
|
||||
const agyStatus = getAgyCliStatus();
|
||||
const geminiStatus = getGeminiCliStatus();
|
||||
const grokStatus = getGrokCliStatus();
|
||||
const opencodeStatus = getOpenCodeCliStatus();
|
||||
const geminiAuthed = geminiStatus.installed && isGeminiAuthenticated();
|
||||
function getLegacyProviderStatuses(
|
||||
wsConfig: WebSearchConfigSnapshot,
|
||||
options?: { includeVersions?: boolean }
|
||||
): WebSearchCliInfo[] {
|
||||
const fetchVersion = options?.includeVersions !== false;
|
||||
const agyEnabled = wsConfig.providers?.agy?.enabled ?? false;
|
||||
const geminiEnabled = wsConfig.providers?.gemini?.enabled ?? false;
|
||||
const grokEnabled = wsConfig.providers?.grok?.enabled ?? false;
|
||||
const opencodeEnabled = wsConfig.providers?.opencode?.enabled ?? false;
|
||||
|
||||
const agyStatus = agyEnabled ? getAgyCliStatus({ fetchVersion }) : { installed: false };
|
||||
const geminiStatus = geminiEnabled ? getGeminiCliStatus({ fetchVersion }) : { installed: false };
|
||||
const grokStatus = grokEnabled ? getGrokCliStatus({ fetchVersion }) : { installed: false };
|
||||
const opencodeStatus = opencodeEnabled
|
||||
? getOpenCodeCliStatus({ fetchVersion })
|
||||
: { installed: false };
|
||||
const geminiAuthed = geminiEnabled && geminiStatus.installed && isGeminiAuthenticated();
|
||||
return [
|
||||
{
|
||||
id: 'agy',
|
||||
@@ -198,7 +208,8 @@ function getLegacyProviderStatuses(wsConfig: WebSearchConfigSnapshot): WebSearch
|
||||
* Get all WebSearch providers with their current status.
|
||||
*/
|
||||
export function getWebSearchCliProviders(
|
||||
wsConfig: WebSearchConfigSnapshot = getWebSearchConfig()
|
||||
wsConfig: WebSearchConfigSnapshot = getWebSearchConfig(),
|
||||
options?: { includeVersions?: boolean }
|
||||
): WebSearchCliInfo[] {
|
||||
const apiKeyStates = getWebSearchApiKeyStates();
|
||||
const cooldowns = readProviderCooldowns();
|
||||
@@ -284,7 +295,7 @@ export function getWebSearchCliProviders(
|
||||
},
|
||||
];
|
||||
|
||||
return [...providers, ...getLegacyProviderStatuses(wsConfig)].map((provider) =>
|
||||
return [...providers, ...getLegacyProviderStatuses(wsConfig, options)].map((provider) =>
|
||||
applyCooldownStatus(provider, cooldowns)
|
||||
);
|
||||
}
|
||||
@@ -359,7 +370,7 @@ export function buildWebSearchReadiness(
|
||||
export function getWebSearchReadiness(
|
||||
wsConfig: WebSearchConfigSnapshot = getWebSearchConfig()
|
||||
): WebSearchStatus {
|
||||
const providers = getWebSearchCliProviders(wsConfig);
|
||||
const providers = getWebSearchCliProviders(wsConfig, { includeVersions: false });
|
||||
return buildWebSearchReadiness(wsConfig.enabled, providers);
|
||||
}
|
||||
|
||||
|
||||
@@ -301,6 +301,113 @@ describe('websearch readiness', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('does not invoke legacy CLI status probes when those providers are disabled', () => {
|
||||
const getConfigSpy = spyOn(unifiedConfigLoader, 'getWebSearchConfig').mockReturnValue({
|
||||
enabled: true,
|
||||
providers: {
|
||||
exa: { enabled: false, max_results: 5 },
|
||||
tavily: { enabled: false, max_results: 5 },
|
||||
brave: { enabled: false, max_results: 5 },
|
||||
searxng: { enabled: false, url: '', max_results: 5 },
|
||||
duckduckgo: { enabled: true, max_results: 5 },
|
||||
agy: { enabled: false },
|
||||
gemini: { enabled: false },
|
||||
grok: { enabled: false },
|
||||
opencode: { enabled: false },
|
||||
},
|
||||
} as any);
|
||||
const apiKeySpy = spyOn(providerSecrets, 'getWebSearchApiKeyStates').mockReturnValue({
|
||||
exa: { envVar: 'EXA_API_KEY', configured: false, available: false, source: 'none' },
|
||||
tavily: { envVar: 'TAVILY_API_KEY', configured: false, available: false, source: 'none' },
|
||||
brave: { envVar: 'BRAVE_API_KEY', configured: false, available: false, source: 'none' },
|
||||
});
|
||||
const agyStatusSpy = spyOn(agyCli, 'getAgyCliStatus').mockReturnValue({
|
||||
installed: false,
|
||||
} as any);
|
||||
const geminiStatusSpy = spyOn(geminiCli, 'getGeminiCliStatus').mockReturnValue({
|
||||
installed: false,
|
||||
} as any);
|
||||
const geminiAuthSpy = spyOn(geminiCli, 'isGeminiAuthenticated').mockReturnValue(false);
|
||||
const grokStatusSpy = spyOn(grokCli, 'getGrokCliStatus').mockReturnValue({
|
||||
installed: false,
|
||||
} as any);
|
||||
const opencodeStatusSpy = spyOn(opencodeCli, 'getOpenCodeCliStatus').mockReturnValue({
|
||||
installed: false,
|
||||
} as any);
|
||||
|
||||
try {
|
||||
getWebSearchCliProviders();
|
||||
expect(agyStatusSpy).not.toHaveBeenCalled();
|
||||
expect(geminiStatusSpy).not.toHaveBeenCalled();
|
||||
expect(geminiAuthSpy).not.toHaveBeenCalled();
|
||||
expect(grokStatusSpy).not.toHaveBeenCalled();
|
||||
expect(opencodeStatusSpy).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
getConfigSpy.mockRestore();
|
||||
apiKeySpy.mockRestore();
|
||||
agyStatusSpy.mockRestore();
|
||||
geminiStatusSpy.mockRestore();
|
||||
geminiAuthSpy.mockRestore();
|
||||
grokStatusSpy.mockRestore();
|
||||
opencodeStatusSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
it('does not spawn --version when includeVersions is false', () => {
|
||||
const wsConfig = {
|
||||
enabled: true,
|
||||
providers: {
|
||||
exa: { enabled: false, max_results: 5 },
|
||||
tavily: { enabled: false, max_results: 5 },
|
||||
brave: { enabled: false, max_results: 5 },
|
||||
searxng: { enabled: false, url: '', max_results: 5 },
|
||||
duckduckgo: { enabled: false, max_results: 5 },
|
||||
agy: { enabled: true, model: 'gemini-2.5-flash', timeout: 90 },
|
||||
gemini: { enabled: true },
|
||||
grok: { enabled: true },
|
||||
opencode: { enabled: true },
|
||||
},
|
||||
};
|
||||
const apiKeySpy = spyOn(providerSecrets, 'getWebSearchApiKeyStates').mockReturnValue({
|
||||
exa: { envVar: 'EXA_API_KEY', configured: false, available: false, source: 'none' },
|
||||
tavily: { envVar: 'TAVILY_API_KEY', configured: false, available: false, source: 'none' },
|
||||
brave: { envVar: 'BRAVE_API_KEY', configured: false, available: false, source: 'none' },
|
||||
});
|
||||
const agyStatusSpy = spyOn(agyCli, 'getAgyCliStatus').mockReturnValue({
|
||||
installed: true,
|
||||
version: undefined,
|
||||
});
|
||||
const geminiStatusSpy = spyOn(geminiCli, 'getGeminiCliStatus').mockReturnValue({
|
||||
installed: false,
|
||||
version: undefined,
|
||||
});
|
||||
const geminiAuthSpy = spyOn(geminiCli, 'isGeminiAuthenticated').mockReturnValue(false);
|
||||
const grokStatusSpy = spyOn(grokCli, 'getGrokCliStatus').mockReturnValue({
|
||||
installed: false,
|
||||
version: undefined,
|
||||
});
|
||||
const opencodeStatusSpy = spyOn(opencodeCli, 'getOpenCodeCliStatus').mockReturnValue({
|
||||
installed: false,
|
||||
version: undefined,
|
||||
});
|
||||
|
||||
try {
|
||||
getWebSearchCliProviders(wsConfig as Parameters<typeof getWebSearchCliProviders>[0], {
|
||||
includeVersions: false,
|
||||
});
|
||||
expect(agyStatusSpy).toHaveBeenCalledWith({ fetchVersion: false });
|
||||
expect(geminiStatusSpy).toHaveBeenCalledWith({ fetchVersion: false });
|
||||
expect(grokStatusSpy).toHaveBeenCalledWith({ fetchVersion: false });
|
||||
expect(opencodeStatusSpy).toHaveBeenCalledWith({ fetchVersion: false });
|
||||
} finally {
|
||||
apiKeySpy.mockRestore();
|
||||
agyStatusSpy.mockRestore();
|
||||
geminiStatusSpy.mockRestore();
|
||||
geminiAuthSpy.mockRestore();
|
||||
grokStatusSpy.mockRestore();
|
||||
opencodeStatusSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it('treats cooled-down providers as temporarily unavailable in readiness status', () => {
|
||||
const tempHome = mkdtempSync(join(tmpdir(), 'websearch-status-cooldown-'));
|
||||
const statePath = join(tempHome, '.ccs', 'cache', 'websearch-provider-state.json');
|
||||
|
||||
Reference in new issue
Block a user