fix(chat): only expose final agent response
This commit is contained in:
+56
-70
@@ -111,24 +111,40 @@ const toRuntimeModel = (model?: SupportedModel) => {
|
||||
};
|
||||
};
|
||||
|
||||
const emitFallbackMessage = async (
|
||||
const emitFinalMessage = async (
|
||||
runtime: OpencodeRuntimeAdapter,
|
||||
sessionId: string,
|
||||
clientSessionId: string,
|
||||
currentAssistantMessageIds: Set<string>,
|
||||
assistantTextParts: Map<string, Map<string, string>>,
|
||||
write: (event: string, data: Record<string, unknown>) => void,
|
||||
) => {
|
||||
const messages = await runtime.messages(sessionId);
|
||||
const assistantMessage = [...messages]
|
||||
let text = [...currentAssistantMessageIds]
|
||||
.reverse()
|
||||
.find((message) => message.info.role === "assistant");
|
||||
const parts = assistantMessage?.parts ?? [];
|
||||
const text = collectTextContent(parts);
|
||||
.map((messageId) => [...(assistantTextParts.get(messageId)?.values() ?? [])].join(""))
|
||||
.find((content) => content.length > 0) ?? "";
|
||||
|
||||
if (!text) {
|
||||
const messages = await runtime.messages(sessionId);
|
||||
const assistantMessage = [...messages]
|
||||
.reverse()
|
||||
.find(
|
||||
(message) =>
|
||||
message.info.role === "assistant" &&
|
||||
(currentAssistantMessageIds.size === 0 ||
|
||||
currentAssistantMessageIds.has(message.info.id)),
|
||||
);
|
||||
text = collectTextContent(assistantMessage?.parts ?? []);
|
||||
}
|
||||
|
||||
if (text) {
|
||||
write("token", {
|
||||
session_id: clientSessionId,
|
||||
content: text,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const streamPromptResponse = async ({
|
||||
@@ -156,15 +172,14 @@ export const streamPromptResponse = async ({
|
||||
const emittedToolParts = new Set<string>();
|
||||
const emittedQuestionToolParts = new Set<string>();
|
||||
const emittedQuestionRequestIds = new Set<string>();
|
||||
const currentAssistantMessageIds = new Set<string>();
|
||||
const assistantTextParts = new Map<string, Map<string, string>>();
|
||||
const partTypes = new Map<string, Part["type"]>();
|
||||
const pendingPartTextDeltas = new Map<string, string[]>();
|
||||
const reasoningDeltas = new Map<string, string[]>();
|
||||
const pendingTextDeltas = new Map<string, string[]>();
|
||||
const reasoningStatuses = new Map<string, "running" | "completed">();
|
||||
const toolStatuses = new Map<string, string>();
|
||||
let firstSessionEventLogged = false;
|
||||
let firstNonStatusEventLogged = false;
|
||||
let firstTokenLogged = false;
|
||||
let firstReasoningLogged = false;
|
||||
let firstToolEventLogged = false;
|
||||
let lastSessionStatus: string | null = null;
|
||||
let lastSessionStatusMessage: string | null = null;
|
||||
@@ -626,45 +641,26 @@ export const streamPromptResponse = async ({
|
||||
if (event.type === "message.updated") {
|
||||
if (event.properties.info.role === "assistant") {
|
||||
sawResponseActivity = true;
|
||||
currentAssistantMessageIds.add(event.properties.info.id);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (event.type === "message.part.delta" && event.properties.field === "text") {
|
||||
sawResponseActivity = true;
|
||||
currentAssistantMessageIds.add(event.properties.messageID);
|
||||
const partType = partTypes.get(event.properties.partID);
|
||||
if (partType === "text") {
|
||||
if (!firstTokenLogged) {
|
||||
firstTokenLogged = true;
|
||||
logDevelopmentDebug("first response token emitted", {
|
||||
...debugContext,
|
||||
partId: event.properties.partID,
|
||||
elapsedMs: Math.max(0, Date.now() - requestStartedAt),
|
||||
sincePromptDispatchMs: Math.max(0, Date.now() - promptStartedAt),
|
||||
});
|
||||
}
|
||||
emittedText = true;
|
||||
write("token", {
|
||||
session_id: clientSessionId,
|
||||
content: event.properties.delta,
|
||||
});
|
||||
} else if (partType === "reasoning") {
|
||||
if (!firstReasoningLogged) {
|
||||
firstReasoningLogged = true;
|
||||
logDevelopmentDebug("first reasoning delta received", {
|
||||
...debugContext,
|
||||
partId: event.properties.partID,
|
||||
elapsedMs: Math.max(0, Date.now() - requestStartedAt),
|
||||
sincePromptDispatchMs: Math.max(0, Date.now() - promptStartedAt),
|
||||
});
|
||||
}
|
||||
const pending = reasoningDeltas.get(event.properties.partID) ?? [];
|
||||
pending.push(event.properties.delta);
|
||||
reasoningDeltas.set(event.properties.partID, pending);
|
||||
const messageParts = assistantTextParts.get(event.properties.messageID) ?? new Map();
|
||||
messageParts.set(
|
||||
event.properties.partID,
|
||||
`${messageParts.get(event.properties.partID) ?? ""}${event.properties.delta}`,
|
||||
);
|
||||
assistantTextParts.set(event.properties.messageID, messageParts);
|
||||
} else if (!partType) {
|
||||
const pending = pendingPartTextDeltas.get(event.properties.partID) ?? [];
|
||||
const pending = pendingTextDeltas.get(event.properties.partID) ?? [];
|
||||
pending.push(event.properties.delta);
|
||||
pendingPartTextDeltas.set(event.properties.partID, pending);
|
||||
pendingTextDeltas.set(event.properties.partID, pending);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -673,23 +669,19 @@ export const streamPromptResponse = async ({
|
||||
sawResponseActivity = true;
|
||||
const part = event.properties.part;
|
||||
partTypes.set(part.id, part.type);
|
||||
if (part.type === "text" || part.type === "reasoning" || part.type === "tool") {
|
||||
currentAssistantMessageIds.add(part.messageID);
|
||||
}
|
||||
if (part.type === "text") {
|
||||
const pending = pendingPartTextDeltas.get(part.id) ?? [];
|
||||
pendingPartTextDeltas.delete(part.id);
|
||||
for (const content of pending) {
|
||||
emittedText = true;
|
||||
write("token", {
|
||||
session_id: clientSessionId,
|
||||
content,
|
||||
});
|
||||
}
|
||||
} else if (part.type === "reasoning") {
|
||||
const pending = pendingPartTextDeltas.get(part.id) ?? [];
|
||||
if (pending.length > 0) {
|
||||
const existing = reasoningDeltas.get(part.id) ?? [];
|
||||
reasoningDeltas.set(part.id, existing.concat(pending));
|
||||
}
|
||||
pendingPartTextDeltas.delete(part.id);
|
||||
const pendingText = (pendingTextDeltas.get(part.id) ?? []).join("");
|
||||
pendingTextDeltas.delete(part.id);
|
||||
const messageParts = assistantTextParts.get(part.messageID) ?? new Map();
|
||||
messageParts.set(part.id, part.text || pendingText);
|
||||
assistantTextParts.set(part.messageID, messageParts);
|
||||
} else {
|
||||
pendingTextDeltas.delete(part.id);
|
||||
}
|
||||
if (part.type === "reasoning") {
|
||||
const reasoningStatus = part.time.end ? "completed" : "running";
|
||||
if (reasoningStatuses.get(part.id) !== reasoningStatus) {
|
||||
reasoningStatuses.set(part.id, reasoningStatus);
|
||||
@@ -697,14 +689,10 @@ export const streamPromptResponse = async ({
|
||||
...debugContext,
|
||||
partId: part.id,
|
||||
status: reasoningStatus,
|
||||
chunkCount: (reasoningDeltas.get(part.id) ?? []).length,
|
||||
elapsedMs: Math.max(0, Date.now() - requestStartedAt),
|
||||
});
|
||||
}
|
||||
const reasoningDetail = buildReasoningProgressDetail(
|
||||
reasoningDeltas.get(part.id) ?? [],
|
||||
part.time.end,
|
||||
);
|
||||
const reasoningDetail = buildReasoningProgressDetail(part.time.end);
|
||||
emitProgress({
|
||||
id: part.id,
|
||||
phase: "planning",
|
||||
@@ -784,9 +772,6 @@ export const streamPromptResponse = async ({
|
||||
detail: buildToolProgressDetail(
|
||||
part.tool,
|
||||
part.state.status,
|
||||
toolParams,
|
||||
reason,
|
||||
part.state.status === "error" ? part.state.error : undefined,
|
||||
),
|
||||
});
|
||||
if (
|
||||
@@ -932,13 +917,14 @@ export const streamPromptResponse = async ({
|
||||
}
|
||||
|
||||
await promptPromise;
|
||||
if (!emittedText) {
|
||||
logDevelopmentDebug("no streamed text emitted, falling back to messages()", {
|
||||
...debugContext,
|
||||
elapsedMs: Math.max(0, Date.now() - requestStartedAt),
|
||||
});
|
||||
await emitFallbackMessage(runtime, sessionId, clientSessionId, write);
|
||||
}
|
||||
emittedText = await emitFinalMessage(
|
||||
runtime,
|
||||
sessionId,
|
||||
clientSessionId,
|
||||
currentAssistantMessageIds,
|
||||
assistantTextParts,
|
||||
write,
|
||||
);
|
||||
emitProgress({
|
||||
id: "request-received",
|
||||
phase: "start",
|
||||
|
||||
Reference in New Issue
Block a user