fix(chat): only expose final agent response
This commit is contained in:
@@ -15,6 +15,240 @@ const createEventStream = (events: unknown[]) => ({
|
||||
});
|
||||
|
||||
describe("streamPromptResponse", () => {
|
||||
it("emits only the final assistant text after tool-driven intermediate messages", async () => {
|
||||
const runtime = {
|
||||
subscribeEvents: async () =>
|
||||
createEventStream([
|
||||
{
|
||||
type: "message.part.delta",
|
||||
properties: {
|
||||
sessionID: "runtime-session-1",
|
||||
messageID: "assistant-intermediate",
|
||||
partID: "text-part-intermediate",
|
||||
field: "text",
|
||||
delta: "正在加载工作流并尝试分页参数。",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "message.part.updated",
|
||||
properties: {
|
||||
sessionID: "runtime-session-1",
|
||||
part: {
|
||||
id: "text-part-intermediate",
|
||||
sessionID: "runtime-session-1",
|
||||
messageID: "assistant-intermediate",
|
||||
type: "text",
|
||||
text: "正在加载工作流并尝试分页参数。",
|
||||
time: { start: 1, end: 2 },
|
||||
},
|
||||
time: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "message.part.delta",
|
||||
properties: {
|
||||
sessionID: "runtime-session-1",
|
||||
messageID: "assistant-final",
|
||||
partID: "text-part-final",
|
||||
field: "text",
|
||||
delta: "共识别 56 条瓶颈管段,建议优先改造 Top 5。",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "message.part.updated",
|
||||
properties: {
|
||||
sessionID: "runtime-session-1",
|
||||
part: {
|
||||
id: "text-part-final",
|
||||
sessionID: "runtime-session-1",
|
||||
messageID: "assistant-final",
|
||||
type: "text",
|
||||
text: "共识别 56 条瓶颈管段,建议优先改造 Top 5。",
|
||||
time: { start: 3, end: 4 },
|
||||
},
|
||||
time: 4,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "session.idle",
|
||||
properties: { sessionID: "runtime-session-1" },
|
||||
},
|
||||
]),
|
||||
prompt: async () => undefined,
|
||||
messages: async () => [
|
||||
{
|
||||
info: { id: "assistant-intermediate", role: "assistant" },
|
||||
parts: [
|
||||
{
|
||||
id: "text-part-intermediate",
|
||||
sessionID: "runtime-session-1",
|
||||
messageID: "assistant-intermediate",
|
||||
type: "text",
|
||||
text: "正在加载工作流并尝试分页参数。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
info: { id: "assistant-final", role: "assistant" },
|
||||
parts: [
|
||||
{
|
||||
id: "text-part-final",
|
||||
sessionID: "runtime-session-1",
|
||||
messageID: "assistant-final",
|
||||
type: "text",
|
||||
text: "共识别 56 条瓶颈管段,建议优先改造 Top 5。",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
} as unknown as OpencodeRuntimeAdapter;
|
||||
const events: Array<{ event: string; data: Record<string, unknown> }> = [];
|
||||
|
||||
await streamPromptResponse({
|
||||
runtime,
|
||||
sessionId: "runtime-session-1",
|
||||
clientSessionId: "client-session-1",
|
||||
message: "分析管网瓶颈",
|
||||
write: (event, data) => events.push({ event, data }),
|
||||
});
|
||||
|
||||
expect(
|
||||
events
|
||||
.filter((item) => item.event === "token")
|
||||
.map((item) => item.data.content)
|
||||
.join(""),
|
||||
).toBe("共识别 56 条瓶颈管段,建议优先改造 Top 5。");
|
||||
});
|
||||
|
||||
it("uses the final text event cache when the messages lookup fails", async () => {
|
||||
const runtime = {
|
||||
subscribeEvents: async () =>
|
||||
createEventStream([
|
||||
{
|
||||
type: "message.part.updated",
|
||||
properties: {
|
||||
sessionID: "runtime-session-1",
|
||||
part: {
|
||||
id: "text-part-final",
|
||||
sessionID: "runtime-session-1",
|
||||
messageID: "assistant-final",
|
||||
type: "text",
|
||||
text: "最终分析结果。",
|
||||
time: { start: 1, end: 2 },
|
||||
},
|
||||
time: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "session.idle",
|
||||
properties: { sessionID: "runtime-session-1" },
|
||||
},
|
||||
]),
|
||||
prompt: async () => undefined,
|
||||
messages: async () => {
|
||||
throw new Error("transient messages lookup failure");
|
||||
},
|
||||
} as unknown as OpencodeRuntimeAdapter;
|
||||
const events: Array<{ event: string; data: Record<string, unknown> }> = [];
|
||||
|
||||
const result = await streamPromptResponse({
|
||||
runtime,
|
||||
sessionId: "runtime-session-1",
|
||||
clientSessionId: "client-session-1",
|
||||
message: "分析管网瓶颈",
|
||||
write: (event, data) => events.push({ event, data }),
|
||||
});
|
||||
|
||||
expect(result.failed).toBe(false);
|
||||
expect(
|
||||
events
|
||||
.filter((item) => item.event === "token")
|
||||
.map((item) => item.data.content)
|
||||
.join(""),
|
||||
).toBe("最终分析结果。");
|
||||
});
|
||||
|
||||
it("keeps reasoning, tool parameters, and raw errors out of progress details", async () => {
|
||||
const runtime = {
|
||||
subscribeEvents: async () =>
|
||||
createEventStream([
|
||||
{
|
||||
type: "message.part.updated",
|
||||
properties: {
|
||||
sessionID: "runtime-session-1",
|
||||
part: {
|
||||
id: "reasoning-part-1",
|
||||
sessionID: "runtime-session-1",
|
||||
messageID: "assistant-1",
|
||||
type: "reasoning",
|
||||
text: "内部推理:尝试 limit=5000 并读取临时路径。",
|
||||
time: { start: 1, end: 2 },
|
||||
},
|
||||
time: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "message.part.delta",
|
||||
properties: {
|
||||
sessionID: "runtime-session-1",
|
||||
messageID: "assistant-1",
|
||||
partID: "reasoning-part-1",
|
||||
field: "text",
|
||||
delta: "内部推理:尝试 limit=5000 并读取临时路径。",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "message.part.updated",
|
||||
properties: {
|
||||
sessionID: "runtime-session-1",
|
||||
part: {
|
||||
id: "tool-part-1",
|
||||
sessionID: "runtime-session-1",
|
||||
messageID: "assistant-1",
|
||||
type: "tool",
|
||||
callID: "call-1",
|
||||
tool: "tjwater_cli",
|
||||
state: {
|
||||
status: "error",
|
||||
input: {
|
||||
command: "network get-all-pipes-properties --limit 5000",
|
||||
reason: "尝试突破分页限制",
|
||||
},
|
||||
error: "HTTP_422 raw backend payload with trace_id=secret-trace",
|
||||
time: { start: 1, end: 2 },
|
||||
},
|
||||
},
|
||||
time: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "session.idle",
|
||||
properties: { sessionID: "runtime-session-1" },
|
||||
},
|
||||
]),
|
||||
prompt: async () => undefined,
|
||||
messages: async () => [],
|
||||
} as unknown as OpencodeRuntimeAdapter;
|
||||
const events: Array<{ event: string; data: Record<string, unknown> }> = [];
|
||||
|
||||
await streamPromptResponse({
|
||||
runtime,
|
||||
sessionId: "runtime-session-1",
|
||||
clientSessionId: "client-session-1",
|
||||
message: "分析管网瓶颈",
|
||||
write: (event, data) => events.push({ event, data }),
|
||||
});
|
||||
|
||||
const reasoningProgress = events.find(
|
||||
(item) => item.event === "progress" && item.data.id === "reasoning-part-1",
|
||||
);
|
||||
const toolProgress = events.find(
|
||||
(item) => item.event === "progress" && item.data.id === "tool-part-1",
|
||||
);
|
||||
expect(reasoningProgress?.data.detail).toBe("分析步骤已整理完成。");
|
||||
expect(toolProgress?.data.detail).toBe("tjwater_cli 调用失败。");
|
||||
});
|
||||
|
||||
it("forwards opencode permission requests as SSE payloads", async () => {
|
||||
const runtime = {
|
||||
subscribeEvents: async () =>
|
||||
|
||||
Reference in New Issue
Block a user