fix: harden phase-aware assistant visibility

This commit is contained in:
Eva
2026-04-06 02:02:32 +07:00
committed by Peter Steinberger
parent e611761809
commit e7311334cb
4 changed files with 61 additions and 4 deletions

View File

@@ -254,7 +254,7 @@ describe("handleMessageUpdate", () => {
expect(ctx.state.blockBuffer).toBe("");
});
it("suppresses commentary partials until a final_answer partial arrives", () => {
it("suppresses commentary partials even when they contain visible text", () => {
const onAgentEvent = vi.fn();
const ctx = {
params: {
@@ -313,6 +313,10 @@ describe("handleMessageUpdate", () => {
},
} as never);
expect(onAgentEvent).not.toHaveBeenCalled();
expect(ctx.state.deltaBuffer).toBe("");
expect(ctx.state.blockBuffer).toBe("");
handleMessageUpdate(ctx, {
type: "message_update",
message: { role: "assistant", content: [] },

View File

@@ -291,7 +291,7 @@ export function handleMessageUpdate(
: msg;
const phaseAwareVisibleText = coerceText(extractAssistantVisibleText(partialAssistant)).trim();
const deliveryPhase = resolveAssistantMessagePhase(partialAssistant);
if (deliveryPhase === "commentary" && !phaseAwareVisibleText) {
if (deliveryPhase === "commentary") {
return;
}
const shouldUsePhaseAwareBlockReply = Boolean(deliveryPhase);

View File

@@ -615,6 +615,23 @@ describe("extractAssistantVisibleText", () => {
expect(extractAssistantVisibleText(msg)).toBe("");
});
it("does not fall back to unphased legacy text when an empty final_answer block exists", () => {
const msg = makeAssistantMessage({
role: "assistant",
content: [
{ type: "text", text: "Legacy answer" },
{
type: "text",
text: " ",
textSignature: JSON.stringify({ v: 1, id: "item_final", phase: "final_answer" }),
},
],
timestamp: Date.now(),
});
expect(extractAssistantVisibleText(msg)).toBe("");
});
it("falls back to legacy unphased text when phased text is absent", () => {
const msg = makeAssistantMessage({
role: "assistant",

View File

@@ -249,6 +249,42 @@ function finalizeAssistantExtraction(msg: AssistantMessage, extracted: string):
return sanitizeUserFacingText(extracted, { errorContext });
}
function hasAssistantTextForPhase(msg: AssistantMessage, phase: AssistantPhase): boolean {
const messagePhase = normalizeAssistantPhase((msg as { phase?: unknown }).phase);
if (typeof msg.content === "string") {
return messagePhase === phase;
}
if (!Array.isArray(msg.content)) {
return false;
}
const hasExplicitPhasedTextBlocks = msg.content.some((block) => {
if (!block || typeof block !== "object") {
return false;
}
const record = block as { type?: unknown; textSignature?: unknown };
if (record.type !== "text") {
return false;
}
return Boolean(parseAssistantTextSignature(record.textSignature)?.phase);
});
return msg.content.some((block) => {
if (!block || typeof block !== "object") {
return false;
}
const record = block as { type?: unknown; textSignature?: unknown };
if (record.type !== "text") {
return false;
}
const signature = parseAssistantTextSignature(record.textSignature);
const resolvedPhase = signature?.phase ?? (hasExplicitPhasedTextBlocks ? undefined : messagePhase);
return resolvedPhase === phase;
});
}
function extractAssistantTextForPhase(msg: AssistantMessage, phase?: AssistantPhase): string {
const messagePhase = normalizeAssistantPhase((msg as { phase?: unknown }).phase);
const shouldIncludeContent = (resolvedPhase?: AssistantPhase) => {
@@ -306,8 +342,8 @@ function extractAssistantTextForPhase(msg: AssistantMessage, phase?: AssistantPh
export function extractAssistantVisibleText(msg: AssistantMessage): string {
const finalAnswerText = extractAssistantTextForPhase(msg, "final_answer");
if (finalAnswerText.trim()) {
return finalAnswerText;
if (hasAssistantTextForPhase(msg, "final_answer")) {
return finalAnswerText.trim() ? finalAnswerText : "";
}
return extractAssistantTextForPhase(msg);