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:
1 parent
1513f4bc2d
commit
d543f3b924
10 files changed
+783
-104
No files matched your search
+80
-16
@@ -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 });
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user