feat(ai): session persist + continue HTTP/UI, checkpoints, queue

落地 docs/16 P1:会话落盘与真续跑(prior tool 轨迹)、incomplete「加预算继续」、
awaiting_confirm outcome、同参拒+第4/6轮检查点、侧栏忙时单槽排队。
Ask 真 token 流式记缺口(三协议 SSE 成本高)。
This commit is contained in:
logan committed 2026-10-15 14:18:18 +08:00
1 parent 1513f4bc2d
commit d543f3b924
10 files changed
+783 -104

No files matched your search

+80 -16
View File
@@ -548,16 +548,49 @@ export type AiAgentRes = {
stepsBudget?: number;
};
export async function callAiAgent(payload: {
prompt: string;
connectionId?: string;
driver?: string;
database?: string;
model?: string;
}) {
export async function callAiAgent(payload: AiAgentStreamPayload) {
return post<AiAgentRes>("/api/ai/agent", payload);
}
export type AiSessionSummary = {
id: string;
title?: string;
status: string;
createdAt: string;
updatedAt: string;
msgCount: number;
lastRunId?: string;
};
export async function listAiSessions() {
return get<{ sessions: AiSessionSummary[] }>("/api/ai/sessions");
}
export async function getAiSession(id: string) {
return get<{
id: string;
title?: string;
status: string;
messages: unknown[];
runs?: unknown[];
}>(`/api/ai/sessions/${encodeURIComponent(id)}`);
}
export async function continueAiSession(
id: string,
payload: {
prompt?: string;
connectionId?: string;
driver?: string;
database?: string;
model?: string;
maxRounds?: number;
stream?: boolean;
},
) {
return post<AiAgentRes>(`/api/ai/sessions/${encodeURIComponent(id)}/continue`, payload);
}
export type AiAgentQueryResult = {
columns: string[];
rows: Record<string, unknown>[];
@@ -579,18 +612,26 @@ export type AiAgentStreamHandlers = {
onStep?: (step: AiAgentStepExt) => void;
onDelta?: (delta: string) => void;
onDone?: (res: AiAgentRes) => void;
onIncomplete?: (res: AiAgentRes) => void;
onSession?: (meta: { sessionId?: string; runId?: string; runOutcome?: string; resumable?: boolean }) => void;
onError?: (error: string, partial?: Partial<AiAgentRes>) => void;
};
/** Agent SSE(stream=true);不可用时回落非流式。signal 供「停止」。 */
export type AiAgentStreamPayload = {
prompt: string;
connectionId?: string;
driver?: string;
database?: string;
model?: string;
sessionId?: string;
continue?: boolean;
maxRounds?: number;
prior?: unknown[];
};
export async function callAiAgentStream(
payload: {
prompt: string;
connectionId?: string;
driver?: string;
database?: string;
model?: string;
},
payload: AiAgentStreamPayload,
handlers: AiAgentStreamHandlers,
signal?: AbortSignal,
): Promise<AiAgentRes> {
@@ -636,15 +677,38 @@ export async function callAiAgentStream(
const d = String(ev.delta || "");
content += d;
handlers.onDelta?.(d);
} else if (typ === "done") {
} else if (typ === "done" || typ === "incomplete") {
finalRes = {
content: String(ev.content ?? content),
sql: ev.sql ? String(ev.sql) : undefined,
steps: (ev.steps as AiAgentStep[]) || steps,
rounds: Number(ev.rounds ?? rounds) || 0,
runId: ev.runId ? String(ev.runId) : undefined,
sessionId: ev.sessionId ? String(ev.sessionId) : undefined,
runOutcome: ev.runOutcome ? String(ev.runOutcome) : typ === "incomplete" ? "incomplete" : undefined,
incompleteReason: ev.incompleteReason ? String(ev.incompleteReason) : undefined,
resumable: !!ev.resumable,
stepsBudget: ev.stepsBudget != null ? Number(ev.stepsBudget) : undefined,
};
rounds = finalRes.rounds;
handlers.onDone?.(finalRes);
if (typ === "incomplete") handlers.onIncomplete?.(finalRes);
else handlers.onDone?.(finalRes);
} else if (typ === "session") {
handlers.onSession?.({
sessionId: ev.content ? String(ev.content) : ev.sessionId ? String(ev.sessionId) : undefined,
runId: ev.runId ? String(ev.runId) : undefined,
runOutcome: ev.runOutcome ? String(ev.runOutcome) : undefined,
resumable: !!ev.resumable,
});
if (finalRes) {
finalRes = {
...finalRes,
sessionId: (ev.content ? String(ev.content) : finalRes.sessionId) || finalRes.sessionId,
runId: ev.runId ? String(ev.runId) : finalRes.runId,
runOutcome: ev.runOutcome ? String(ev.runOutcome) : finalRes.runOutcome,
resumable: ev.resumable != null ? !!ev.resumable : finalRes.resumable,
};
}
} else if (typ === "error") {
handlers.onError?.(String(ev.error || "agent error"), { content, steps, rounds: Number(ev.rounds) || 0 });
}