This commit is contained in:
ukeate 2026-01-30 17:05:38 +05:30 committed by GitHub
commit e2f0a82202
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 69 additions and 0 deletions

View File

@ -73,6 +73,7 @@ Status: stable.
- **BREAKING:** Gateway auth mode "none" is removed; gateway now requires token/password (Tailscale Serve identity still allowed). - **BREAKING:** Gateway auth mode "none" is removed; gateway now requires token/password (Tailscale Serve identity still allowed).
### Fixes ### Fixes
- TUI: emit final assistant event when reply tags hide stream. (#4495) Thanks @ukeate.
- Telegram: use undici fetch for per-account proxy dispatcher. (#4456) Thanks @spiceoogway. - Telegram: use undici fetch for per-account proxy dispatcher. (#4456) Thanks @spiceoogway.
- Telegram: fix HTML nesting for overlapping styles and links. (#4578) Thanks @ThanhNguyxn. - Telegram: fix HTML nesting for overlapping styles and links. (#4578) Thanks @ThanhNguyxn.
- Telegram: avoid silent empty replies by tracking normalization skips before fallback. (#3796) - Telegram: avoid silent empty replies by tracking normalization skips before fallback. (#3796)

View File

@ -176,6 +176,30 @@ export function handleMessageEnd(
}); });
const text = ctx.stripBlockTags(rawText, { thinking: false, final: false }); const text = ctx.stripBlockTags(rawText, { thinking: false, final: false });
const { text: cleanedText, mediaUrls } = parseReplyDirectives(text);
const { text: previousCleanedText } = parseReplyDirectives(ctx.state.lastStreamedAssistant ?? "");
if (cleanedText && cleanedText !== previousCleanedText) {
const deltaText = cleanedText.startsWith(previousCleanedText)
? cleanedText.slice(previousCleanedText.length)
: cleanedText;
emitAgentEvent({
runId: ctx.params.runId,
stream: "assistant",
data: {
text: cleanedText,
delta: deltaText,
mediaUrls: mediaUrls?.length ? mediaUrls : undefined,
},
});
void ctx.params.onAgentEvent?.({
stream: "assistant",
data: {
text: cleanedText,
delta: deltaText,
mediaUrls: mediaUrls?.length ? mediaUrls : undefined,
},
});
}
const rawThinking = const rawThinking =
ctx.state.includeReasoning || ctx.state.streamReasoning ctx.state.includeReasoning || ctx.state.streamReasoning
? extractAssistantThinking(assistantMessage) || extractThinkingFromTaggedText(rawText) ? extractAssistantThinking(assistantMessage) || extractThinkingFromTaggedText(rawText)

View File

@ -221,6 +221,50 @@ describe("subscribeEmbeddedPiSession", () => {
expect(payloads[0]?.text).toBe("MEDIA:"); expect(payloads[0]?.text).toBe("MEDIA:");
}); });
it("emits agent events on message_end when reply tags hide final text", () => {
let handler: ((evt: unknown) => void) | undefined;
const session: StubSession = {
subscribe: (fn) => {
handler = fn;
return () => {};
},
};
const onAgentEvent = vi.fn();
subscribeEmbeddedPiSession({
session: session as unknown as Parameters<typeof subscribeEmbeddedPiSession>[0]["session"],
runId: "run",
onAgentEvent,
});
handler?.({ type: "message_start", message: { role: "assistant" } });
handler?.({
type: "message_update",
message: { role: "assistant" },
assistantMessageEvent: { type: "text_delta", delta: "[[reply_to: 123" },
});
handler?.({
type: "message_update",
message: { role: "assistant" },
assistantMessageEvent: { type: "text_delta", delta: "]] Hello" },
});
const assistantMessage = {
role: "assistant",
content: [{ type: "text", text: "[[reply_to: 123]] Hello" }],
} as AssistantMessage;
handler?.({ type: "message_end", message: assistantMessage });
const payloads = onAgentEvent.mock.calls
.map((call) => call[0]?.data as Record<string, unknown> | undefined)
.filter((value): value is Record<string, unknown> => Boolean(value));
expect(payloads.length).toBeGreaterThan(0);
const last = payloads[payloads.length - 1];
expect(last?.text).toBe("Hello");
expect(last?.delta).toBe("Hello");
});
it("emits agent events when media arrives without text", () => { it("emits agent events when media arrives without text", () => {
let handler: ((evt: unknown) => void) | undefined; let handler: ((evt: unknown) => void) | undefined;
const session: StubSession = { const session: StubSession = {