refactor: redirect reasoning output to stderr

This commit is contained in:
mitchell 2026-04-17 03:04:47 -04:00
parent 712666255a
commit 4716712a92
2 changed files with 38 additions and 38 deletions

View file

@ -1,7 +1,8 @@
import os from "node:os";
import type { WriteStream } from "node:tty";
import type { AsyncIterableStream, TextStreamPart, ToolSet } from "ai";
import styles from "ansi-styles";
import { supportsColor } from "chalk";
import { supportsColorStderr } from "chalk";
const home = os.homedir();
const reasoningWriter = Bun.file(`${home}/.aicl_reasoning.md`).writer();
@ -15,18 +16,21 @@ export default class StreamRenderer {
process.stdout.addListener("drain", () => {
this.memOk = true;
});
process.stderr.addListener("drain", () => {
this.memOk = true;
});
}
private write(text: string) {
this.memOk = process.stdout.write(text);
private write(text: string, pipe: WriteStream = process.stdout) {
this.memOk = pipe.write(text);
}
private moveCursor(x: number, y: number) {
this.memOk = process.stdout.moveCursor(x, y);
private moveCursor(y: number, pipe: WriteStream = process.stdout) {
this.memOk = pipe.moveCursor(-9999, y);
}
private clearScreenDown() {
this.memOk = process.stdout.clearScreenDown();
private clearScreenDown(pipe: WriteStream = process.stdout) {
this.memOk = pipe.clearScreenDown();
}
private resetCounts() {
@ -34,8 +38,8 @@ export default class StreamRenderer {
this.charsSinceNL = 0;
}
private checkContainment(text: string) {
const [columns, rows] = process.stdout.getWindowSize();
private checkContainment(text: string, pipe: WriteStream = process.stdout) {
const [columns, rows] = pipe.getWindowSize();
this.charsSinceNL += text.length;
if (text.includes("\n")) {
this.lineCount += text.match(/\n/g)?.length ?? 1;
@ -46,10 +50,9 @@ export default class StreamRenderer {
}
if (this.lineCount >= Math.floor(rows * 0.75)) {
this.moveCursor(0, -this.lineCount);
this.clearScreenDown();
this.lineCount = 0;
this.charsSinceNL = 0;
this.moveCursor(-this.lineCount, pipe);
this.clearScreenDown(pipe);
this.resetCounts();
}
}
@ -58,18 +61,21 @@ export default class StreamRenderer {
for await (const part of stream) {
if (part.type === "reasoning-start") {
if (supportsColor)
this.write(`${styles.dim.open}${styles.italic.open}`);
this.write("\n");
if (supportsColorStderr)
this.write(`${styles.dim.open}${styles.italic.open}`, process.stderr);
this.write("\n", process.stderr);
} else if (part.type === "reasoning-delta") {
this.write(part.text);
this.write(part.text, process.stderr);
reasoningWriter.write(part.text);
this.checkContainment(part.text);
this.checkContainment(part.text, process.stderr);
} else if (part.type === "reasoning-end") {
if (supportsColor)
this.write(`${styles.italic.close}${styles.dim.close}`);
this.moveCursor(0, -this.lineCount - 1);
this.clearScreenDown();
if (supportsColorStderr)
this.write(
`${styles.italic.close}${styles.dim.close}`,
process.stderr,
);
this.moveCursor(-this.lineCount - 1, process.stderr);
this.clearScreenDown(process.stderr);
this.resetCounts();
reasoningWriter.end();
} else if (part.type === "text-start") {
@ -78,8 +84,8 @@ export default class StreamRenderer {
this.write(part.text);
this.checkContainment(part.text);
} else if (part.type === "text-end") {
this.moveCursor(0, -this.lineCount - 1);
this.clearScreenDown();
this.moveCursor(-this.lineCount - 1);
this.clearScreenDown(process.stderr);
this.resetCounts();
}