fix(core): handle optional response fields from code assist API (#20345)

This commit is contained in:
Sehoon Shon
2026-02-27 11:52:37 -05:00
committed by GitHub
parent 514d431049
commit e709789067
5 changed files with 58 additions and 24 deletions

View File

@@ -10,6 +10,7 @@ import {
type CodeAssistServer,
UserTierId,
getCodeAssistServer,
debugLogger,
} from '@google/gemini-cli-core';
export interface PrivacyState {
@@ -103,7 +104,12 @@ async function getRemoteDataCollectionOptIn(
): Promise<boolean> {
try {
const resp = await server.getCodeAssistGlobalUserSetting();
return resp.freeTierDataCollectionOptin;
if (resp.freeTierDataCollectionOptin === undefined) {
debugLogger.warn(
'Warning: Code Assist API did not return freeTierDataCollectionOptin. Defaulting to true.',
);
}
return resp.freeTierDataCollectionOptin ?? true;
} catch (error: unknown) {
if (error && typeof error === 'object' && 'response' in error) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
@@ -128,5 +134,10 @@ async function setRemoteDataCollectionOptIn(
cloudaicompanionProject: server.projectId,
freeTierDataCollectionOptin: optIn,
});
return resp.freeTierDataCollectionOptin;
if (resp.freeTierDataCollectionOptin === undefined) {
debugLogger.warn(
`Warning: Code Assist API did not return freeTierDataCollectionOptin. Defaulting to ${optIn}.`,
);
}
return resp.freeTierDataCollectionOptin ?? optIn;
}

View File

@@ -246,7 +246,7 @@ describe('converter', () => {
};
const genaiRes = fromGenerateContentResponse(codeAssistRes);
expect(genaiRes).toBeInstanceOf(GenerateContentResponse);
expect(genaiRes.candidates).toEqual(codeAssistRes.response.candidates);
expect(genaiRes.candidates).toEqual(codeAssistRes.response!.candidates);
});
it('should handle prompt feedback and usage metadata', () => {
@@ -266,10 +266,10 @@ describe('converter', () => {
};
const genaiRes = fromGenerateContentResponse(codeAssistRes);
expect(genaiRes.promptFeedback).toEqual(
codeAssistRes.response.promptFeedback,
codeAssistRes.response!.promptFeedback,
);
expect(genaiRes.usageMetadata).toEqual(
codeAssistRes.response.usageMetadata,
codeAssistRes.response!.usageMetadata,
);
});
@@ -296,7 +296,7 @@ describe('converter', () => {
};
const genaiRes = fromGenerateContentResponse(codeAssistRes);
expect(genaiRes.automaticFunctionCallingHistory).toEqual(
codeAssistRes.response.automaticFunctionCallingHistory,
codeAssistRes.response!.automaticFunctionCallingHistory,
);
});

View File

@@ -27,6 +27,7 @@ import type {
ToolConfig,
} from '@google/genai';
import { GenerateContentResponse } from '@google/genai';
import { debugLogger } from '../utils/debugLogger.js';
export interface CAGenerateContentRequest {
model: string;
@@ -72,12 +73,12 @@ interface VertexGenerationConfig {
}
export interface CaGenerateContentResponse {
response: VertexGenerateContentResponse;
response?: VertexGenerateContentResponse;
traceId?: string;
}
interface VertexGenerateContentResponse {
candidates: Candidate[];
candidates?: Candidate[];
automaticFunctionCallingHistory?: Content[];
promptFeedback?: GenerateContentResponsePromptFeedback;
usageMetadata?: GenerateContentResponseUsageMetadata;
@@ -94,7 +95,7 @@ interface VertexCountTokenRequest {
}
export interface CaCountTokenResponse {
totalTokens: number;
totalTokens?: number;
}
export function toCountTokenRequest(
@@ -111,8 +112,13 @@ export function toCountTokenRequest(
export function fromCountTokenResponse(
res: CaCountTokenResponse,
): CountTokensResponse {
if (res.totalTokens === undefined) {
debugLogger.warn(
'Warning: Code Assist API did not return totalTokens. Defaulting to 0.',
);
}
return {
totalTokens: res.totalTokens,
totalTokens: res.totalTokens ?? 0,
};
}

View File

@@ -18,6 +18,7 @@ import type { AuthClient } from 'google-auth-library';
import type { ValidationHandler } from '../fallback/types.js';
import { ChangeAuthRequestedError } from '../utils/errors.js';
import { ValidationRequiredError } from '../utils/googleQuotaErrors.js';
import { debugLogger } from '../utils/debugLogger.js';
export class ProjectIdRequiredError extends Error {
constructor() {
@@ -130,11 +131,20 @@ export async function setupUser(
}
if (loadRes.currentTier) {
if (!loadRes.paidTier?.id && !loadRes.currentTier.id) {
debugLogger.warn(
'Warning: Code Assist API did not return a user tier ID. Defaulting to STANDARD tier.',
);
}
if (!loadRes.cloudaicompanionProject) {
if (projectId) {
return {
projectId,
userTier: loadRes.paidTier?.id ?? loadRes.currentTier.id,
userTier:
loadRes.paidTier?.id ??
loadRes.currentTier.id ??
UserTierId.STANDARD,
userTierName: loadRes.paidTier?.name ?? loadRes.currentTier.name,
};
}
@@ -144,13 +154,20 @@ export async function setupUser(
}
return {
projectId: loadRes.cloudaicompanionProject,
userTier: loadRes.paidTier?.id ?? loadRes.currentTier.id,
userTier:
loadRes.paidTier?.id ?? loadRes.currentTier.id ?? UserTierId.STANDARD,
userTierName: loadRes.paidTier?.name ?? loadRes.currentTier.name,
};
}
const tier = getOnboardTier(loadRes);
if (!tier.id) {
debugLogger.warn(
'Warning: Code Assist API did not return an onboarding tier ID. Defaulting to STANDARD tier.',
);
}
let onboardReq: OnboardUserRequest;
if (tier.id === UserTierId.FREE) {
// The free tier uses a managed google cloud project. Setting a project in the `onboardUser` request causes a `Precondition Failed` error.
@@ -183,7 +200,7 @@ export async function setupUser(
if (projectId) {
return {
projectId,
userTier: tier.id,
userTier: tier.id ?? UserTierId.STANDARD,
userTierName: tier.name,
};
}
@@ -193,7 +210,7 @@ export async function setupUser(
return {
projectId: lroRes.response.cloudaicompanionProject.id,
userTier: tier.id,
userTier: tier.id ?? UserTierId.STANDARD,
userTierName: tier.name,
};
}

View File

@@ -60,7 +60,7 @@ export interface LoadCodeAssistResponse {
* GeminiUserTier reflects the structure received from the CodeAssist when calling LoadCodeAssist.
*/
export interface GeminiUserTier {
id: UserTierId;
id?: UserTierId;
name?: string;
description?: string;
// This value is used to declare whether a given tier requires the user to configure the project setting on the IDE settings or not.
@@ -79,10 +79,10 @@ export interface GeminiUserTier {
* @param tierName name of the tier.
*/
export interface IneligibleTier {
reasonCode: IneligibleTierReasonCode;
reasonMessage: string;
tierId: UserTierId;
tierName: string;
reasonCode?: IneligibleTierReasonCode;
reasonMessage?: string;
tierId?: UserTierId;
tierName?: string;
validationErrorMessage?: string;
validationUrl?: string;
validationUrlLinkText?: string;
@@ -127,7 +127,7 @@ export type UserTierId = (typeof UserTierId)[keyof typeof UserTierId] | string;
* privacy notice.
*/
export interface PrivacyNotice {
showNotice: boolean;
showNotice?: boolean;
noticeText?: string;
}
@@ -145,7 +145,7 @@ export interface OnboardUserRequest {
* http://google3/google/longrunning/operations.proto;rcl=698857719;l=107
*/
export interface LongRunningOperationResponse {
name: string;
name?: string;
done?: boolean;
response?: OnboardUserResponse;
}
@@ -157,8 +157,8 @@ export interface LongRunningOperationResponse {
export interface OnboardUserResponse {
// tslint:disable-next-line:enforce-name-casing This is the name of the field in the proto.
cloudaicompanionProject?: {
id: string;
name: string;
id?: string;
name?: string;
};
}
@@ -195,7 +195,7 @@ export interface SetCodeAssistGlobalUserSettingRequest {
export interface CodeAssistGlobalUserSettingResponse {
cloudaicompanionProject?: string;
freeTierDataCollectionOptin: boolean;
freeTierDataCollectionOptin?: boolean;
}
/**