mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-04-26 00:46:23 +02:00
Refactor StreamRenderer for improved output formatting and clarity. Enhanced visual structure for agent events, tool invocations, and usage statistics. Removed deprecated provider options from agent stream function.
This commit is contained in:
parent
0258f8284f
commit
fb542afc38
2 changed files with 75 additions and 30 deletions
|
|
@ -314,12 +314,6 @@ async function* streamLlm(
|
|||
system: instructions,
|
||||
tools,
|
||||
stopWhen: stepCountIs(1),
|
||||
providerOptions: {
|
||||
openai: {
|
||||
reasoningEffort: "low",
|
||||
reasoningSummary: "auto",
|
||||
},
|
||||
}
|
||||
});
|
||||
for await (const event of fullStream) {
|
||||
// console.log("\n\n\t>>>>\t\tstream event", JSON.stringify(event));
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export class StreamRenderer {
|
|||
private options: Required<StreamRendererOptions>;
|
||||
private reasoningActive = false;
|
||||
private textActive = false;
|
||||
private firstText = true;
|
||||
|
||||
constructor(options?: StreamRendererOptions) {
|
||||
this.options = {
|
||||
|
|
@ -96,26 +97,38 @@ export class StreamRenderer {
|
|||
|
||||
private onStart(agent: string, runId: string) {
|
||||
this.write("\n");
|
||||
this.write(this.bold(`▶ Agent ${agent} (run ${runId})`));
|
||||
this.write(this.bold(this.cyan(`╭─ Agent: ${agent}`)));
|
||||
this.write(this.dim(` │ run ${runId}`));
|
||||
this.write("\n");
|
||||
this.write(this.dim(`╰─────────────────────────────────────────────────\n`));
|
||||
}
|
||||
|
||||
private onEnd() {
|
||||
this.write(this.bold("\n■ complete\n"));
|
||||
this.write("\n");
|
||||
this.write(this.dim("─".repeat(50)));
|
||||
this.write("\n");
|
||||
this.write(this.green(this.bold("✓ Complete")));
|
||||
this.write("\n\n");
|
||||
}
|
||||
|
||||
private onError(error: string) {
|
||||
this.write(this.red(`\n✖ error: ${error}\n`));
|
||||
this.write("\n");
|
||||
this.write(this.red(this.bold("✖ Error")));
|
||||
this.write("\n");
|
||||
this.write(this.red(this.indent(error)));
|
||||
this.write("\n\n");
|
||||
}
|
||||
|
||||
private onStepStart() {
|
||||
this.write("\n");
|
||||
this.write(this.cyan(`─ Step started`));
|
||||
this.write(this.dim("│ "));
|
||||
this.write(this.dim("Step in progress..."));
|
||||
this.write("\n");
|
||||
}
|
||||
|
||||
private onStepEnd() {
|
||||
this.write(this.dim(`✓ Step finished\n`));
|
||||
// More subtle step end - just add a little spacing
|
||||
this.write(this.dim("\n"));
|
||||
}
|
||||
|
||||
private onStepMessage(stepIndex: number, message: any) {
|
||||
|
|
@ -131,18 +144,22 @@ export class StreamRenderer {
|
|||
}
|
||||
|
||||
private onStepToolInvocation(toolName: string, input: string) {
|
||||
this.write(this.cyan(`\n→ Tool invoke ${toolName}`));
|
||||
this.write("\n");
|
||||
this.write(this.cyan("┌─ ") + this.bold(this.cyan(`🔧 ${toolName}`)));
|
||||
this.write("\n");
|
||||
if (input && input.length) {
|
||||
this.write("\n" + this.dim(this.indent(this.truncate(input))) + "\n");
|
||||
} else {
|
||||
this.write(this.dim("│ ") + this.dim(this.indent(this.truncate(input)).replace(/\n/g, "\n│ ")));
|
||||
this.write("\n");
|
||||
}
|
||||
}
|
||||
|
||||
private onStepToolResult(toolName: string, result: unknown) {
|
||||
const res = this.truncate(JSON.stringify(result, null, this.options.jsonIndent));
|
||||
this.write(this.cyan(`\n← Tool result ${toolName}\n`));
|
||||
this.write(this.dim(this.indent(res)) + "\n");
|
||||
this.write(this.dim("│\n"));
|
||||
this.write(this.green("└─ ") + this.dim(this.green(`Result`)));
|
||||
this.write("\n");
|
||||
this.write(this.dim(" " + this.indent(res).replace(/\n/g, "\n ")));
|
||||
this.write("\n");
|
||||
}
|
||||
|
||||
private onReasoningStart() {
|
||||
|
|
@ -150,7 +167,8 @@ export class StreamRenderer {
|
|||
this.reasoningActive = true;
|
||||
if (this.options.showHeaders) {
|
||||
this.write("\n");
|
||||
this.write(this.dim("Reasoning: "));
|
||||
this.write(this.dim("│ "));
|
||||
this.write(this.dim(this.italic("thinking... ")));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -162,21 +180,32 @@ export class StreamRenderer {
|
|||
private onReasoningEnd() {
|
||||
if (!this.reasoningActive) return;
|
||||
this.reasoningActive = false;
|
||||
this.write(this.dim("\n"));
|
||||
this.write("\n");
|
||||
}
|
||||
|
||||
private onTextStart() {
|
||||
if (this.textActive) return;
|
||||
this.textActive = true;
|
||||
if (this.options.showHeaders) {
|
||||
if (this.options.showHeaders && this.firstText) {
|
||||
this.write("\n");
|
||||
this.write(this.bold("Assistant: "));
|
||||
this.write(this.bold("╭─ ") + this.bold("Response"));
|
||||
this.write("\n");
|
||||
this.write(this.dim("│\n"));
|
||||
this.firstText = false;
|
||||
} else if (this.options.showHeaders) {
|
||||
this.write("\n");
|
||||
this.write(this.dim("│ "));
|
||||
}
|
||||
}
|
||||
|
||||
private onTextDelta(delta: string) {
|
||||
if (!this.textActive) this.onTextStart();
|
||||
this.write(delta);
|
||||
// Add subtle left margin to assistant text for better readability
|
||||
if (delta.includes("\n")) {
|
||||
this.write(delta.replace(/\n/g, "\n "));
|
||||
} else {
|
||||
this.write(delta);
|
||||
}
|
||||
}
|
||||
|
||||
private onTextEnd() {
|
||||
|
|
@ -188,10 +217,12 @@ export class StreamRenderer {
|
|||
private onToolCall(toolCallId: string, toolName: string, input: unknown) {
|
||||
const inputStr = this.truncate(JSON.stringify(input, null, this.options.jsonIndent));
|
||||
this.write("\n");
|
||||
this.write(this.cyan(`→ Tool call ${toolName} (${toolCallId})`));
|
||||
this.write(this.magenta("┌─ ") + this.bold(this.magenta(`⚡ ${toolName}`)));
|
||||
this.write(this.dim(` (${toolCallId.slice(0, 8)}...)`));
|
||||
this.write("\n");
|
||||
this.write(this.dim(this.indent(inputStr)));
|
||||
this.write(this.dim("│ ") + this.dim(this.indent(inputStr).replace(/\n/g, "\n│ ")));
|
||||
this.write("\n");
|
||||
this.write(this.dim("└─────────────\n"));
|
||||
}
|
||||
|
||||
private onUsage(usage: {
|
||||
|
|
@ -202,13 +233,17 @@ export class StreamRenderer {
|
|||
cachedInputTokens?: number;
|
||||
}) {
|
||||
const parts: string[] = [];
|
||||
if (usage.inputTokens !== undefined) parts.push(`input=${usage.inputTokens}`);
|
||||
if (usage.outputTokens !== undefined) parts.push(`output=${usage.outputTokens}`);
|
||||
if (usage.reasoningTokens !== undefined) parts.push(`reasoning=${usage.reasoningTokens}`);
|
||||
if (usage.cachedInputTokens !== undefined) parts.push(`cached=${usage.cachedInputTokens}`);
|
||||
if (usage.totalTokens !== undefined) parts.push(`total=${usage.totalTokens}`);
|
||||
const line = parts.join(", ");
|
||||
this.write(this.dim(`\nUsage: ${line}\n`));
|
||||
if (usage.inputTokens !== undefined) parts.push(`${this.dim("in:")} ${usage.inputTokens}`);
|
||||
if (usage.outputTokens !== undefined) parts.push(`${this.dim("out:")} ${usage.outputTokens}`);
|
||||
if (usage.reasoningTokens !== undefined) parts.push(`${this.dim("reasoning:")} ${usage.reasoningTokens}`);
|
||||
if (usage.cachedInputTokens !== undefined) parts.push(`${this.dim("cached:")} ${usage.cachedInputTokens}`);
|
||||
if (usage.totalTokens !== undefined) parts.push(`${this.dim("total:")} ${this.bold(usage.totalTokens.toString())}`);
|
||||
const line = parts.join(this.dim(" | "));
|
||||
this.write("\n");
|
||||
this.write(this.dim("╭─ Usage\n"));
|
||||
this.write(this.dim("│ ") + line);
|
||||
this.write("\n");
|
||||
this.write(this.dim("╰─────────────\n"));
|
||||
}
|
||||
|
||||
// Formatting helpers
|
||||
|
|
@ -236,13 +271,29 @@ export class StreamRenderer {
|
|||
return "\x1b[2m" + text + "\x1b[0m";
|
||||
}
|
||||
|
||||
private italic(text: string): string {
|
||||
return "\x1b[3m" + text + "\x1b[0m";
|
||||
}
|
||||
|
||||
private cyan(text: string): string {
|
||||
return "\x1b[36m" + text + "\x1b[0m";
|
||||
}
|
||||
|
||||
private green(text: string): string {
|
||||
return "\x1b[32m" + text + "\x1b[0m";
|
||||
}
|
||||
|
||||
private red(text: string): string {
|
||||
return "\x1b[31m" + text + "\x1b[0m";
|
||||
}
|
||||
|
||||
private magenta(text: string): string {
|
||||
return "\x1b[35m" + text + "\x1b[0m";
|
||||
}
|
||||
|
||||
private yellow(text: string): string {
|
||||
return "\x1b[33m" + text + "\x1b[0m";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue