mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-20 02:17:05 +00:00
feat(websearch): add agy provider and deprecate gemini cli fallback (#1607)
Google retired the gemini CLI on 2026-06-18, so the gemini websearch fallback no longer works. Add agy (Antigravity) as the primary CLI websearch provider end to end: runtime spawn in websearch-transformer.cjs (agy --dangerously-skip-permissions --print-timeout Ns -p), detection (agy.ts), config schema and defaults, hook env, status, dashboard persistence, and docs. The legacy gemini provider stays present but is marked deprecated for enterprise users who retain access. Image analysis already supported the agy provider (no change). The CLIProxy gemini quota fetcher (Gemini API OAuth, not the cli binary) is untouched. Validation: typecheck, lint, and the websearch + config + web-server unit suites pass.
This commit is contained in:
1 parent
608ff97e35
commit
9dd9bf2978
18 files changed
+395
-43
No files matched your search
@@ -9,8 +9,9 @@
|
||||
* - SearXNG JSON API
|
||||
* - DuckDuckGo HTML search
|
||||
*
|
||||
* Legacy compatibility fallback:
|
||||
* - Gemini CLI
|
||||
* Optional LLM CLI fallback:
|
||||
* - Antigravity CLI (agy) - recommended (Gemini CLI successor)
|
||||
* - Gemini CLI - deprecated (Google retired the gemini CLI on 2026-06-18)
|
||||
* - OpenCode
|
||||
* - Grok CLI
|
||||
*/
|
||||
@@ -49,6 +50,11 @@ const SHARED_INSTRUCTIONS = `Instructions:
|
||||
7. Format output clearly with sections if the topic is complex`;
|
||||
|
||||
const PROVIDER_CONFIG = {
|
||||
agy: {
|
||||
model: 'gemini-2.5-flash',
|
||||
toolInstruction: 'Use your web search tool to find current information.',
|
||||
quirks: null,
|
||||
},
|
||||
gemini: {
|
||||
model: 'gemini-2.5-flash',
|
||||
toolInstruction: 'Use the google_web_search tool to find current information.',
|
||||
@@ -124,7 +130,10 @@ function readProviderState() {
|
||||
|
||||
const parsed = JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
||||
const cooldowns =
|
||||
parsed && typeof parsed === 'object' && parsed.cooldowns && typeof parsed.cooldowns === 'object'
|
||||
parsed &&
|
||||
typeof parsed === 'object' &&
|
||||
parsed.cooldowns &&
|
||||
typeof parsed.cooldowns === 'object'
|
||||
? parsed.cooldowns
|
||||
: {};
|
||||
return { cooldowns };
|
||||
@@ -856,6 +865,66 @@ async function tryDuckDuckGoSearch(query, timeoutSec = DEFAULT_TIMEOUT_SEC) {
|
||||
}
|
||||
}
|
||||
|
||||
function runAgyCommand(args, timeoutMs) {
|
||||
const result = spawnSync('agy', args, {
|
||||
encoding: 'utf8',
|
||||
timeout: timeoutMs,
|
||||
maxBuffer: 1024 * 1024 * 2,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
// Never route query-derived prompts through a shell. Node concatenates
|
||||
// arguments for shell-backed Windows spawns, which lets shell metacharacters
|
||||
// in WebSearch queries escape the intended CLI invocation.
|
||||
shell: false,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
if (result.error.code === 'ENOENT')
|
||||
return { success: false, error: 'Antigravity CLI (agy) not installed' };
|
||||
throw result.error;
|
||||
}
|
||||
if (result.status !== 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: (result.stderr || '').trim() || `Antigravity CLI exited with code ${result.status}`,
|
||||
};
|
||||
}
|
||||
|
||||
const output = (result.stdout || '').trim();
|
||||
if (!output || output.length < MIN_VALID_RESPONSE_LENGTH) {
|
||||
return { success: false, error: 'Empty or too short response from Antigravity CLI' };
|
||||
}
|
||||
return { success: true, content: output };
|
||||
}
|
||||
|
||||
function tryAgySearch(query, timeoutSec = DEFAULT_TIMEOUT_SEC) {
|
||||
try {
|
||||
const timeoutMs = timeoutSec * 1000;
|
||||
const model = process.env.CCS_WEBSEARCH_AGY_MODEL || PROVIDER_CONFIG.agy.model;
|
||||
const prompt = buildPrompt('agy', query);
|
||||
// gemini flag mapping: `--yolo` -> `--dangerously-skip-permissions`, `-m` -> `--model`,
|
||||
// shell `timeout N` -> native `--print-timeout Ns`. The prompt is passed to `-p` (print mode).
|
||||
const args = [
|
||||
'--model',
|
||||
model,
|
||||
'--dangerously-skip-permissions',
|
||||
'--print-timeout',
|
||||
`${timeoutSec}s`,
|
||||
'-p',
|
||||
prompt,
|
||||
];
|
||||
|
||||
debug(`Executing Antigravity (agy) fallback with model ${model}`);
|
||||
return runAgyCommand(args, timeoutMs);
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.killed
|
||||
? 'Antigravity CLI timed out'
|
||||
: error.message || 'Unknown Antigravity error',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function shouldRetryGeminiWithLegacyPrompt(errorMessage) {
|
||||
const lower = (errorMessage || '').toLowerCase();
|
||||
return (
|
||||
@@ -1042,6 +1111,12 @@ function getConfiguredProviders() {
|
||||
available: () => isProviderEnabled('duckduckgo'),
|
||||
fn: tryDuckDuckGoSearch,
|
||||
},
|
||||
{
|
||||
name: 'Antigravity CLI',
|
||||
id: 'agy',
|
||||
available: () => isProviderEnabled('agy') && isCliAvailable('agy'),
|
||||
fn: tryAgySearch,
|
||||
},
|
||||
{
|
||||
name: 'Gemini CLI',
|
||||
id: 'gemini',
|
||||
@@ -1230,7 +1305,9 @@ async function runProviderWithPolicy(provider, query, timeoutSec, fingerprint) {
|
||||
}
|
||||
|
||||
function getActiveProviders() {
|
||||
return getConfiguredProviders().filter((provider) => !getProviderCooldown(provider.id) && provider.available());
|
||||
return getConfiguredProviders().filter(
|
||||
(provider) => !getProviderCooldown(provider.id) && provider.available()
|
||||
);
|
||||
}
|
||||
|
||||
function getActiveProviderIds() {
|
||||
|
||||
Reference in new issue
Block a user