Separate live text and tool stream parts
This commit is contained in:
@@ -53,6 +53,8 @@ type ClawStreamItem =
|
||||
type StreamState = {
|
||||
textStarted: boolean;
|
||||
textStreamed: boolean;
|
||||
textPartIndex: number;
|
||||
activeTextId?: string;
|
||||
reasoningEnded: boolean;
|
||||
phase: "waiting" | "queued" | "running" | "done";
|
||||
runId?: string;
|
||||
@@ -112,6 +114,7 @@ export async function POST(req: Request) {
|
||||
const streamState: StreamState = {
|
||||
textStarted: false,
|
||||
textStreamed: false,
|
||||
textPartIndex: 0,
|
||||
reasoningEnded: false,
|
||||
phase: "waiting",
|
||||
announcedToolCallKeys: new Set<string>(),
|
||||
@@ -151,13 +154,9 @@ export async function POST(req: Request) {
|
||||
}
|
||||
writeToolTrace(writer, payload.transcript, streamState);
|
||||
if (!streamState.textStreamed) {
|
||||
writer.write({ type: "text-start", id: "text-1" });
|
||||
writer.write({ type: "text-delta", id: "text-1", delta: text });
|
||||
writer.write({ type: "text-end", id: "text-1" });
|
||||
} else if (streamState.textStarted) {
|
||||
writer.write({ type: "text-end", id: "text-1" });
|
||||
streamState.textStarted = false;
|
||||
writeTextDelta(writer, text, streamState);
|
||||
}
|
||||
endTextPart(writer, streamState);
|
||||
writer.write({ type: "finish-step" });
|
||||
writer.write({
|
||||
type: "finish",
|
||||
@@ -178,9 +177,9 @@ export async function POST(req: Request) {
|
||||
? err.message
|
||||
: "请求失败。";
|
||||
writer.write({ type: "reasoning-end", id: "reasoning-1" });
|
||||
writer.write({ type: "text-start", id: "text-1" });
|
||||
writer.write({ type: "text-delta", id: "text-1", delta: text });
|
||||
writer.write({ type: "text-end", id: "text-1" });
|
||||
endTextPart(writer, streamState);
|
||||
writeTextDelta(writer, text, streamState);
|
||||
endTextPart(writer, streamState);
|
||||
writer.write({ type: "finish-step" });
|
||||
writer.write({
|
||||
type: "finish",
|
||||
@@ -209,6 +208,45 @@ function formatDuration(ms: number) {
|
||||
return seconds > 0 ? `${minutes}min${seconds}s` : `${minutes}min`;
|
||||
}
|
||||
|
||||
function startTextPart(
|
||||
writer: UIMessageStreamWriter<UIMessage>,
|
||||
streamState: StreamState,
|
||||
) {
|
||||
if (streamState.textStarted && streamState.activeTextId) {
|
||||
return streamState.activeTextId;
|
||||
}
|
||||
streamState.textPartIndex += 1;
|
||||
const id = `text-${streamState.textPartIndex}`;
|
||||
streamState.activeTextId = id;
|
||||
streamState.textStarted = true;
|
||||
writer.write({ type: "text-start", id });
|
||||
return id;
|
||||
}
|
||||
|
||||
function writeTextDelta(
|
||||
writer: UIMessageStreamWriter<UIMessage>,
|
||||
delta: string,
|
||||
streamState?: StreamState,
|
||||
) {
|
||||
if (!streamState) {
|
||||
writer.write({ type: "text-delta", id: "text-1", delta });
|
||||
return;
|
||||
}
|
||||
const id = startTextPart(writer, streamState);
|
||||
writer.write({ type: "text-delta", id, delta });
|
||||
streamState.textStreamed = true;
|
||||
}
|
||||
|
||||
function endTextPart(
|
||||
writer: UIMessageStreamWriter<UIMessage>,
|
||||
streamState?: StreamState,
|
||||
) {
|
||||
if (!streamState?.textStarted || !streamState.activeTextId) return;
|
||||
writer.write({ type: "text-end", id: streamState.activeTextId });
|
||||
streamState.textStarted = false;
|
||||
streamState.activeTextId = undefined;
|
||||
}
|
||||
|
||||
function writeToolTrace(
|
||||
writer: UIMessageStreamWriter<UIMessage>,
|
||||
transcript?: ClawTranscriptEntry[],
|
||||
@@ -232,6 +270,7 @@ function writeToolTrace(
|
||||
if (!toolCallId || !toolName) continue;
|
||||
if (streamState?.streamedToolCallIds.has(toolCallId)) continue;
|
||||
|
||||
endTextPart(writer, streamState);
|
||||
writer.write({
|
||||
type: "tool-input-available",
|
||||
toolCallId,
|
||||
@@ -524,12 +563,7 @@ function writeRuntimeEvent(
|
||||
);
|
||||
}
|
||||
if (event.type === "content_delta" && event.delta) {
|
||||
if (!streamState?.textStarted) {
|
||||
writer.write({ type: "text-start", id: "text-1" });
|
||||
if (streamState) streamState.textStarted = true;
|
||||
}
|
||||
writer.write({ type: "text-delta", id: "text-1", delta: event.delta });
|
||||
if (streamState) streamState.textStreamed = true;
|
||||
writeTextDelta(writer, event.delta, streamState);
|
||||
}
|
||||
if (event.type === "tool_call_delta") {
|
||||
const key =
|
||||
@@ -539,6 +573,7 @@ function writeRuntimeEvent(
|
||||
: `index:${event.tool_call_index}`);
|
||||
if (key && !streamState?.announcedToolCallKeys.has(key)) {
|
||||
streamState?.announcedToolCallKeys.add(key);
|
||||
endTextPart(writer, streamState);
|
||||
writeReasoningDelta(
|
||||
writer,
|
||||
{
|
||||
@@ -551,26 +586,16 @@ function writeRuntimeEvent(
|
||||
}
|
||||
}
|
||||
if (event.type === "final_text_start") {
|
||||
if (!streamState?.textStarted) {
|
||||
writer.write({ type: "text-start", id: "text-1" });
|
||||
if (streamState) streamState.textStarted = true;
|
||||
}
|
||||
if (streamState) startTextPart(writer, streamState);
|
||||
}
|
||||
if (event.type === "final_text_delta" && event.delta) {
|
||||
if (!streamState?.textStarted) {
|
||||
writer.write({ type: "text-start", id: "text-1" });
|
||||
if (streamState) streamState.textStarted = true;
|
||||
}
|
||||
writer.write({ type: "text-delta", id: "text-1", delta: event.delta });
|
||||
if (streamState) streamState.textStreamed = true;
|
||||
writeTextDelta(writer, event.delta, streamState);
|
||||
}
|
||||
if (event.type === "final_text_end") {
|
||||
if (streamState?.textStarted) {
|
||||
writer.write({ type: "text-end", id: "text-1" });
|
||||
streamState.textStarted = false;
|
||||
}
|
||||
endTextPart(writer, streamState);
|
||||
}
|
||||
if (event.type === "tool_start" && event.tool_call_id && event.tool_name) {
|
||||
endTextPart(writer, streamState);
|
||||
streamState?.streamedToolCallIds.add(event.tool_call_id);
|
||||
writer.write({
|
||||
type: "tool-input-available",
|
||||
@@ -580,6 +605,7 @@ function writeRuntimeEvent(
|
||||
});
|
||||
}
|
||||
if (event.type === "tool_delta" && event.tool_call_id && event.delta) {
|
||||
endTextPart(writer, streamState);
|
||||
writeReasoningDelta(
|
||||
writer,
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user