Skip to content

Commit 12765eb

Browse files
boylin0jacob314SandyTao520
authored
fix: character encoding issues in shell command processor (google-gemini#1949)
Co-authored-by: Jacob Richman <jacob314@gmail.com> Co-authored-by: Sandy Tao <sandytao520@icloud.com>
1 parent 4c3532d commit 12765eb

File tree

7 files changed

+696
-9
lines changed

7 files changed

+696
-9
lines changed

package-lock.json

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/cli/src/ui/hooks/shellCommandProcessor.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ vi.mock('os', () => ({
1919
default: {
2020
platform: () => 'linux',
2121
tmpdir: () => '/tmp',
22+
homedir: () => '/home/user',
2223
},
2324
platform: () => 'linux',
2425
tmpdir: () => '/tmp',
26+
homedir: () => '/home/user',
2527
}));
2628
vi.mock('@google/gemini-cli-core');
2729
vi.mock('../utils/textUtils.js', () => ({

packages/cli/src/ui/hooks/shellCommandProcessor.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,18 @@
55
*/
66

77
import { spawn } from 'child_process';
8-
import { StringDecoder } from 'string_decoder';
8+
import { TextDecoder } from 'util';
99
import {
1010
HistoryItemWithoutId,
1111
IndividualToolCallDisplay,
1212
ToolCallStatus,
1313
} from '../types.js';
1414
import { useCallback } from 'react';
15-
import { Config, GeminiClient } from '@google/gemini-cli-core';
15+
import {
16+
Config,
17+
GeminiClient,
18+
getCachedEncodingForBuffer,
19+
} from '@google/gemini-cli-core';
1620
import { type PartListUnion } from '@google/genai';
1721
import { formatMemoryUsage } from '../utils/formatters.js';
1822
import { isBinary } from '../utils/textUtils.js';
@@ -71,8 +75,8 @@ function executeShellCommand(
7175
});
7276

7377
// Use decoders to handle multi-byte characters safely (for streaming output).
74-
const stdoutDecoder = new StringDecoder('utf8');
75-
const stderrDecoder = new StringDecoder('utf8');
78+
let stdoutDecoder: TextDecoder | null = null;
79+
let stderrDecoder: TextDecoder | null = null;
7680

7781
let stdout = '';
7882
let stderr = '';
@@ -85,6 +89,12 @@ function executeShellCommand(
8589
let sniffedBytes = 0;
8690

8791
const handleOutput = (data: Buffer, stream: 'stdout' | 'stderr') => {
92+
if (!stdoutDecoder || !stderrDecoder) {
93+
const encoding = getCachedEncodingForBuffer(data);
94+
stdoutDecoder = new TextDecoder(encoding);
95+
stderrDecoder = new TextDecoder(encoding);
96+
}
97+
8898
outputChunks.push(data);
8999

90100
if (streamToUi && sniffedBytes < MAX_SNIFF_SIZE) {
@@ -101,8 +111,8 @@ function executeShellCommand(
101111

102112
const decodedChunk =
103113
stream === 'stdout'
104-
? stdoutDecoder.write(data)
105-
: stderrDecoder.write(data);
114+
? stdoutDecoder.decode(data, { stream: true })
115+
: stderrDecoder.decode(data, { stream: true });
106116
if (stream === 'stdout') {
107117
stdout += stripAnsi(decodedChunk);
108118
} else {
@@ -160,8 +170,12 @@ function executeShellCommand(
160170
abortSignal.removeEventListener('abort', abortHandler);
161171

162172
// Handle any final bytes lingering in the decoders
163-
stdout += stdoutDecoder.end();
164-
stderr += stderrDecoder.end();
173+
if (stdoutDecoder) {
174+
stdout += stdoutDecoder.decode();
175+
}
176+
if (stderrDecoder) {
177+
stderr += stderrDecoder.decode();
178+
}
165179

166180
const finalBuffer = Buffer.concat(outputChunks);
167181

packages/core/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@
4444
"simple-git": "^3.28.0",
4545
"strip-ansi": "^7.1.0",
4646
"undici": "^7.10.0",
47-
"ws": "^8.18.0"
47+
"ws": "^8.18.0",
48+
"chardet": "^2.1.0"
4849
},
4950
"devDependencies": {
5051
"@types/diff": "^7.0.2",

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export * from './utils/editor.js';
3535
export * from './utils/quotaErrorDetection.js';
3636
export * from './utils/fileUtils.js';
3737
export * from './utils/retry.js';
38+
export * from './utils/systemEncoding.js';
3839

3940
// Export services
4041
export * from './services/fileDiscoveryService.js';

0 commit comments

Comments
 (0)