mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-08-11 01:16:28 -07:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bd94d7434c | |||
| b39816c870 | |||
| 157e64b490 |
Generated
+9
-9
@@ -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
@@ -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,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",
|
||||
|
||||
@@ -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';
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,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": {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,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,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,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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user