diff options
| author | BOYI <[email protected]> | 2025-07-22 06:26:40 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-07-21 22:26:40 +0000 |
| commit | 12765eb77595aa3ef7c0fe4cb201410d71588753 (patch) | |
| tree | 6802d6bbc2f216df950450c4d75b5d990015dc4f /packages/cli/src/ui/hooks/shellCommandProcessor.ts | |
| parent | 4c3532d2b395859fb0d9db00bc5209f160ce2e29 (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.ts | 30 |
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); |
