summaryrefslogtreecommitdiff
path: root/packages/cli/src/ui/hooks/shellCommandProcessor.ts
diff options
context:
space:
mode:
authorBOYI <[email protected]>2025-07-22 06:26:40 +0800
committerGitHub <[email protected]>2025-07-21 22:26:40 +0000
commit12765eb77595aa3ef7c0fe4cb201410d71588753 (patch)
tree6802d6bbc2f216df950450c4d75b5d990015dc4f /packages/cli/src/ui/hooks/shellCommandProcessor.ts
parent4c3532d2b395859fb0d9db00bc5209f160ce2e29 (diff)
fix: character encoding issues in shell command processor (#1949)
Co-authored-by: Jacob Richman <[email protected]> Co-authored-by: Sandy Tao <[email protected]>
Diffstat (limited to 'packages/cli/src/ui/hooks/shellCommandProcessor.ts')
-rw-r--r--packages/cli/src/ui/hooks/shellCommandProcessor.ts30
1 files changed, 22 insertions, 8 deletions
diff --git a/packages/cli/src/ui/hooks/shellCommandProcessor.ts b/packages/cli/src/ui/hooks/shellCommandProcessor.ts
index e04c9f54..5d2b3166 100644
--- a/packages/cli/src/ui/hooks/shellCommandProcessor.ts
+++ b/packages/cli/src/ui/hooks/shellCommandProcessor.ts
@@ -5,14 +5,18 @@
*/
import { spawn } from 'child_process';
-import { StringDecoder } from 'string_decoder';
+import { TextDecoder } from 'util';
import {
HistoryItemWithoutId,
IndividualToolCallDisplay,
ToolCallStatus,
} from '../types.js';
import { useCallback } from 'react';
-import { Config, GeminiClient } from '@google/gemini-cli-core';
+import {
+ Config,
+ GeminiClient,
+ getCachedEncodingForBuffer,
+} from '@google/gemini-cli-core';
import { type PartListUnion } from '@google/genai';
import { formatMemoryUsage } from '../utils/formatters.js';
import { isBinary } from '../utils/textUtils.js';
@@ -71,8 +75,8 @@ function executeShellCommand(
});
// Use decoders to handle multi-byte characters safely (for streaming output).
- const stdoutDecoder = new StringDecoder('utf8');
- const stderrDecoder = new StringDecoder('utf8');
+ let stdoutDecoder: TextDecoder | null = null;
+ let stderrDecoder: TextDecoder | null = null;
let stdout = '';
let stderr = '';
@@ -85,6 +89,12 @@ function executeShellCommand(
let sniffedBytes = 0;
const handleOutput = (data: Buffer, stream: 'stdout' | 'stderr') => {
+ if (!stdoutDecoder || !stderrDecoder) {
+ const encoding = getCachedEncodingForBuffer(data);
+ stdoutDecoder = new TextDecoder(encoding);
+ stderrDecoder = new TextDecoder(encoding);
+ }
+
outputChunks.push(data);
if (streamToUi && sniffedBytes < MAX_SNIFF_SIZE) {
@@ -101,8 +111,8 @@ function executeShellCommand(
const decodedChunk =
stream === 'stdout'
- ? stdoutDecoder.write(data)
- : stderrDecoder.write(data);
+ ? stdoutDecoder.decode(data, { stream: true })
+ : stderrDecoder.decode(data, { stream: true });
if (stream === 'stdout') {
stdout += stripAnsi(decodedChunk);
} else {
@@ -160,8 +170,12 @@ function executeShellCommand(
abortSignal.removeEventListener('abort', abortHandler);
// Handle any final bytes lingering in the decoders
- stdout += stdoutDecoder.end();
- stderr += stderrDecoder.end();
+ if (stdoutDecoder) {
+ stdout += stdoutDecoder.decode();
+ }
+ if (stderrDecoder) {
+ stderr += stderrDecoder.decode();
+ }
const finalBuffer = Buffer.concat(outputChunks);