mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-15 22:33:05 -07:00
fix(telemetry): resolve OOM risk with Array.from, add surrogate pair protection, and fix try/catch key scope
This commit is contained in:
@@ -52,13 +52,13 @@ describe('truncateForTelemetry', () => {
|
||||
});
|
||||
|
||||
it('should correctly truncate strings with multi-byte unicode characters (emojis)', () => {
|
||||
// 5 emojis, each is a single grapheme cluster
|
||||
// 5 emojis, each is a surrogate pair (2 code units)
|
||||
const emojis = '👋🌍🚀🔥🎉';
|
||||
|
||||
// Truncating to 2 graphemes
|
||||
// Truncating to 2 code units
|
||||
const result = truncateForTelemetry(emojis, 2);
|
||||
|
||||
expect(result).toBe('👋🌍...[TRUNCATED: original length 5]');
|
||||
expect(result).toBe('👋...[TRUNCATED: original length 10]');
|
||||
});
|
||||
|
||||
it('should stringify and structurally truncate objects if exceeding limits', () => {
|
||||
@@ -130,7 +130,7 @@ describe('truncateForTelemetry', () => {
|
||||
b: 'y'.repeat(100),
|
||||
};
|
||||
// Capping global string length to 50
|
||||
const result = truncateForTelemetry(obj, 100, 100, 4, 50) as string;
|
||||
const result = truncateForTelemetry(obj, 100, 100, 4, 100, 50) as string;
|
||||
|
||||
// It should replace the entire object with a valid JSON string indicating truncation
|
||||
expect(result).toBe(
|
||||
|
||||
@@ -48,7 +48,7 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null;
|
||||
}
|
||||
|
||||
function isHasToJSON(value: unknown): value is { toJSON: () => unknown } {
|
||||
function hasToJSON(value: unknown): value is { toJSON: () => unknown } {
|
||||
if (!isRecord(value)) return false;
|
||||
if (!('toJSON' in value)) return false;
|
||||
const toJSONFn = value['toJSON'];
|
||||
@@ -67,16 +67,17 @@ export function truncateForTelemetry(
|
||||
maxStringLength = 10000,
|
||||
maxArrayLength = 100,
|
||||
maxDepth = 4,
|
||||
maxObjectKeys = 100,
|
||||
maxGlobalStringLength = 50000,
|
||||
): AttributeValue | undefined {
|
||||
const truncateObj = (v: unknown, depth: number): unknown => {
|
||||
if (typeof v === 'string') {
|
||||
const graphemes = Array.from(v);
|
||||
if (graphemes.length > maxStringLength) {
|
||||
return (
|
||||
graphemes.slice(0, maxStringLength).join('') +
|
||||
`...[TRUNCATED: original length ${graphemes.length}]`
|
||||
);
|
||||
if (v.length > maxStringLength) {
|
||||
let truncatedStr = v.slice(0, maxStringLength);
|
||||
if (truncatedStr.length > 0 && /[\uD800-\uDBFF]$/.test(truncatedStr)) {
|
||||
truncatedStr = truncatedStr.slice(0, -1);
|
||||
}
|
||||
return truncatedStr + `...[TRUNCATED: original length ${v.length}]`;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
@@ -88,7 +89,7 @@ export function truncateForTelemetry(
|
||||
) {
|
||||
return v;
|
||||
}
|
||||
if (isHasToJSON(v)) {
|
||||
if (hasToJSON(v)) {
|
||||
try {
|
||||
return truncateObj(v.toJSON(), depth);
|
||||
} catch {
|
||||
@@ -112,21 +113,21 @@ export function truncateForTelemetry(
|
||||
|
||||
const newObj: Record<string, unknown> = {};
|
||||
let numKeys = 0;
|
||||
const MAX_KEYS = 100;
|
||||
const recordV = isRecord(v) ? v : {};
|
||||
for (const key in recordV) {
|
||||
if (!Object.prototype.hasOwnProperty.call(recordV, key)) continue;
|
||||
if (numKeys >= MAX_KEYS) {
|
||||
newObj['__truncated'] = `[TRUNCATED: Object with >${MAX_KEYS} keys]`;
|
||||
if (numKeys >= maxObjectKeys) {
|
||||
newObj['__truncated'] =
|
||||
`[TRUNCATED: Object with >${maxObjectKeys} keys]`;
|
||||
break;
|
||||
}
|
||||
const truncatedKey =
|
||||
key.length > 100 ? key.slice(0, 100) + '...[TRUNCATED_KEY]' : key;
|
||||
try {
|
||||
const val = recordV[key];
|
||||
const truncatedKey =
|
||||
key.length > 100 ? key.slice(0, 100) + '...[TRUNCATED_KEY]' : key;
|
||||
newObj[truncatedKey] = truncateObj(val, depth + 1);
|
||||
} catch {
|
||||
newObj[key] = '[ERROR: Failed to read property]';
|
||||
newObj[truncatedKey] = '[ERROR: Failed to read property]';
|
||||
}
|
||||
numKeys++;
|
||||
}
|
||||
@@ -151,10 +152,7 @@ export function truncateForTelemetry(
|
||||
const stringified = safeJsonStringify(truncated);
|
||||
|
||||
if (stringified.length > maxGlobalStringLength) {
|
||||
const graphemes = Array.from(stringified);
|
||||
if (graphemes.length > maxGlobalStringLength) {
|
||||
return `"[TRUNCATED: Payload exceeded global limit of ${maxGlobalStringLength} characters. Original length: ${graphemes.length}]"`;
|
||||
}
|
||||
return `"[TRUNCATED: Payload exceeded global limit of ${maxGlobalStringLength} characters. Original length: ${stringified.length}]"`;
|
||||
}
|
||||
|
||||
return stringified as AttributeValue;
|
||||
|
||||
Reference in New Issue
Block a user