Files
gemini-cli/packages/core/src/utils/stdio.ts
2026-02-21 01:12:56 +00:00

120 lines
3.7 KiB
TypeScript

/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { coreEvents } from './events.js';
// Capture the original stdout and stderr write methods before any monkey patching occurs.
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
const originalStderrWrite = process.stderr.write.bind(process.stderr);
/**
* Writes to the real stdout, bypassing any monkey patching on process.stdout.write.
*/
export function writeToStdout(
...args: Parameters<typeof process.stdout.write>
): boolean {
return originalStdoutWrite(...args);
}
/**
* Writes to the real stderr, bypassing any monkey patching on process.stderr.write.
*/
export function writeToStderr(
...args: Parameters<typeof process.stderr.write>
): boolean {
return originalStderrWrite(...args);
}
/**
* Monkey patches process.stdout.write and process.stderr.write to redirect output to the provided logger.
* This prevents stray output from libraries (or the app itself) from corrupting the UI.
* Returns a cleanup function that restores the original write methods.
*/
export function patchStdio(): () => void {
const previousStdoutWrite = process.stdout.write;
const previousStderrWrite = process.stderr.write;
process.stdout.write = (
chunk: Uint8Array | string,
encodingOrCb?:
| BufferEncoding
| ((err?: NodeJS.ErrnoException | null) => void),
cb?: (err?: NodeJS.ErrnoException | null) => void,
) => {
const encoding =
typeof encodingOrCb === 'string' ? encodingOrCb : undefined;
coreEvents.emitOutput(false, chunk, encoding);
const callback = typeof encodingOrCb === 'function' ? encodingOrCb : cb;
if (callback) {
callback();
}
return true;
};
process.stderr.write = (
chunk: Uint8Array | string,
encodingOrCb?:
| BufferEncoding
| ((err?: NodeJS.ErrnoException | null) => void),
cb?: (err?: NodeJS.ErrnoException | null) => void,
) => {
const encoding =
typeof encodingOrCb === 'string' ? encodingOrCb : undefined;
coreEvents.emitOutput(true, chunk, encoding);
const callback = typeof encodingOrCb === 'function' ? encodingOrCb : cb;
if (callback) {
callback();
}
return true;
};
return () => {
process.stdout.write = previousStdoutWrite;
process.stderr.write = previousStderrWrite;
};
}
/**
* Creates proxies for process.stdout and process.stderr that use the real write methods
* (writeToStdout and writeToStderr) bypassing any monkey patching.
* This is used to write to the real output even when stdio is patched.
*/
export function createWorkingStdio() {
const inkStdout = new Proxy(process.stdout, {
get(target, prop, receiver) {
if (prop === 'write') {
return writeToStdout;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const value = Reflect.get(target, prop, receiver);
if (typeof value === 'function') {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return value.bind(target);
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return value;
},
});
const inkStderr = new Proxy(process.stderr, {
get(target, prop, receiver) {
if (prop === 'write') {
return writeToStderr;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const value = Reflect.get(target, prop, receiver);
if (typeof value === 'function') {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return value.bind(target);
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return value;
},
});
return { stdout: inkStdout, stderr: inkStderr };
}