refactor: consolidate requests and finalize agent tool states
Share project JSON transport, interaction replies and report selectors. Settle tool progress consistently for direct streams, reconnects and history, preserving failure reasons and stopped state.
This commit is contained in:
@@ -121,6 +121,7 @@ export type AgentSessionStreamDataEventType =
|
||||
| "todo_update"
|
||||
| "permission_request"
|
||||
| "permission_response"
|
||||
| "run_status"
|
||||
| "interaction_request"
|
||||
| "question_request"
|
||||
| "question_response"
|
||||
@@ -131,6 +132,7 @@ export type AgentSessionStreamDataEventType =
|
||||
| "ui_envelope"
|
||||
| "session_title"
|
||||
| "done"
|
||||
| "aborted"
|
||||
| "error"
|
||||
| "auth_required"
|
||||
| "credential_refresh_required"
|
||||
@@ -837,6 +839,7 @@ function isSessionStreamDataEventType(value: string): value is AgentSessionStrea
|
||||
value === "execution_plan" ||
|
||||
value === "permission_request" ||
|
||||
value === "permission_response" ||
|
||||
value === "run_status" ||
|
||||
value === "interaction_request" ||
|
||||
value === "question_request" ||
|
||||
value === "question_response" ||
|
||||
@@ -847,6 +850,7 @@ function isSessionStreamDataEventType(value: string): value is AgentSessionStrea
|
||||
value === "ui_envelope" ||
|
||||
value === "session_title" ||
|
||||
value === "done" ||
|
||||
value === "aborted" ||
|
||||
value === "error" ||
|
||||
value === "credential_refresh_required" ||
|
||||
value === "credential_refreshed" ||
|
||||
|
||||
@@ -1066,6 +1066,11 @@ function BackendMessage({
|
||||
<AgentUserMessageText content={message.content} />
|
||||
)
|
||||
) : null}
|
||||
{message.role === "assistant" && message.runError && message.content !== `错误:${message.runError}` ? (
|
||||
<p role="status" className="whitespace-pre-wrap break-words text-sm text-destructive">
|
||||
错误:{message.runError}
|
||||
</p>
|
||||
) : null}
|
||||
{message.role === "assistant" && processComplete && hasProcessError ? (
|
||||
<p role="status" className="text-xs text-[var(--status-warning-foreground)]">
|
||||
部分查询或工具未完成,可展开处理过程查看原因。
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { CheckCircle2, LoaderCircle, XCircle } from "lucide-react";
|
||||
import { Ban, CheckCircle2, LoaderCircle, XCircle } from "lucide-react";
|
||||
import { StatusIcon, type StatusTone } from "@/shared/ui/status";
|
||||
import type { AgentChatProgress } from "../types";
|
||||
|
||||
const states = {
|
||||
completed: { label: "已完成", tone: "success", Icon: CheckCircle2 },
|
||||
cancelled: { label: "已中止", tone: "neutral", Icon: Ban },
|
||||
error: { label: "失败", tone: "danger", Icon: XCircle },
|
||||
running: { label: "进行中", tone: "info", Icon: LoaderCircle }
|
||||
} satisfies Record<
|
||||
|
||||
@@ -55,6 +55,7 @@ export {
|
||||
cancelRunningTodos,
|
||||
completeRunningProgress,
|
||||
finalizeAssistantMessageAfterAbort,
|
||||
finalizeAssistantMessageAfterError,
|
||||
toTodoUpdate,
|
||||
upsertPermission,
|
||||
upsertProgress,
|
||||
|
||||
@@ -73,6 +73,8 @@ describe("agent session state", () => {
|
||||
requestId: "permission-1",
|
||||
status: "approved_once"
|
||||
});
|
||||
expect(applyPermissionResponse(permissions, { request_id: "permission-1", status: "error", error: "native unavailable" })?.[0])
|
||||
.toMatchObject({ status: "error", error: "native unavailable" });
|
||||
});
|
||||
|
||||
it("dedupes question requests by tool call id", () => {
|
||||
@@ -152,7 +154,7 @@ describe("agent session state", () => {
|
||||
const next = finalizeAssistantMessageAfterAbort(message);
|
||||
|
||||
expect(next.content).toBe("请求已中止。");
|
||||
expect(next.progress?.[0].status).toBe("completed");
|
||||
expect(next.progress?.[0].status).toBe("cancelled");
|
||||
expect(next.permissions?.[0].status).toBe("aborted");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -47,7 +47,10 @@ export function upsertProgress(progress: AgentChatProgress[] | undefined, data:
|
||||
return next;
|
||||
}
|
||||
|
||||
export function completeRunningProgress(progress: AgentChatProgress[] | undefined) {
|
||||
export function completeRunningProgress(
|
||||
progress: AgentChatProgress[] | undefined,
|
||||
status: "completed" | "error" | "cancelled" = "completed"
|
||||
) {
|
||||
return progress?.map((item) => {
|
||||
if (item.status !== "running") {
|
||||
return item;
|
||||
@@ -56,7 +59,7 @@ export function completeRunningProgress(progress: AgentChatProgress[] | undefine
|
||||
const endedAt = Date.now();
|
||||
return {
|
||||
...item,
|
||||
status: "completed" as const,
|
||||
status,
|
||||
endedAt,
|
||||
elapsedMs: undefined,
|
||||
elapsedSnapshotAt: undefined,
|
||||
@@ -154,9 +157,9 @@ export function applyPermissionResponse(
|
||||
permission.requestId === requestId
|
||||
? {
|
||||
...permission,
|
||||
status: toPermissionStatus(readString(payload.reply)),
|
||||
status: payload.status === "error" ? "error" as const : toPermissionStatus(readString(payload.reply)),
|
||||
repliedAt: Date.now(),
|
||||
error: undefined
|
||||
error: payload.status === "error" ? readString(payload.error) : undefined
|
||||
}
|
||||
: permission
|
||||
);
|
||||
@@ -235,7 +238,7 @@ export function applyQuestionResponse(questions: AgentQuestionRequest[] | undefi
|
||||
}
|
||||
|
||||
export function finalizeAssistantMessageAfterAbort(message: AgentChatMessage): AgentChatMessage {
|
||||
const progress = completeRunningProgress(message.progress);
|
||||
const progress = completeRunningProgress(message.progress, "cancelled");
|
||||
const todos = cancelRunningTodos(message.todos);
|
||||
|
||||
return {
|
||||
@@ -266,6 +269,18 @@ export function finalizeAssistantMessageAfterAbort(message: AgentChatMessage): A
|
||||
};
|
||||
}
|
||||
|
||||
export function finalizeAssistantMessageAfterError(message: AgentChatMessage, reason: string): AgentChatMessage {
|
||||
const settled = finalizeAssistantMessageAfterAbort(message);
|
||||
return {
|
||||
...settled,
|
||||
content: message.content || `错误:${reason}`,
|
||||
runError: reason,
|
||||
progress: settled.progress?.map((item, index) => message.progress?.[index].status === "running"
|
||||
? { ...item, status: "error", detail: item.detail ? `${item.detail}\n${reason}` : reason }
|
||||
: item)
|
||||
};
|
||||
}
|
||||
|
||||
function toPermissionStatus(reply: string | undefined): AgentPermissionStatus {
|
||||
if (reply === "always") {
|
||||
return "approved_always";
|
||||
@@ -340,7 +355,7 @@ function toToolRef(value: unknown): AgentInteractionToolRef | undefined {
|
||||
}
|
||||
|
||||
function readProgressStatus(value: unknown): AgentChatProgress["status"] {
|
||||
return value === "completed" || value === "error" ? value : "running";
|
||||
return value === "completed" || value === "error" || value === "cancelled" ? value : "running";
|
||||
}
|
||||
|
||||
function readTodoStatus(value: unknown): AgentTodoItem["status"] | undefined {
|
||||
|
||||
@@ -2,6 +2,7 @@ export type AgentChatMessage = {
|
||||
id: string;
|
||||
role: "user" | "assistant";
|
||||
content: string;
|
||||
runError?: string;
|
||||
answerParts?: import("./answer-ui/contract").AnswerUiPart[];
|
||||
finalAnswer?: { content: string; uiBlockIds: string[] };
|
||||
progress?: AgentChatProgress[];
|
||||
@@ -24,7 +25,7 @@ export type AgentChatProgress = {
|
||||
tool?: { name: string; callId?: string; input?: Record<string, unknown> };
|
||||
id: string;
|
||||
phase: string;
|
||||
status: "running" | "completed" | "error";
|
||||
status: "running" | "completed" | "error" | "cancelled";
|
||||
title: string;
|
||||
detail?: string;
|
||||
startedAt?: number;
|
||||
|
||||
Reference in New Issue
Block a user