fix: print the running tool once, not twice (#17)

This commit was merged in pull request #17.
This commit is contained in:
2026-10-05 09:49:39 +00:00
parent 26e05dea5b
commit ff99778e5d
7 changed files with 198 additions and 34 deletions
+29 -8
View File
@@ -1,5 +1,6 @@
import { imageSource } from "./attachments";
import { repairCodeBlocks } from "./code";
import { describeActivity } from "./thread";
import type { Attachment, ChatTurn, ThreadMessage } from "./types";
/**
@@ -82,17 +83,37 @@ function renderMessage(message: ThreadMessage, options: TranscriptOptions): stri
}
// While a tool is running there is no answer text yet, so the activity line
// stands in and the user can see what Lam is actually doing.
const placeholder = message.status ? `_${message.status}…_` : "_Thinking…_";
const body = message.text.trim()
// stands in and the user can see what Lam is actually doing. It is shown
// whether or not tokens have arrived, because a tool that starts after the
// answer has begun would otherwise be invisible for the rest of the turn.
const activityLine =
message.isStreaming && message.activity ? `_${describeActivity(message.activity)}…_` : undefined;
/** The answer, or whatever stands in for it while there is none. */
const answered = message.text.trim();
const body = answered
? // Fences without a language render as a plain dark panel, and a lone
// inline command renders as a chip with no Copy button at all. Both are
// repaired before the markdown reaches the renderer.
repairCodeBlocks(message.text.trim())
: message.isStreaming
? placeholder
: "_No response._";
const sections = [message.isStreaming ? `${body} ${STREAMING_CURSOR}` : body];
repairCodeBlocks(answered)
: !message.isStreaming
? "_No response._"
: activityLine
? // The activity line above is already saying what is being waited on, so
// printing the placeholder as well put the same sentence on screen
// twice: once as a note above the transcript and once as the stand-in.
undefined
: message.status
? `_${message.status}…_`
: "_Thinking…_";
// The cursor trails whichever of the two is written last, so it always sits at
// the end of the text arriving rather than on a line of its own.
const written = [activityLine, body].filter((line): line is string => Boolean(line));
const last = written.length - 1;
const sections = written.map((line, index) =>
message.isStreaming && index === last ? `${line} ${STREAMING_CURSOR}` : line,
);
// The one thing allowed to break the plain-text rule, because it means
// something went wrong and should not be skimmed past.