mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 05:12:55 -07:00
fix(patch): cherry-pick c31f053 to release/v0.23.0-preview.4-pr-16004 to patch version v0.23.0-preview.4 and create version 0.23.0-preview.5 (#16027)
This commit is contained in:
+17
-10
@@ -12,7 +12,7 @@ import prettierConfig from 'eslint-config-prettier';
|
|||||||
import importPlugin from 'eslint-plugin-import';
|
import importPlugin from 'eslint-plugin-import';
|
||||||
import vitest from '@vitest/eslint-plugin';
|
import vitest from '@vitest/eslint-plugin';
|
||||||
import globals from 'globals';
|
import globals from 'globals';
|
||||||
import licenseHeader from 'eslint-plugin-license-header';
|
import headers from 'eslint-plugin-headers';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import url from 'node:url';
|
import url from 'node:url';
|
||||||
|
|
||||||
@@ -209,19 +209,26 @@ export default tseslint.config(
|
|||||||
{
|
{
|
||||||
files: ['./**/*.{tsx,ts,js}'],
|
files: ['./**/*.{tsx,ts,js}'],
|
||||||
plugins: {
|
plugins: {
|
||||||
'license-header': licenseHeader,
|
headers,
|
||||||
import: importPlugin,
|
import: importPlugin,
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
'license-header/header': [
|
'headers/header-format': [
|
||||||
'error',
|
'error',
|
||||||
[
|
{
|
||||||
'/**',
|
source: 'string',
|
||||||
' * @license',
|
content: [
|
||||||
' * Copyright 2025 Google LLC',
|
'@license',
|
||||||
' * SPDX-License-Identifier: Apache-2.0',
|
'Copyright (year) Google LLC',
|
||||||
' */',
|
'SPDX-License-Identifier: Apache-2.0',
|
||||||
],
|
].join('\n'),
|
||||||
|
patterns: {
|
||||||
|
year: {
|
||||||
|
pattern: '202[5-6]',
|
||||||
|
defaultValue: '2026',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
'import/enforce-node-protocol-usage': ['error', 'always'],
|
'import/enforce-node-protocol-usage': ['error', 'always'],
|
||||||
},
|
},
|
||||||
|
|||||||
Generated
+14
-21
@@ -36,8 +36,8 @@
|
|||||||
"esbuild-plugin-wasm": "^1.1.0",
|
"esbuild-plugin-wasm": "^1.1.0",
|
||||||
"eslint": "^9.24.0",
|
"eslint": "^9.24.0",
|
||||||
"eslint-config-prettier": "^10.1.2",
|
"eslint-config-prettier": "^10.1.2",
|
||||||
|
"eslint-plugin-headers": "^1.3.3",
|
||||||
"eslint-plugin-import": "^2.31.0",
|
"eslint-plugin-import": "^2.31.0",
|
||||||
"eslint-plugin-license-header": "^0.8.0",
|
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"glob": "^12.0.0",
|
"glob": "^12.0.0",
|
||||||
@@ -8865,6 +8865,19 @@
|
|||||||
"ms": "^2.1.1"
|
"ms": "^2.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eslint-plugin-headers": {
|
||||||
|
"version": "1.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-plugin-headers/-/eslint-plugin-headers-1.3.3.tgz",
|
||||||
|
"integrity": "sha512-VzZY4+cGRoR5HpALLARH+ibIjB6a2w12/cFEayORHXMRHMzDnweSjpmvxyzX3rsSIVCg01zmvepB7Tnmaj4kGQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": "^16.0.0 || >= 18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"eslint": ">=7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/eslint-plugin-import": {
|
"node_modules/eslint-plugin-import": {
|
||||||
"version": "2.32.0",
|
"version": "2.32.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz",
|
||||||
@@ -8919,16 +8932,6 @@
|
|||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-plugin-license-header": {
|
|
||||||
"version": "0.8.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-license-header/-/eslint-plugin-license-header-0.8.0.tgz",
|
|
||||||
"integrity": "sha512-khTCz6G3JdoQfwrtY4XKl98KW4PpnWUKuFx8v+twIRhJADEyYglMDC0td8It75C1MZ88gcvMusWuUlJsos7gYg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"requireindex": "^1.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/eslint-plugin-react": {
|
"node_modules/eslint-plugin-react": {
|
||||||
"version": "7.37.5",
|
"version": "7.37.5",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz",
|
||||||
@@ -15069,16 +15072,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/requireindex": {
|
|
||||||
"version": "1.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz",
|
|
||||||
"integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/requires-port": {
|
"node_modules/requires-port": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||||
|
|||||||
+1
-1
@@ -94,8 +94,8 @@
|
|||||||
"esbuild-plugin-wasm": "^1.1.0",
|
"esbuild-plugin-wasm": "^1.1.0",
|
||||||
"eslint": "^9.24.0",
|
"eslint": "^9.24.0",
|
||||||
"eslint-config-prettier": "^10.1.2",
|
"eslint-config-prettier": "^10.1.2",
|
||||||
|
"eslint-plugin-headers": "^1.3.3",
|
||||||
"eslint-plugin-import": "^2.31.0",
|
"eslint-plugin-import": "^2.31.0",
|
||||||
"eslint-plugin-license-header": "^0.8.0",
|
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"glob": "^12.0.0",
|
"glob": "^12.0.0",
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ import {
|
|||||||
applyModelSelection,
|
applyModelSelection,
|
||||||
createAvailabilityContextProvider,
|
createAvailabilityContextProvider,
|
||||||
} from '../availability/policyHelpers.js';
|
} from '../availability/policyHelpers.js';
|
||||||
|
import { resolveModel } from '../config/models.js';
|
||||||
import type { RetryAvailabilityContext } from '../utils/retry.js';
|
import type { RetryAvailabilityContext } from '../utils/retry.js';
|
||||||
|
|
||||||
const MAX_TURNS = 100;
|
const MAX_TURNS = 100;
|
||||||
@@ -397,7 +398,7 @@ export class GeminiClient {
|
|||||||
|
|
||||||
// Availability logic: The configured model is the source of truth,
|
// Availability logic: The configured model is the source of truth,
|
||||||
// including any permanent fallbacks (config.setModel) or manual overrides.
|
// including any permanent fallbacks (config.setModel) or manual overrides.
|
||||||
return this.config.getActiveModel();
|
return resolveModel(this.config.getActiveModel());
|
||||||
}
|
}
|
||||||
|
|
||||||
async *sendMessageStream(
|
async *sendMessageStream(
|
||||||
|
|||||||
@@ -123,8 +123,26 @@ describe('calculateRequestTokenCount', () => {
|
|||||||
|
|
||||||
// Should fallback to estimation:
|
// Should fallback to estimation:
|
||||||
// 'Hello': 5 chars * 0.25 = 1.25
|
// 'Hello': 5 chars * 0.25 = 1.25
|
||||||
// inlineData: JSON.stringify length / 4
|
// inlineData: 3000
|
||||||
expect(count).toBeGreaterThan(0);
|
// Total: 3001.25 -> 3001
|
||||||
|
expect(count).toBe(3001);
|
||||||
expect(mockContentGenerator.countTokens).toHaveBeenCalled();
|
expect(mockContentGenerator.countTokens).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should use fixed estimate for images in fallback', async () => {
|
||||||
|
vi.mocked(mockContentGenerator.countTokens).mockRejectedValue(
|
||||||
|
new Error('API error'),
|
||||||
|
);
|
||||||
|
const request = [
|
||||||
|
{ inlineData: { mimeType: 'image/png', data: 'large_data' } },
|
||||||
|
];
|
||||||
|
|
||||||
|
const count = await calculateRequestTokenCount(
|
||||||
|
request,
|
||||||
|
mockContentGenerator,
|
||||||
|
model,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(count).toBe(3000);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
import type { PartListUnion, Part } from '@google/genai';
|
import type { PartListUnion, Part } from '@google/genai';
|
||||||
import type { ContentGenerator } from '../core/contentGenerator.js';
|
import type { ContentGenerator } from '../core/contentGenerator.js';
|
||||||
|
import { debugLogger } from './debugLogger.js';
|
||||||
|
|
||||||
// Token estimation constants
|
// Token estimation constants
|
||||||
// ASCII characters (0-127) are roughly 4 chars per token
|
// ASCII characters (0-127) are roughly 4 chars per token
|
||||||
@@ -13,6 +14,8 @@ const ASCII_TOKENS_PER_CHAR = 0.25;
|
|||||||
// Non-ASCII characters (including CJK) are often 1-2 tokens per char.
|
// Non-ASCII characters (including CJK) are often 1-2 tokens per char.
|
||||||
// We use 1.3 as a conservative estimate to avoid underestimation.
|
// We use 1.3 as a conservative estimate to avoid underestimation.
|
||||||
const NON_ASCII_TOKENS_PER_CHAR = 1.3;
|
const NON_ASCII_TOKENS_PER_CHAR = 1.3;
|
||||||
|
// Fixed token estimate for images
|
||||||
|
const IMAGE_TOKEN_ESTIMATE = 3000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Estimates token count for parts synchronously using a heuristic.
|
* Estimates token count for parts synchronously using a heuristic.
|
||||||
@@ -31,12 +34,23 @@ export function estimateTokenCountSync(parts: Part[]): number {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// For non-text parts (functionCall, functionResponse, executableCode, etc.),
|
// For images, we use a fixed safe estimate (3,000 tokens) covering
|
||||||
|
// up to 4K resolution on Gemini 3.
|
||||||
|
// See: https://ai.google.dev/gemini-api/docs/vision#token_counting
|
||||||
|
const inlineData = 'inlineData' in part ? part.inlineData : undefined;
|
||||||
|
const fileData = 'fileData' in part ? part.fileData : undefined;
|
||||||
|
const mimeType = inlineData?.mimeType || fileData?.mimeType;
|
||||||
|
|
||||||
|
if (mimeType?.startsWith('image/')) {
|
||||||
|
totalTokens += IMAGE_TOKEN_ESTIMATE;
|
||||||
|
} else {
|
||||||
|
// For other non-text parts (functionCall, functionResponse, etc.),
|
||||||
// we fallback to the JSON string length heuristic.
|
// we fallback to the JSON string length heuristic.
|
||||||
// Note: This is an approximation.
|
// Note: This is an approximation.
|
||||||
totalTokens += JSON.stringify(part).length / 4;
|
totalTokens += JSON.stringify(part).length / 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return Math.floor(totalTokens);
|
return Math.floor(totalTokens);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,8 +83,9 @@ export async function calculateRequestTokenCount(
|
|||||||
contents: [{ role: 'user', parts }],
|
contents: [{ role: 'user', parts }],
|
||||||
});
|
});
|
||||||
return response.totalTokens ?? 0;
|
return response.totalTokens ?? 0;
|
||||||
} catch {
|
} catch (error) {
|
||||||
// Fallback to local estimation if the API call fails
|
// Fallback to local estimation if the API call fails
|
||||||
|
debugLogger.debug('countTokens API failed:', error);
|
||||||
return estimateTokenCountSync(parts);
|
return estimateTokenCountSync(parts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ try {
|
|||||||
|
|
||||||
const fileContent = `/**
|
const fileContent = `/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright ${new Date().getFullYear()} Google LLC
|
* Copyright ${new Date().getUTCFullYear()} Google LLC
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user