refactor: reuse streaming text normalizer across callbacks

This commit is contained in:
Peter Steinberger
2026-01-12 22:27:46 +00:00
parent 7ba72aeb6c
commit 27d940f5b6

View File

@@ -549,8 +549,8 @@ export async function runReplyAgent(params: {
); );
const normalizeStreamingText = ( const normalizeStreamingText = (
payload: ReplyPayload, payload: ReplyPayload,
): string | undefined => { ): { text?: string; skip: boolean } => {
if (!allowPartialStream) return undefined; if (!allowPartialStream) return { skip: true };
let text = payload.text; let text = payload.text;
if (!isHeartbeat && text?.includes("HEARTBEAT_OK")) { if (!isHeartbeat && text?.includes("HEARTBEAT_OK")) {
const stripped = stripHeartbeatToken(text, { const stripped = stripHeartbeatToken(text, {
@@ -561,18 +561,18 @@ export async function runReplyAgent(params: {
logVerbose("Stripped stray HEARTBEAT_OK token from reply"); logVerbose("Stripped stray HEARTBEAT_OK token from reply");
} }
if (stripped.shouldSkip && (payload.mediaUrls?.length ?? 0) === 0) { if (stripped.shouldSkip && (payload.mediaUrls?.length ?? 0) === 0) {
return undefined; return { skip: true };
} }
text = stripped.text; text = stripped.text;
} }
if (isSilentReplyText(text, SILENT_REPLY_TOKEN)) return undefined; if (isSilentReplyText(text, SILENT_REPLY_TOKEN)) return { skip: true };
return text; return { text, skip: false };
}; };
const handlePartialForTyping = async ( const handlePartialForTyping = async (
payload: ReplyPayload, payload: ReplyPayload,
): Promise<string | undefined> => { ): Promise<string | undefined> => {
const text = normalizeStreamingText(payload); const { text, skip } = normalizeStreamingText(payload);
if (!text) return undefined; if (skip || !text) return undefined;
await typingSignals.signalTextDelta(text); await typingSignals.signalTextDelta(text);
return text; return text;
}; };
@@ -712,21 +712,9 @@ export async function runReplyAgent(params: {
onBlockReply: onBlockReply:
blockStreamingEnabled && opts?.onBlockReply blockStreamingEnabled && opts?.onBlockReply
? async (payload) => { ? async (payload) => {
let text = payload.text; const { text, skip } = normalizeStreamingText(payload);
if (!isHeartbeat && text?.includes("HEARTBEAT_OK")) { const hasMedia = (payload.mediaUrls?.length ?? 0) > 0;
const stripped = stripHeartbeatToken(text, { if (skip && !hasMedia) return;
mode: "message",
});
if (stripped.didStrip && !didLogHeartbeatStrip) {
didLogHeartbeatStrip = true;
logVerbose(
"Stripped stray HEARTBEAT_OK token from reply",
);
}
const hasMedia = (payload.mediaUrls?.length ?? 0) > 0;
if (stripped.shouldSkip && !hasMedia) return;
text = stripped.text;
}
const taggedPayload = applyReplyTagsToPayload( const taggedPayload = applyReplyTagsToPayload(
{ {
text, text,
@@ -799,25 +787,8 @@ export async function runReplyAgent(params: {
// If a tool callback starts typing after the run finalized, we can end up with // If a tool callback starts typing after the run finalized, we can end up with
// a typing loop that never sees a matching markRunComplete(). Track and drain. // a typing loop that never sees a matching markRunComplete(). Track and drain.
const task = (async () => { const task = (async () => {
let text = payload.text; const { text, skip } = normalizeStreamingText(payload);
if (!isHeartbeat && text?.includes("HEARTBEAT_OK")) { if (skip) return;
const stripped = stripHeartbeatToken(text, {
mode: "message",
});
if (stripped.didStrip && !didLogHeartbeatStrip) {
didLogHeartbeatStrip = true;
logVerbose(
"Stripped stray HEARTBEAT_OK token from reply",
);
}
if (
stripped.shouldSkip &&
(payload.mediaUrls?.length ?? 0) === 0
) {
return;
}
text = stripped.text;
}
await typingSignals.signalTextDelta(text); await typingSignals.signalTextDelta(text);
await opts.onToolResult?.({ await opts.onToolResult?.({
text, text,