fix(canvas): 云端 Agent - 参数错误有限重试并折叠中间失败
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import { agentToolRetry } from "./agent-tool-retry";
|
||||
|
||||
export const AGENT_TOOL_METADATA: Record<string, { summary: string | ((context: { pending: boolean; detail?: unknown }) => string); failureMessage: string }> = {
|
||||
canvas_list_node_types: { summary: "已读取可用节点类型", failureMessage: "获取可用节点类型失败" },
|
||||
canvas_get_state: { summary: "已读取当前画布", failureMessage: "获取画布内容失败" },
|
||||
@@ -43,7 +45,7 @@ export function agentToolCategoryLabel(toolName: string, category: AgentToolCate
|
||||
return "操作画布";
|
||||
}
|
||||
|
||||
type ToolStatus = "completed" | "failed" | "noop" | "rejected" | "pending";
|
||||
type ToolStatus = "completed" | "failed" | "noop" | "rejected" | "pending" | "retrying";
|
||||
|
||||
function field(value: unknown, key: string): unknown {
|
||||
return value && typeof value === "object" ? (value as Record<string, unknown>)[key] : undefined;
|
||||
@@ -51,6 +53,7 @@ function field(value: unknown, key: string): unknown {
|
||||
|
||||
export function agentToolStatus(title: string, text: string, detail?: unknown): ToolStatus {
|
||||
const event = field(detail, "eventType");
|
||||
if (event === "tool_failed" && agentToolRetry(detail)?.status === "retrying") return "retrying";
|
||||
if (event === "tool_completed" || event === "generation_task_created" || event === "canvas_updated") return "completed";
|
||||
if (event === "tool_failed") return "failed";
|
||||
const raw = `${title} ${text} ${field(detail, "error") || ""}`;
|
||||
|
||||
35
web/src/lib/canvas/agent-tool-retry.ts
Normal file
35
web/src/lib/canvas/agent-tool-retry.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
type RetryStatus = "retrying" | "recovered" | "exhausted";
|
||||
export type AgentToolRetry = { groupId: string; attempt: number; maxAttempts: number; status: RetryStatus };
|
||||
type RetryMessage = { id: string; role: string; title?: string; text: string; detail?: unknown };
|
||||
export type AgentToolRetryAttempt = { id: string; text: string; detail: unknown };
|
||||
|
||||
function record(value: unknown): Record<string, unknown> {
|
||||
return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
||||
}
|
||||
|
||||
export function agentToolRetry(detail: unknown): AgentToolRetry | undefined {
|
||||
const retry = record(record(detail).retry);
|
||||
if (typeof retry.groupId !== "string" || !retry.groupId || typeof retry.attempt !== "number" || !Number.isInteger(retry.attempt) || retry.attempt < 1 || typeof retry.maxAttempts !== "number" || !Number.isInteger(retry.maxAttempts) || retry.maxAttempts < retry.attempt) return;
|
||||
if (retry.status !== "retrying" && retry.status !== "recovered" && retry.status !== "exhausted") return;
|
||||
return retry as AgentToolRetry;
|
||||
}
|
||||
|
||||
// Run-scoped group IDs and event IDs keep SSE replay idempotent. Successful
|
||||
// calls retain their ordinary result card; only failed attempts are folded.
|
||||
export function mergeAgentToolRetry<T extends RetryMessage>(messages: T[], message: T): T[] {
|
||||
const retry = agentToolRetry(message.detail);
|
||||
if (!retry) return messages;
|
||||
const id = `tool-retry:${retry.groupId}`;
|
||||
const index = messages.findIndex((item) => item.id === id);
|
||||
const previous = index >= 0 ? messages[index] : undefined;
|
||||
const previousDetail = record(previous?.detail);
|
||||
const attempts: AgentToolRetryAttempt[] = Array.isArray(previousDetail.retryAttempts) ? [...previousDetail.retryAttempts] : [];
|
||||
if (retry.status !== "recovered" && !attempts.some((item) => item.id === message.id)) attempts.push({ id: message.id, text: message.text, detail: message.detail });
|
||||
const previousRetry = agentToolRetry(previous?.detail);
|
||||
// An old failed event must not undo a later recovery during reconnection.
|
||||
const previousIsFinal = previousRetry?.status === "recovered" || previousRetry?.status === "exhausted";
|
||||
const latest = previousRetry && (previousIsFinal || previousRetry.attempt > retry.attempt) ? previousRetry : retry;
|
||||
const group = { ...message, id, detail: { ...record(message.detail), retry: latest, retryAttempts: attempts } };
|
||||
if (index < 0) return [...messages, group];
|
||||
return messages.map((item, itemIndex) => itemIndex === index ? group : item);
|
||||
}
|
||||
Reference in New Issue
Block a user