mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 14:10:37 -07:00
fix(ui): handle headless execution in credits and upgrade dialogs (#21850)
This commit is contained in:
@@ -11,6 +11,7 @@ import { createMockCommandContext } from '../../test-utils/mockCommandContext.js
|
||||
import {
|
||||
AuthType,
|
||||
openBrowserSecurely,
|
||||
shouldLaunchBrowser,
|
||||
UPGRADE_URL_PAGE,
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
@@ -20,6 +21,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
return {
|
||||
...actual,
|
||||
openBrowserSecurely: vi.fn(),
|
||||
shouldLaunchBrowser: vi.fn().mockReturnValue(true),
|
||||
UPGRADE_URL_PAGE: 'https://goo.gle/set-up-gemini-code-assist',
|
||||
};
|
||||
});
|
||||
@@ -96,4 +98,21 @@ describe('upgradeCommand', () => {
|
||||
content: 'Failed to open upgrade page: Failed to open',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return URL message when shouldLaunchBrowser returns false', async () => {
|
||||
vi.mocked(shouldLaunchBrowser).mockReturnValue(false);
|
||||
|
||||
if (!upgradeCommand.action) {
|
||||
throw new Error('The upgrade command must have an action.');
|
||||
}
|
||||
|
||||
const result = await upgradeCommand.action(mockContext, '');
|
||||
|
||||
expect(result).toEqual({
|
||||
type: 'message',
|
||||
messageType: 'info',
|
||||
content: `Please open this URL in a browser: ${UPGRADE_URL_PAGE}`,
|
||||
});
|
||||
expect(openBrowserSecurely).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import {
|
||||
AuthType,
|
||||
openBrowserSecurely,
|
||||
shouldLaunchBrowser,
|
||||
UPGRADE_URL_PAGE,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { SlashCommand } from './types.js';
|
||||
@@ -35,6 +36,14 @@ export const upgradeCommand: SlashCommand = {
|
||||
};
|
||||
}
|
||||
|
||||
if (!shouldLaunchBrowser()) {
|
||||
return {
|
||||
type: 'message',
|
||||
messageType: 'info',
|
||||
content: `Please open this URL in a browser: ${UPGRADE_URL_PAGE}`,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
await openBrowserSecurely(UPGRADE_URL_PAGE);
|
||||
} catch (error) {
|
||||
|
||||
@@ -18,6 +18,12 @@ Spinner Connecting to MCP servers... (0/5) - Waiting for: s1, s2, s3, +2 more
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ConfigInitDisplay > truncates list of waiting servers if too many 2`] = `
|
||||
"
|
||||
Spinner Connecting to MCP servers... (0/5) - Waiting for: s1, s2, s3, +2 more
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ConfigInitDisplay > updates message on McpClientUpdate event 1`] = `
|
||||
"
|
||||
Spinner Connecting to MCP servers... (1/2) - Waiting for: server2
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
shouldAutoUseCredits,
|
||||
shouldShowOverageMenu,
|
||||
shouldShowEmptyWalletMenu,
|
||||
shouldLaunchBrowser,
|
||||
logBillingEvent,
|
||||
G1_CREDIT_TYPE,
|
||||
UserTierId,
|
||||
@@ -32,6 +33,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
shouldShowEmptyWalletMenu: vi.fn(),
|
||||
logBillingEvent: vi.fn(),
|
||||
openBrowserSecurely: vi.fn(),
|
||||
shouldLaunchBrowser: vi.fn().mockReturnValue(true),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -237,4 +239,49 @@ describe('handleCreditsFlow', () => {
|
||||
expect(isDialogPending.current).toBe(false);
|
||||
expect(mockSetEmptyWalletRequest).toHaveBeenCalledWith(null);
|
||||
});
|
||||
|
||||
describe('headless mode (shouldLaunchBrowser=false)', () => {
|
||||
beforeEach(() => {
|
||||
vi.mocked(shouldLaunchBrowser).mockReturnValue(false);
|
||||
});
|
||||
|
||||
it('should show manage URL in history when manage selected in headless mode', async () => {
|
||||
vi.mocked(shouldShowOverageMenu).mockReturnValue(true);
|
||||
|
||||
const flowPromise = handleCreditsFlow(makeArgs());
|
||||
const request = mockSetOverageMenuRequest.mock.calls[0][0];
|
||||
request.resolve('manage');
|
||||
const result = await flowPromise;
|
||||
|
||||
expect(result).toBe('stop');
|
||||
expect(mockHistoryManager.addItem).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: MessageType.INFO,
|
||||
text: expect.stringContaining('Please open this URL in a browser:'),
|
||||
}),
|
||||
expect.any(Number),
|
||||
);
|
||||
});
|
||||
|
||||
it('should show credits URL in history when get_credits selected in headless mode', async () => {
|
||||
vi.mocked(shouldShowEmptyWalletMenu).mockReturnValue(true);
|
||||
|
||||
const flowPromise = handleCreditsFlow(makeArgs());
|
||||
const request = mockSetEmptyWalletRequest.mock.calls[0][0];
|
||||
|
||||
// Trigger onGetCredits callback and wait for it
|
||||
await request.onGetCredits();
|
||||
|
||||
expect(mockHistoryManager.addItem).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: MessageType.INFO,
|
||||
text: expect.stringContaining('Please open this URL in a browser:'),
|
||||
}),
|
||||
expect.any(Number),
|
||||
);
|
||||
|
||||
request.resolve('get_credits');
|
||||
await flowPromise;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
shouldShowOverageMenu,
|
||||
shouldShowEmptyWalletMenu,
|
||||
openBrowserSecurely,
|
||||
shouldLaunchBrowser,
|
||||
logBillingEvent,
|
||||
OverageMenuShownEvent,
|
||||
OverageOptionSelectedEvent,
|
||||
@@ -159,10 +160,23 @@ async function handleOverageMenu(
|
||||
case 'use_fallback':
|
||||
return 'retry_always';
|
||||
|
||||
case 'manage':
|
||||
case 'manage': {
|
||||
logCreditPurchaseClick(config, 'manage', usageLimitReachedModel);
|
||||
await openG1Url('activity', G1_UTM_CAMPAIGNS.MANAGE_ACTIVITY);
|
||||
const manageUrl = await openG1Url(
|
||||
'activity',
|
||||
G1_UTM_CAMPAIGNS.MANAGE_ACTIVITY,
|
||||
);
|
||||
if (manageUrl) {
|
||||
args.historyManager.addItem(
|
||||
{
|
||||
type: MessageType.INFO,
|
||||
text: `Please open this URL in a browser: ${manageUrl}`,
|
||||
},
|
||||
Date.now(),
|
||||
);
|
||||
}
|
||||
return 'stop';
|
||||
}
|
||||
|
||||
case 'stop':
|
||||
default:
|
||||
@@ -205,13 +219,25 @@ async function handleEmptyWalletMenu(
|
||||
failedModel: usageLimitReachedModel,
|
||||
fallbackModel,
|
||||
resetTime,
|
||||
onGetCredits: () => {
|
||||
onGetCredits: async () => {
|
||||
logCreditPurchaseClick(
|
||||
config,
|
||||
'empty_wallet_menu',
|
||||
usageLimitReachedModel,
|
||||
);
|
||||
void openG1Url('credits', G1_UTM_CAMPAIGNS.EMPTY_WALLET_ADD_CREDITS);
|
||||
const creditsUrl = await openG1Url(
|
||||
'credits',
|
||||
G1_UTM_CAMPAIGNS.EMPTY_WALLET_ADD_CREDITS,
|
||||
);
|
||||
if (creditsUrl) {
|
||||
args.historyManager.addItem(
|
||||
{
|
||||
type: MessageType.INFO,
|
||||
text: `Please open this URL in a browser: ${creditsUrl}`,
|
||||
},
|
||||
Date.now(),
|
||||
);
|
||||
}
|
||||
},
|
||||
resolve,
|
||||
});
|
||||
@@ -272,11 +298,16 @@ function logCreditPurchaseClick(
|
||||
async function openG1Url(
|
||||
path: 'activity' | 'credits',
|
||||
campaign: string,
|
||||
): Promise<void> {
|
||||
): Promise<string | undefined> {
|
||||
try {
|
||||
const userEmail = new UserAccountManager().getCachedGoogleAccount() ?? '';
|
||||
await openBrowserSecurely(buildG1Url(path, userEmail, campaign));
|
||||
const url = buildG1Url(path, userEmail, campaign);
|
||||
if (!shouldLaunchBrowser()) {
|
||||
return url;
|
||||
}
|
||||
await openBrowserSecurely(url);
|
||||
} catch {
|
||||
// Ignore browser open errors
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user