mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-13 15:47:28 +00:00
fix(tui): emit v4 embedded chat deltas
(cherry picked from commit a6d878376b)
This commit is contained in:
@@ -207,6 +207,7 @@ describe("EmbeddedTuiBackend", () => {
|
||||
runId: "run-local-1",
|
||||
sessionKey: "agent:main:main",
|
||||
state: "delta",
|
||||
deltaText: "hello",
|
||||
message: {
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "hello" }],
|
||||
@@ -303,6 +304,65 @@ describe("EmbeddedTuiBackend", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("marks local embedded replacement deltas", async () => {
|
||||
const { EmbeddedTuiBackend } = await import("./embedded-backend.js");
|
||||
const pending = deferred<{
|
||||
payloads: Array<{ text: string }>;
|
||||
meta: Record<string, unknown>;
|
||||
}>();
|
||||
agentCommandFromIngressMock.mockReturnValueOnce(pending.promise);
|
||||
|
||||
const backend = new EmbeddedTuiBackend();
|
||||
const events: Array<{ event: string; payload: unknown }> = [];
|
||||
backend.onEvent = (evt) => {
|
||||
events.push({ event: evt.event, payload: evt.payload });
|
||||
};
|
||||
|
||||
backend.start();
|
||||
await backend.sendChat({
|
||||
sessionKey: "agent:main:main",
|
||||
message: "replace",
|
||||
runId: "run-local-replace",
|
||||
});
|
||||
|
||||
registeredListener?.({
|
||||
runId: "run-local-replace",
|
||||
stream: "assistant",
|
||||
data: { text: "Hello world" },
|
||||
});
|
||||
registeredListener?.({
|
||||
runId: "run-local-replace",
|
||||
stream: "assistant",
|
||||
data: { text: "Goodbye world" },
|
||||
});
|
||||
|
||||
pending.resolve({ payloads: [{ text: "Goodbye world" }], meta: {} });
|
||||
await flushMicrotasks();
|
||||
|
||||
const chatPayloads = events
|
||||
.filter((entry) => entry.event === "chat")
|
||||
.map(
|
||||
(entry) =>
|
||||
entry.payload as {
|
||||
state?: string;
|
||||
deltaText?: string;
|
||||
replace?: boolean;
|
||||
},
|
||||
);
|
||||
expect(
|
||||
chatPayloads
|
||||
.filter((payload) => payload.state === "delta")
|
||||
.map((payload) => ({
|
||||
state: payload.state,
|
||||
deltaText: payload.deltaText,
|
||||
replace: payload.replace,
|
||||
})),
|
||||
).toEqual([
|
||||
{ state: "delta", deltaText: "Hello world", replace: undefined },
|
||||
{ state: "delta", deltaText: "Goodbye world", replace: true },
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps a fallback response deliverable after a retryable lifecycle error", async () => {
|
||||
const { EmbeddedTuiBackend } = await import("./embedded-backend.js");
|
||||
const pending = deferred<{
|
||||
@@ -496,6 +556,7 @@ describe("EmbeddedTuiBackend", () => {
|
||||
runId: "run-tool-first",
|
||||
sessionKey: "agent:main:main",
|
||||
state: "delta",
|
||||
deltaText: "",
|
||||
message: {
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "" }],
|
||||
|
||||
@@ -60,6 +60,7 @@ type LocalRunState = {
|
||||
sessionKey: string;
|
||||
controller: AbortController;
|
||||
buffer: string;
|
||||
lastBroadcastText?: string;
|
||||
isBtw: boolean;
|
||||
question?: string;
|
||||
finalSent: boolean;
|
||||
@@ -106,6 +107,16 @@ function timeoutSecondsFromMs(timeoutMs?: number): string | undefined {
|
||||
return String(Math.max(0, Math.ceil(timeoutMs / 1000)));
|
||||
}
|
||||
|
||||
function resolveDeltaPayload(text: string, previousText: string | undefined) {
|
||||
if (previousText === undefined) {
|
||||
return { deltaText: text };
|
||||
}
|
||||
if (!text.startsWith(previousText)) {
|
||||
return { deltaText: text, replace: true as const };
|
||||
}
|
||||
return { deltaText: text.slice(previousText.length) };
|
||||
}
|
||||
|
||||
export class EmbeddedTuiBackend implements TuiBackend {
|
||||
readonly connection = { url: "local embedded" };
|
||||
|
||||
@@ -396,11 +407,17 @@ export class EmbeddedTuiBackend implements TuiBackend {
|
||||
if (!text || projected.suppress) {
|
||||
return;
|
||||
}
|
||||
const deltaPayload = resolveDeltaPayload(text, run.lastBroadcastText);
|
||||
if (!deltaPayload.deltaText && !deltaPayload.replace) {
|
||||
return;
|
||||
}
|
||||
run.registered = true;
|
||||
run.lastBroadcastText = text;
|
||||
this.emit("chat", {
|
||||
runId,
|
||||
sessionKey: run.sessionKey,
|
||||
state: "delta",
|
||||
...deltaPayload,
|
||||
message: {
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text }],
|
||||
@@ -416,6 +433,7 @@ export class EmbeddedTuiBackend implements TuiBackend {
|
||||
}
|
||||
run.finalSent = true;
|
||||
run.registered = true;
|
||||
run.lastBroadcastText = undefined;
|
||||
const projected = projectLiveAssistantBufferedText(run.buffer.trim(), {
|
||||
suppressLeadFragments: false,
|
||||
});
|
||||
@@ -445,6 +463,7 @@ export class EmbeddedTuiBackend implements TuiBackend {
|
||||
}
|
||||
run.finalSent = true;
|
||||
run.registered = true;
|
||||
run.lastBroadcastText = undefined;
|
||||
this.emit("chat", {
|
||||
runId,
|
||||
sessionKey: run.sessionKey,
|
||||
@@ -459,6 +478,7 @@ export class EmbeddedTuiBackend implements TuiBackend {
|
||||
}
|
||||
run.finalSent = true;
|
||||
run.registered = true;
|
||||
run.lastBroadcastText = undefined;
|
||||
this.emit("chat", {
|
||||
runId,
|
||||
sessionKey: run.sessionKey,
|
||||
@@ -472,10 +492,12 @@ export class EmbeddedTuiBackend implements TuiBackend {
|
||||
return;
|
||||
}
|
||||
run.registered = true;
|
||||
run.lastBroadcastText = "";
|
||||
this.emit("chat", {
|
||||
runId,
|
||||
sessionKey: run.sessionKey,
|
||||
state: "delta",
|
||||
deltaText: "",
|
||||
message: {
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "" }],
|
||||
|
||||
Reference in New Issue
Block a user