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
@@ -10,6 +10,7 @@ import {
type CodeAssistServer, type CodeAssistServer,
UserTierId, UserTierId,
getCodeAssistServer, getCodeAssistServer,
debugLogger,
} from '@google/gemini-cli-core'; } from '@google/gemini-cli-core';
export interface PrivacyState { export interface PrivacyState {
@@ -103,7 +104,12 @@ async function getRemoteDataCollectionOptIn(
): Promise<boolean> { ): Promise<boolean> {
try { try {
const resp = await server.getCodeAssistGlobalUserSetting(); 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) { } catch (error: unknown) {
if (error && typeof error === 'object' && 'response' in error) { if (error && typeof error === 'object' && 'response' in error) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
@@ -128,5 +134,10 @@ async function setRemoteDataCollectionOptIn(
cloudaicompanionProject: server.projectId, cloudaicompanionProject: server.projectId,
freeTierDataCollectionOptin: optIn, 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;
} }
@@ -246,7 +246,7 @@ describe('converter', () => {
}; };
const genaiRes = fromGenerateContentResponse(codeAssistRes); const genaiRes = fromGenerateContentResponse(codeAssistRes);
expect(genaiRes).toBeInstanceOf(GenerateContentResponse); 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', () => { it('should handle prompt feedback and usage metadata', () => {
@@ -266,10 +266,10 @@ describe('converter', () => {
}; };
const genaiRes = fromGenerateContentResponse(codeAssistRes); const genaiRes = fromGenerateContentResponse(codeAssistRes);
expect(genaiRes.promptFeedback).toEqual( expect(genaiRes.promptFeedback).toEqual(
codeAssistRes.response.promptFeedback, codeAssistRes.response!.promptFeedback,
); );
expect(genaiRes.usageMetadata).toEqual( expect(genaiRes.usageMetadata).toEqual(
codeAssistRes.response.usageMetadata, codeAssistRes.response!.usageMetadata,
); );
}); });
@@ -296,7 +296,7 @@ describe('converter', () => {
}; };
const genaiRes = fromGenerateContentResponse(codeAssistRes); const genaiRes = fromGenerateContentResponse(codeAssistRes);
expect(genaiRes.automaticFunctionCallingHistory).toEqual( expect(genaiRes.automaticFunctionCallingHistory).toEqual(
codeAssistRes.response.automaticFunctionCallingHistory, codeAssistRes.response!.automaticFunctionCallingHistory,
); );
}); });
+10 -4
View File
@@ -27,6 +27,7 @@ import type {
ToolConfig, ToolConfig,
} from '@google/genai'; } from '@google/genai';
import { GenerateContentResponse } from '@google/genai'; import { GenerateContentResponse } from '@google/genai';
import { debugLogger } from '../utils/debugLogger.js';
export interface CAGenerateContentRequest { export interface CAGenerateContentRequest {
model: string; model: string;
@@ -72,12 +73,12 @@ interface VertexGenerationConfig {
} }
export interface CaGenerateContentResponse { export interface CaGenerateContentResponse {
response: VertexGenerateContentResponse; response?: VertexGenerateContentResponse;
traceId?: string; traceId?: string;
} }
interface VertexGenerateContentResponse { interface VertexGenerateContentResponse {
candidates: Candidate[]; candidates?: Candidate[];
automaticFunctionCallingHistory?: Content[]; automaticFunctionCallingHistory?: Content[];
promptFeedback?: GenerateContentResponsePromptFeedback; promptFeedback?: GenerateContentResponsePromptFeedback;
usageMetadata?: GenerateContentResponseUsageMetadata; usageMetadata?: GenerateContentResponseUsageMetadata;
@@ -94,7 +95,7 @@ interface VertexCountTokenRequest {
} }
export interface CaCountTokenResponse { export interface CaCountTokenResponse {
totalTokens: number; totalTokens?: number;
} }
export function toCountTokenRequest( export function toCountTokenRequest(
@@ -111,8 +112,13 @@ export function toCountTokenRequest(
export function fromCountTokenResponse( export function fromCountTokenResponse(
res: CaCountTokenResponse, res: CaCountTokenResponse,
): CountTokensResponse { ): CountTokensResponse {
if (res.totalTokens === undefined) {
debugLogger.warn(
'Warning: Code Assist API did not return totalTokens. Defaulting to 0.',
);
}
return { return {
totalTokens: res.totalTokens, totalTokens: res.totalTokens ?? 0,
}; };
} }
+21 -4
View File
@@ -18,6 +18,7 @@ import type { AuthClient } from 'google-auth-library';
import type { ValidationHandler } from '../fallback/types.js'; import type { ValidationHandler } from '../fallback/types.js';
import { ChangeAuthRequestedError } from '../utils/errors.js'; import { ChangeAuthRequestedError } from '../utils/errors.js';
import { ValidationRequiredError } from '../utils/googleQuotaErrors.js'; import { ValidationRequiredError } from '../utils/googleQuotaErrors.js';
import { debugLogger } from '../utils/debugLogger.js';
export class ProjectIdRequiredError extends Error { export class ProjectIdRequiredError extends Error {
constructor() { constructor() {
@@ -130,11 +131,20 @@ export async function setupUser(
} }
if (loadRes.currentTier) { 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 (!loadRes.cloudaicompanionProject) {
if (projectId) { if (projectId) {
return { return {
projectId, projectId,
userTier: loadRes.paidTier?.id ?? loadRes.currentTier.id, userTier:
loadRes.paidTier?.id ??
loadRes.currentTier.id ??
UserTierId.STANDARD,
userTierName: loadRes.paidTier?.name ?? loadRes.currentTier.name, userTierName: loadRes.paidTier?.name ?? loadRes.currentTier.name,
}; };
} }
@@ -144,13 +154,20 @@ export async function setupUser(
} }
return { return {
projectId: loadRes.cloudaicompanionProject, 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, userTierName: loadRes.paidTier?.name ?? loadRes.currentTier.name,
}; };
} }
const tier = getOnboardTier(loadRes); 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; let onboardReq: OnboardUserRequest;
if (tier.id === UserTierId.FREE) { 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. // 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) { if (projectId) {
return { return {
projectId, projectId,
userTier: tier.id, userTier: tier.id ?? UserTierId.STANDARD,
userTierName: tier.name, userTierName: tier.name,
}; };
} }
@@ -193,7 +210,7 @@ export async function setupUser(
return { return {
projectId: lroRes.response.cloudaicompanionProject.id, projectId: lroRes.response.cloudaicompanionProject.id,
userTier: tier.id, userTier: tier.id ?? UserTierId.STANDARD,
userTierName: tier.name, userTierName: tier.name,
}; };
} }
+10 -10
View File
@@ -60,7 +60,7 @@ export interface LoadCodeAssistResponse {
* GeminiUserTier reflects the structure received from the CodeAssist when calling LoadCodeAssist. * GeminiUserTier reflects the structure received from the CodeAssist when calling LoadCodeAssist.
*/ */
export interface GeminiUserTier { export interface GeminiUserTier {
id: UserTierId; id?: UserTierId;
name?: string; name?: string;
description?: 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. // 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. * @param tierName name of the tier.
*/ */
export interface IneligibleTier { export interface IneligibleTier {
reasonCode: IneligibleTierReasonCode; reasonCode?: IneligibleTierReasonCode;
reasonMessage: string; reasonMessage?: string;
tierId: UserTierId; tierId?: UserTierId;
tierName: string; tierName?: string;
validationErrorMessage?: string; validationErrorMessage?: string;
validationUrl?: string; validationUrl?: string;
validationUrlLinkText?: string; validationUrlLinkText?: string;
@@ -127,7 +127,7 @@ export type UserTierId = (typeof UserTierId)[keyof typeof UserTierId] | string;
* privacy notice. * privacy notice.
*/ */
export interface PrivacyNotice { export interface PrivacyNotice {
showNotice: boolean; showNotice?: boolean;
noticeText?: string; noticeText?: string;
} }
@@ -145,7 +145,7 @@ export interface OnboardUserRequest {
* http://google3/google/longrunning/operations.proto;rcl=698857719;l=107 * http://google3/google/longrunning/operations.proto;rcl=698857719;l=107
*/ */
export interface LongRunningOperationResponse { export interface LongRunningOperationResponse {
name: string; name?: string;
done?: boolean; done?: boolean;
response?: OnboardUserResponse; response?: OnboardUserResponse;
} }
@@ -157,8 +157,8 @@ export interface LongRunningOperationResponse {
export interface OnboardUserResponse { export interface OnboardUserResponse {
// tslint:disable-next-line:enforce-name-casing This is the name of the field in the proto. // tslint:disable-next-line:enforce-name-casing This is the name of the field in the proto.
cloudaicompanionProject?: { cloudaicompanionProject?: {
id: string; id?: string;
name: string; name?: string;
}; };
} }
@@ -195,7 +195,7 @@ export interface SetCodeAssistGlobalUserSettingRequest {
export interface CodeAssistGlobalUserSettingResponse { export interface CodeAssistGlobalUserSettingResponse {
cloudaicompanionProject?: string; cloudaicompanionProject?: string;
freeTierDataCollectionOptin: boolean; freeTierDataCollectionOptin?: boolean;
} }
/** /**