fix gemini oauth validation site proxy (#298)

This commit is contained in:
Cita
2026-03-28 20:06:20 +08:00
committed by GitHub
parent 4d4684a39b
commit 785ee93505
3 changed files with 67 additions and 6 deletions
@@ -75,7 +75,12 @@ describe('refreshModelsForAccount credential discovery', () => {
await db.delete(schema.modelAvailability).run();
await db.delete(schema.accountTokens).run();
await db.delete(schema.accounts).run();
await db.delete(schema.settings).run();
await db.delete(schema.sites).run();
const { config } = await import('../config.js');
config.systemProxyUrl = '';
const { invalidateSiteProxyCache } = await import('./siteProxy.js');
invalidateSiteProxyCache();
});
afterAll(() => {
@@ -874,6 +879,62 @@ describe('refreshModelsForAccount credential discovery', () => {
});
});
it('inherits site system proxy for gemini oauth validation requests', async () => {
getApiTokenMock.mockResolvedValue(null);
getModelsMock.mockRejectedValue(new Error('gemini oauth validation should not call adapter.getModels'));
undiciFetchMock.mockResolvedValue({
ok: true,
status: 200,
json: async () => ({
state: 'ENABLED',
}),
text: async () => JSON.stringify({ state: 'ENABLED' }),
});
const { config } = await import('../config.js');
config.systemProxyUrl = 'http://127.0.0.1:1081';
const site = await db.insert(schema.sites).values({
name: 'gemini-site-proxy-site',
url: 'https://cloudcode-pa.googleapis.com',
platform: 'gemini-cli',
status: 'active',
useSystemProxy: true,
}).returning().get();
const account = await db.insert(schema.accounts).values({
siteId: site.id,
username: 'gemini-site-proxy-user@example.com',
accessToken: 'gemini-access-token',
apiToken: null,
status: 'active',
extraConfig: JSON.stringify({
credentialMode: 'session',
oauth: {
provider: 'gemini-cli',
projectId: 'project-site-proxy-demo',
email: 'gemini-site-proxy-user@example.com',
},
}),
}).returning().get();
const result = await refreshModelsForAccount(account.id);
expect(result).toMatchObject({
accountId: account.id,
refreshed: true,
status: 'success',
modelCount: expect.any(Number),
});
expect(undiciFetchMock).toHaveBeenCalledTimes(1);
expect(proxyAgentCtorMock).toHaveBeenCalledWith('http://127.0.0.1:1081');
expect(String(undiciFetchMock.mock.calls[0]?.[0] || '')).toContain('/projects/project-site-proxy-demo/services/');
expect(undiciFetchMock.mock.calls[0]?.[1]).toMatchObject({
method: 'GET',
dispatcher: expect.any(MockProxyAgent),
});
});
it('refreshes gemini oauth access token during validation through singleflight and reuses the refreshed account state', async () => {
getApiTokenMock.mockResolvedValue(null);
getModelsMock.mockRejectedValue(new Error('gemini oauth validation should not call adapter.getModels'));
+2 -2
View File
@@ -529,7 +529,7 @@ export async function refreshModelsForAccount(
try {
await withTimeout(
() => withAccountProxyOverride(accountProxyUrl,
() => validateGeminiCliOauthConnection({ account: discoveryAccount })),
() => validateGeminiCliOauthConnection({ site, account: discoveryAccount })),
MODEL_DISCOVERY_TIMEOUT_MS,
`gemini cli oauth validation timeout (${Math.round(MODEL_DISCOVERY_TIMEOUT_MS / 1000)}s)`,
);
@@ -548,7 +548,7 @@ export async function refreshModelsForAccount(
};
await withTimeout(
() => withAccountProxyOverride(accountProxyUrl,
() => validateGeminiCliOauthConnection({ account: discoveryAccount })),
() => validateGeminiCliOauthConnection({ site, account: discoveryAccount })),
MODEL_DISCOVERY_TIMEOUT_MS,
`gemini cli oauth validation timeout (${Math.round(MODEL_DISCOVERY_TIMEOUT_MS / 1000)}s)`,
);
@@ -1,7 +1,6 @@
import { fetch } from 'undici';
import { schema } from '../db/index.js';
import { getProxyUrlFromExtraConfig } from './accountExtraConfig.js';
import { withExplicitProxyRequestInit, withSiteRecordProxyRequestInit } from './siteProxy.js';
import { withSiteRecordProxyRequestInit } from './siteProxy.js';
import { getOauthInfoFromAccount } from './oauth/oauthAccount.js';
import { CLAUDE_DEFAULT_ANTHROPIC_VERSION } from './oauth/claudeProvider.js';
import {
@@ -175,6 +174,7 @@ export async function discoverClaudeModelsFromCloud(input: {
}
export async function validateGeminiCliOauthConnection(input: {
site: PlatformDiscoverySite;
account: PlatformDiscoveryAccount;
}): Promise<void> {
const accessToken = (input.account.accessToken || '').trim();
@@ -188,7 +188,7 @@ export async function validateGeminiCliOauthConnection(input: {
}
const response = await fetch(
`https://serviceusage.googleapis.com/v1/projects/${encodeURIComponent(projectId)}/services/${encodeURIComponent(GEMINI_CLI_REQUIRED_SERVICE)}`,
withExplicitProxyRequestInit(getProxyUrlFromExtraConfig(input.account.extraConfig), {
withSiteRecordProxyRequestInit(input.site, {
method: 'GET',
headers: {
Authorization: `Bearer ${accessToken}`,
@@ -196,7 +196,7 @@ export async function validateGeminiCliOauthConnection(input: {
'User-Agent': GEMINI_CLI_USER_AGENT,
'X-Goog-Api-Client': GEMINI_CLI_GOOGLE_API_CLIENT,
},
}, true),
}),
);
if (!response.ok) {
const text = await response.text().catch(() => '');