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

@ -78,25 +78,19 @@ async function main() {
}); });
const start = Date.now(); const start = Date.now();
await new StreamRenderer().renderStream(fullStream);
const streamRender = new StreamRenderer().renderStream(fullStream);
const [width] = process.stdout.getWindowSize();
const glow = Bun.spawn({
cmd: ["glow", "-w", `${width}`, "-"],
stdin: textStream,
stdout: "inherit",
});
await streamRender;
const stop = Date.now(); const stop = Date.now();
await glow.exited;
convo.messages.push({ role: "assistant", content: await text }); convo.messages.push({ role: "assistant", content: await text });
await convoFile.write(JSON.stringify(convo)); await convoFile.write(JSON.stringify(convo));
const [width] = process.stdout.getWindowSize();
await Bun.spawn({
cmd: ["glow", "-w", `${width}`, "-"],
stdin: textStream,
stdout: "inherit",
}).exited;
const { inputTokens, outputTokens } = await totalUsage; const { inputTokens, outputTokens } = await totalUsage;
const context = (inputTokens ?? 0) / 1000; const context = (inputTokens ?? 0) / 1000;
const output = (outputTokens ?? 0) / 1000; const output = (outputTokens ?? 0) / 1000;

View file

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