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:
gemini-cli-robot
2026-01-06 15:13:30 -08:00
committed by GitHub
parent 518cc1ab63
commit 17fb758664
7 changed files with 75 additions and 41 deletions
+17 -10
View File
@@ -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'],
}, },
+14 -21
View File
@@ -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
View File
@@ -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",
+2 -1
View File
@@ -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);
});
}); });
+17 -2
View File
@@ -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);
} }
} }
+1 -1
View File
@@ -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
*/ */