mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-20 00:14:58 +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
@@ -2,6 +2,7 @@ import { describe, expect, it, spyOn } from 'bun:test';
|
||||
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import * as agyCli from '../../../../src/utils/websearch/agy';
|
||||
import * as geminiCli from '../../../../src/utils/websearch/gemini-cli';
|
||||
import * as grokCli from '../../../../src/utils/websearch/grok-cli';
|
||||
import * as opencodeCli from '../../../../src/utils/websearch/opencode-cli';
|
||||
@@ -13,7 +14,9 @@ import {
|
||||
} from '../../../../src/utils/websearch/status';
|
||||
import type { WebSearchCliInfo } from '../../../../src/utils/websearch/types';
|
||||
|
||||
function provider(overrides: Partial<WebSearchCliInfo> & Pick<WebSearchCliInfo, 'id' | 'name'>): WebSearchCliInfo {
|
||||
function provider(
|
||||
overrides: Partial<WebSearchCliInfo> & Pick<WebSearchCliInfo, 'id' | 'name'>
|
||||
): WebSearchCliInfo {
|
||||
return {
|
||||
id: overrides.id,
|
||||
kind: overrides.kind ?? 'backend',
|
||||
@@ -151,6 +154,10 @@ describe('websearch readiness', () => {
|
||||
installed: false,
|
||||
version: null,
|
||||
} as any);
|
||||
const agyStatusSpy = spyOn(agyCli, 'getAgyCliStatus').mockReturnValue({
|
||||
installed: false,
|
||||
version: null,
|
||||
} as any);
|
||||
|
||||
try {
|
||||
const providers = getWebSearchCliProviders();
|
||||
@@ -166,6 +173,7 @@ describe('websearch readiness', () => {
|
||||
geminiAuthSpy.mockRestore();
|
||||
grokStatusSpy.mockRestore();
|
||||
opencodeStatusSpy.mockRestore();
|
||||
agyStatusSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -205,6 +213,10 @@ describe('websearch readiness', () => {
|
||||
installed: false,
|
||||
version: null,
|
||||
} as any);
|
||||
const agyStatusSpy = spyOn(agyCli, 'getAgyCliStatus').mockReturnValue({
|
||||
installed: false,
|
||||
version: null,
|
||||
} as any);
|
||||
|
||||
try {
|
||||
const providers = getWebSearchCliProviders();
|
||||
@@ -220,6 +232,72 @@ describe('websearch readiness', () => {
|
||||
geminiAuthSpy.mockRestore();
|
||||
grokStatusSpy.mockRestore();
|
||||
opencodeStatusSpy.mockRestore();
|
||||
agyStatusSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it('exposes Antigravity (agy) as a recommended CLI provider when enabled and installed', () => {
|
||||
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: false, max_results: 5 },
|
||||
agy: { enabled: true, model: 'gemini-2.5-flash', timeout: 90 },
|
||||
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: true,
|
||||
version: '1.0.13',
|
||||
} as any);
|
||||
const geminiStatusSpy = spyOn(geminiCli, 'getGeminiCliStatus').mockReturnValue({
|
||||
installed: false,
|
||||
version: null,
|
||||
} as any);
|
||||
const geminiAuthSpy = spyOn(geminiCli, 'isGeminiAuthenticated').mockReturnValue(false);
|
||||
const grokStatusSpy = spyOn(grokCli, 'getGrokCliStatus').mockReturnValue({
|
||||
installed: false,
|
||||
version: null,
|
||||
} as any);
|
||||
const opencodeStatusSpy = spyOn(opencodeCli, 'getOpenCodeCliStatus').mockReturnValue({
|
||||
installed: false,
|
||||
version: null,
|
||||
} as any);
|
||||
|
||||
try {
|
||||
const providers = getWebSearchCliProviders();
|
||||
const agy = providers.find((entry) => entry.id === 'agy');
|
||||
|
||||
expect(agy?.name).toBe('Antigravity CLI');
|
||||
expect(agy?.kind).toBe('legacy-cli');
|
||||
expect(agy?.command).toBe('agy');
|
||||
expect(agy?.enabled).toBe(true);
|
||||
expect(agy?.available).toBe(true);
|
||||
expect(agy?.requiresApiKey).toBe(false);
|
||||
expect(agy?.installCommand).toContain('antigravity.google');
|
||||
expect(agy?.detail).toContain('1.0.13');
|
||||
|
||||
const readiness = buildWebSearchReadiness(true, providers);
|
||||
expect(readiness.readiness).toBe('ready');
|
||||
expect(readiness.message).toContain('Antigravity CLI');
|
||||
} finally {
|
||||
getConfigSpy.mockRestore();
|
||||
apiKeySpy.mockRestore();
|
||||
agyStatusSpy.mockRestore();
|
||||
geminiStatusSpy.mockRestore();
|
||||
geminiAuthSpy.mockRestore();
|
||||
grokStatusSpy.mockRestore();
|
||||
opencodeStatusSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -293,6 +371,10 @@ describe('websearch readiness', () => {
|
||||
installed: false,
|
||||
version: null,
|
||||
} as any);
|
||||
const agyStatusSpy = spyOn(agyCli, 'getAgyCliStatus').mockReturnValue({
|
||||
installed: false,
|
||||
version: null,
|
||||
} as any);
|
||||
|
||||
try {
|
||||
const providers = getWebSearchCliProviders();
|
||||
@@ -313,6 +395,7 @@ describe('websearch readiness', () => {
|
||||
geminiAuthSpy.mockRestore();
|
||||
grokStatusSpy.mockRestore();
|
||||
opencodeStatusSpy.mockRestore();
|
||||
agyStatusSpy.mockRestore();
|
||||
|
||||
if (originalCcsHome === undefined) {
|
||||
delete process.env.CCS_HOME;
|
||||
|
||||
Reference in new issue
Block a user