Compare commits

...

3 Commits

Author SHA1 Message Date
gemini-cli-robot bd94d7434c chore(release): v0.55.0-preview.2 2026-08-07 02:43:34 +00:00
gemini-cli-robot b39816c870 fix(patch): cherry-pick 2139b12 to release/v0.55.0-preview.1-pr-28716 to patch version v0.55.0-preview.1 and create version 0.55.0-preview.2 (#28719)
Co-authored-by: luisfelipe-alt <luisfelipe@google.com>
2026-08-07 01:50:21 +00:00
gemini-cli-robot 157e64b490 chore(release): v0.55.0-preview.1 2026-08-06 01:21:02 +00:00
14 changed files with 147 additions and 53 deletions
+9 -9
View File
@@ -1,12 +1,12 @@
{
"name": "@google/gemini-cli",
"version": "0.55.0-nightly.20260729.g3499c84f7",
"version": "0.55.0-preview.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@google/gemini-cli",
"version": "0.55.0-nightly.20260729.g3499c84f7",
"version": "0.55.0-preview.2",
"workspaces": [
"packages/*"
],
@@ -17782,7 +17782,7 @@
},
"packages/a2a-server": {
"name": "@google/gemini-cli-a2a-server",
"version": "0.55.0-nightly.20260729.g3499c84f7",
"version": "0.55.0-preview.2",
"dependencies": {
"@a2a-js/sdk": "0.3.11",
"@google-cloud/storage": "7.19.0",
@@ -18242,7 +18242,7 @@
},
"packages/cli": {
"name": "@google/gemini-cli",
"version": "0.55.0-nightly.20260729.g3499c84f7",
"version": "0.55.0-preview.2",
"license": "Apache-2.0",
"dependencies": {
"@agentclientprotocol/sdk": "0.16.1",
@@ -18458,7 +18458,7 @@
},
"packages/core": {
"name": "@google/gemini-cli-core",
"version": "0.55.0-nightly.20260729.g3499c84f7",
"version": "0.55.0-preview.2",
"license": "Apache-2.0",
"dependencies": {
"@a2a-js/sdk": "0.3.11",
@@ -19131,7 +19131,7 @@
},
"packages/devtools": {
"name": "@google/gemini-cli-devtools",
"version": "0.55.0-nightly.20260729.g3499c84f7",
"version": "0.55.0-preview.2",
"license": "Apache-2.0",
"dependencies": {
"ws": "8.16.0"
@@ -19167,7 +19167,7 @@
},
"packages/sdk": {
"name": "@google/gemini-cli-sdk",
"version": "0.55.0-nightly.20260729.g3499c84f7",
"version": "0.55.0-preview.2",
"license": "Apache-2.0",
"dependencies": {
"@google/gemini-cli-core": "file:../core",
@@ -19506,7 +19506,7 @@
},
"packages/test-utils": {
"name": "@google/gemini-cli-test-utils",
"version": "0.55.0-nightly.20260729.g3499c84f7",
"version": "0.55.0-preview.2",
"license": "Apache-2.0",
"dependencies": {
"@google/gemini-cli-core": "file:../core",
@@ -19524,7 +19524,7 @@
},
"packages/vscode-ide-companion": {
"name": "gemini-cli-vscode-ide-companion",
"version": "0.55.0-nightly.20260729.g3499c84f7",
"version": "0.55.0-preview.2",
"license": "LICENSE",
"dependencies": {
"@modelcontextprotocol/sdk": "1.23.0",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli",
"version": "0.55.0-nightly.20260729.g3499c84f7",
"version": "0.55.0-preview.2",
"engines": {
"node": ">=20.0.0"
},
@@ -14,7 +14,7 @@
"url": "git+https://github.com/google-gemini/gemini-cli.git"
},
"config": {
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.55.0-nightly.20260729.g3499c84f7"
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.55.0-preview.2"
},
"scripts": {
"start": "cross-env NODE_ENV=development node scripts/start.js",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-a2a-server",
"version": "0.55.0-nightly.20260729.g3499c84f7",
"version": "0.55.0-preview.2",
"description": "Gemini CLI A2A Server",
"repository": {
"type": "git",
+5 -3
View File
@@ -131,9 +131,11 @@ export class Task {
this.autoExecute = autoExecute;
this.config.setFallbackModelHandler(
// For a2a-server, we want to automatically switch to the fallback model
// and retry the current request seamlessly. The 'retry_always' intent
// achieves this, ensuring a smooth fallback experience for the user.
async () => 'retry_always',
// for future requests without retrying the current one.
async (failedModel, fallbackModel) => {
this.config.activateFallbackMode(fallbackModel, failedModel);
return 'stop';
},
);
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli",
"version": "0.55.0-nightly.20260729.g3499c84f7",
"version": "0.55.0-preview.2",
"description": "Gemini CLI",
"license": "Apache-2.0",
"repository": {
@@ -27,7 +27,7 @@
"dist"
],
"config": {
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.55.0-nightly.20260729.g3499c84f7"
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.55.0-preview.2"
},
"dependencies": {
"@agentclientprotocol/sdk": "0.16.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-core",
"version": "0.55.0-nightly.20260729.g3499c84f7",
"version": "0.55.0-preview.2",
"description": "Gemini CLI Core",
"license": "Apache-2.0",
"repository": {
+3
View File
@@ -1938,6 +1938,9 @@ export class Config implements McpContext, AgentLoopContext {
}
activateFallbackMode(model: string, failedModel?: string): void {
debugLogger.log(
`Model fallback activated: switching from ${failedModel ?? 'unknown'} to ${model}`,
);
if (this.getActiveModel() !== model) {
this.setModel(model, true);
}
@@ -107,6 +107,36 @@ describe('Retry Utility Fallback Integration', () => {
expect(mockApiCall).toHaveBeenCalledTimes(3);
});
it('should call onPersistent429 immediately on attempt 1 when classifyGoogleError returns TerminalQuotaError', async () => {
const mockApiCall = vi
.fn()
.mockRejectedValue(
new TerminalQuotaError('Capacity exhausted', mockGoogleApiError),
);
const mockPersistent429Callback = vi.fn(
async () =>
// Return null to stop retrying after fallback attempt
null,
);
const promise = retryWithBackoff(mockApiCall, {
maxAttempts: 10, // High maxAttempts to prove we don't wait for max attempts
initialDelayMs: 1,
maxDelayMs: 10,
onPersistent429: mockPersistent429Callback,
authType: AuthType.LOGIN_WITH_GOOGLE,
});
await expect(promise).rejects.toThrow('Capacity exhausted');
expect(mockApiCall).toHaveBeenCalledTimes(1); // Only called once because it's terminal and fallback returned null
expect(mockPersistent429Callback).toHaveBeenCalledTimes(1);
expect(mockPersistent429Callback).toHaveBeenCalledWith(
AuthType.LOGIN_WITH_GOOGLE,
expect.any(TerminalQuotaError),
);
});
it('should trigger onPersistent429 when HTTP 499 persists through all retry attempts', async () => {
let fallbackCalled = false;
const mockError: HttpError = new Error('Simulated 499 error');
@@ -81,7 +81,7 @@ describe('classifyGoogleError', () => {
}
});
it('should return RetryableQuotaError with delay for 503 Service Unavailable with RetryInfo', () => {
it('should return TerminalQuotaError for MODEL_CAPACITY_EXHAUSTED even with RetryInfo headers', () => {
const apiError: GoogleApiError = {
code: 503,
message:
@@ -103,8 +103,7 @@ describe('classifyGoogleError', () => {
};
vi.spyOn(errorParser, 'parseGoogleApiError').mockReturnValue(apiError);
const result = classifyGoogleError(new Error());
expect(result).toBeInstanceOf(RetryableQuotaError);
expect((result as RetryableQuotaError).retryDelayMs).toBe(9000);
expect(result).toBeInstanceOf(TerminalQuotaError);
});
it('should return TerminalQuotaError for MODEL_CAPACITY_EXHAUSTED when no retry delay is specified', () => {
@@ -126,6 +125,24 @@ describe('classifyGoogleError', () => {
expect(result).toBeInstanceOf(TerminalQuotaError);
});
it('should return TerminalQuotaError for structured error with details when message contains capacity exhaustion keywords', () => {
const apiError: GoogleApiError = {
code: 429,
message: 'You have exhausted your capacity on this model.',
details: [
{
'@type': 'type.googleapis.com/google.rpc.Help',
links: [
{ description: 'Learn more', url: 'https://support.google.com' },
],
},
],
};
vi.spyOn(errorParser, 'parseGoogleApiError').mockReturnValue(apiError);
const result = classifyGoogleError(new Error());
expect(result).toBeInstanceOf(TerminalQuotaError);
});
it('should return TerminalQuotaError for MODEL_CAPACITY_EXHAUSTED even when the domain is not a Cloud Code domain (domain-agnostic)', () => {
const apiError: GoogleApiError = {
code: 429,
@@ -396,6 +413,28 @@ describe('classifyGoogleError', () => {
expect((result as TerminalQuotaError).reason).toBe('RATE_LIMIT_EXCEEDED');
});
it('should return TerminalQuotaError for Cloud Code RATE_LIMIT_EXCEEDED without a specified server delay', () => {
const apiError: GoogleApiError = {
code: 429,
message: 'Rate limit exceeded',
details: [
{
'@type': 'type.googleapis.com/google.rpc.ErrorInfo',
reason: 'RATE_LIMIT_EXCEEDED',
domain: 'cloudcode-pa.googleapis.com',
metadata: {
uiMessage: 'true',
model: 'gemini-2.5-pro',
},
},
],
};
vi.spyOn(errorParser, 'parseGoogleApiError').mockReturnValue(apiError);
const result = classifyGoogleError(new Error());
expect(result).toBeInstanceOf(TerminalQuotaError);
expect((result as TerminalQuotaError).reason).toBe('RATE_LIMIT_EXCEEDED');
});
it('should return TerminalQuotaError for Cloud Code QUOTA_EXHAUSTED', () => {
const apiError: GoogleApiError = {
code: 429,
+48 -28
View File
@@ -289,16 +289,23 @@ export function classifyGoogleError(error: unknown): unknown {
return new RetryableQuotaError(errorMessage, cause, retryDelaySeconds);
}
} else if (status === 429 || status === 499 || status === 503) {
// Fallback: If it is a 429, 499, or 503 but doesn't have a specific "retry in" message,
// assume it is a temporary rate limit and retry.
return new RetryableQuotaError(
errorMessage,
googleApiError ?? {
code: status,
message: errorMessage,
details: [],
},
);
const cause = googleApiError ?? {
code: status,
message: errorMessage,
details: [],
};
// If the error message indicates capacity exhaustion, classify as TerminalQuotaError
if (
/exhausted your capacity|capacity exceeded|MODEL_CAPACITY_EXHAUSTED/i.test(
errorMessage,
)
) {
return new TerminalQuotaError(errorMessage, cause);
}
// Fallback: assume it is a temporary rate limit and retry.
return new RetryableQuotaError(errorMessage, cause);
}
return error; // Not a retryable error we can handle with structured details or a parsable retry message.
@@ -338,6 +345,19 @@ export function classifyGoogleError(error: unknown): unknown {
}
if (errorInfo) {
// Always treat capacity exhaustion as terminal error to trigger immediate model fallback
if (
errorInfo.reason === 'MODEL_CAPACITY_EXHAUSTED' ||
errorInfo.reason === 'MODEL_CAPACITY_EXCEEDED'
) {
return new TerminalQuotaError(
googleApiError.message,
googleApiError,
delaySeconds,
errorInfo.reason,
);
}
// INSUFFICIENT_G1_CREDITS_BALANCE is always terminal, regardless of domain
if (errorInfo.reason === 'INSUFFICIENT_G1_CREDITS_BALANCE') {
return new TerminalQuotaError(
@@ -348,28 +368,19 @@ export function classifyGoogleError(error: unknown): unknown {
);
}
if (
errorInfo.reason === 'MODEL_CAPACITY_EXHAUSTED' ||
errorInfo.reason === 'MODEL_CAPACITY_EXCEEDED'
) {
// If no server backoff delay is specified, treat capacity exhaustion as a terminal error
// to trigger immediate model fallback without retrying on the same exhausted model.
if (delaySeconds === undefined) {
return new TerminalQuotaError(
googleApiError.message,
googleApiError,
delaySeconds,
errorInfo.reason,
);
}
// Otherwise, fall through to RetryableQuotaError to honor the server's requested delay.
}
// New Cloud Code API quota handling
if (errorInfo.domain) {
if (isCloudCodeDomain(errorInfo.domain)) {
if (errorInfo.reason === 'RATE_LIMIT_EXCEEDED') {
const effectiveDelay = delaySeconds ?? 10;
if (delaySeconds === undefined) {
return new TerminalQuotaError(
googleApiError.message,
googleApiError,
undefined,
errorInfo.reason,
);
}
const effectiveDelay = delaySeconds;
if (effectiveDelay > MAX_RETRYABLE_DELAY_SECONDS) {
return new TerminalQuotaError(
googleApiError.message,
@@ -437,6 +448,15 @@ export function classifyGoogleError(error: unknown): unknown {
}
}
// If the error message indicates capacity exhaustion, classify as TerminalQuotaError
if (
/exhausted your capacity|capacity exceeded|MODEL_CAPACITY_EXHAUSTED/i.test(
errorMessage,
)
) {
return new TerminalQuotaError(errorMessage, googleApiError);
}
// If we reached this point, the status is 429, 499, or 503 and we have details,
// but no specific violation was matched. We return a generic retryable error.
return new RetryableQuotaError(errorMessage, googleApiError);
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-devtools",
"version": "0.55.0-nightly.20260729.g3499c84f7",
"version": "0.55.0-preview.2",
"license": "Apache-2.0",
"type": "module",
"main": "dist/src/index.js",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-sdk",
"version": "0.55.0-nightly.20260729.g3499c84f7",
"version": "0.55.0-preview.2",
"description": "Gemini CLI SDK",
"license": "Apache-2.0",
"repository": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-test-utils",
"version": "0.55.0-nightly.20260729.g3499c84f7",
"version": "0.55.0-preview.2",
"private": true,
"main": "src/index.ts",
"license": "Apache-2.0",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "gemini-cli-vscode-ide-companion",
"displayName": "Gemini CLI Companion",
"description": "Enable Gemini CLI with direct access to your IDE workspace.",
"version": "0.55.0-nightly.20260729.g3499c84f7",
"version": "0.55.0-preview.2",
"publisher": "google",
"icon": "assets/icon.png",
"repository": {