This commit is contained in:
SALhik 2026-04-21 12:32:26 +09:00 committed by GitHub
commit 6b3617e665
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 89 additions and 5 deletions

View file

@ -347,7 +347,7 @@ describe('CodeAssistServer', () => {
});
it('should construct the default URL correctly', () => {
const server = new CodeAssistServer({} as never);
const server = new CodeAssistServer({} as never, 'test-project');
const url = server.getMethodUrl('testMethod');
expect(url).toBe(
'https://cloudcode-pa.googleapis.com/v1internal:testMethod',
@ -356,26 +356,79 @@ describe('CodeAssistServer', () => {
it('should use the CODE_ASSIST_ENDPOINT environment variable if set', () => {
process.env['CODE_ASSIST_ENDPOINT'] = 'https://custom-endpoint.com';
const server = new CodeAssistServer({} as never);
const server = new CodeAssistServer({} as never, 'test-project');
const url = server.getMethodUrl('testMethod');
expect(url).toBe('https://custom-endpoint.com/v1internal:testMethod');
});
it('should use the CODE_ASSIST_API_VERSION environment variable if set', () => {
process.env['CODE_ASSIST_API_VERSION'] = 'v2beta';
const server = new CodeAssistServer({} as never);
const server = new CodeAssistServer({} as never, 'test-project');
const url = server.getMethodUrl('testMethod');
expect(url).toBe('https://cloudcode-pa.googleapis.com/v2beta:testMethod');
});
it('should use default value if CODE_ASSIST_API_VERSION env var is empty', () => {
process.env['CODE_ASSIST_API_VERSION'] = '';
const server = new CodeAssistServer({} as never);
const server = new CodeAssistServer({} as never, 'test-project');
const url = server.getMethodUrl('testMethod');
expect(url).toBe(
'https://cloudcode-pa.googleapis.com/v1internal:testMethod',
);
});
it('should use standard API endpoint when no projectId is provided', () => {
const server = new CodeAssistServer({} as never);
const url = server.getMethodUrl('testMethod');
expect(url).toBe(
'https://generativelanguage.googleapis.com/v1beta:testMethod',
);
});
});
describe('loadCodeAssist project hijacking prevention', () => {
it('should strip cloudaicompanionProject from response when no projectId is set', async () => {
const mockRequest = vi.fn();
const client = { request: mockRequest } as unknown as OAuth2Client;
const server = new CodeAssistServer(client, undefined, {}, undefined);
mockRequest.mockResolvedValue({
data: {
cloudaicompanionProject: 'ghost-project-id',
currentTier: { id: 'standard-tier' },
},
});
const result = await server.loadCodeAssist({
metadata: { ideType: 'IDE_UNSPECIFIED' },
});
expect(result.cloudaicompanionProject).toBeUndefined();
});
it('should preserve cloudaicompanionProject when projectId is explicitly set', async () => {
const mockRequest = vi.fn();
const client = { request: mockRequest } as unknown as OAuth2Client;
const server = new CodeAssistServer(
client,
'explicit-project',
{},
undefined,
);
mockRequest.mockResolvedValue({
data: {
cloudaicompanionProject: 'server-project-id',
currentTier: { id: 'standard-tier' },
},
});
const result = await server.loadCodeAssist({
metadata: { ideType: 'IDE_UNSPECIFIED' },
});
expect(result.cloudaicompanionProject).toBe('server-project-id');
});
});
it('should call the generateContentStream endpoint and parse SSE', async () => {

View file

@ -264,10 +264,29 @@ export class CodeAssistServer implements ContentGenerator {
req: LoadCodeAssistRequest,
): Promise<LoadCodeAssistResponse> {
try {
return await this.requestPost<LoadCodeAssistResponse>(
const res = await this.requestPost<LoadCodeAssistResponse>(
'loadCodeAssist',
req,
);
/**
* FIX: Prevent automatic project hijacking for personal users.
* For Google One AI Premium subscribers, the backend may return a
* "shadow" project ID (e.g., master-impulse-jrkws) in
* cloudaicompanionProject. If adopted, this forces the CLI to route
* through the Enterprise endpoint (cloudcode-pa.googleapis.com),
* which rejects personal OAuth tokens with 403 PERMISSION_DENIED.
*
* By stripping this field when no explicit projectId was provided,
* we preserve the Personal/Standard flow routing.
* See: https://github.com/google-gemini/gemini-cli/issues/25189
* See: https://github.com/google-gemini/gemini-cli/issues/24517
*/
if (res.cloudaicompanionProject && !this.projectId) {
delete res.cloudaicompanionProject;
}
return res;
} catch (e) {
if (isVpcScAffectedUser(e)) {
return {
@ -508,6 +527,18 @@ export class CodeAssistServer implements ContentGenerator {
}
private getBaseUrl(): string {
/**
* FIX: Endpoint fallback logic.
* If no explicit projectId is provided via CLI flags or config, default
* to the standard Generative Language API. This prevents accidental
* routing to the Enterprise Cloud Code PA endpoint for personal users.
* See: https://github.com/google-gemini/gemini-cli/issues/25189
* See: https://github.com/google-gemini/gemini-cli/issues/24517
*/
if (!this.projectId) {
return 'https://generativelanguage.googleapis.com/v1beta';
}
const endpoint =
process.env['CODE_ASSIST_ENDPOINT'] ?? CODE_ASSIST_ENDPOINT;
const version =