feat(agent): 聊天提交统一走 POST /agent/runs 异步网关

引入 Agent Run 网关替代直连 /sessions/:id/reply,并在 api_lockdown 白名单中放行新入口,避免策略拦截导致聊天不可用。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-06-29 22:50:00 +08:00
parent 16828a58f7
commit 8f620fa714
14 changed files with 784 additions and 89 deletions
+26 -4
View File
@@ -51,6 +51,21 @@ import { normalizeConversationMessages, normalizeUserMessageForApi } from '../ut
const API = '/api';
const DEFAULT_API_TIMEOUT_MS = 20_000;
const AGENT_RUNS_PATH = '/agent/runs';
export type AgentRun = {
id: string;
userId: string;
sessionId: string | null;
requestId: string;
status: 'queued' | 'running' | 'retryable' | 'succeeded' | 'failed';
attempts: number;
error: string | null;
createdAt: number;
updatedAt: number;
startedAt: number | null;
completedAt: number | null;
};
export class ApiError extends Error {
readonly status: number;
@@ -2038,18 +2053,25 @@ export async function loadSessionDetail(
return { session: detail, messages };
}
export async function sendReply(
sessionId: string,
export async function createAgentRun(
sessionId: string | null,
requestId: string,
userMessage: Message,
): Promise<void> {
await apiFetch(`${sessionPath(sessionId)}/reply`, {
): Promise<AgentRun> {
const result = await apiFetch<{ run: AgentRun }>(AGENT_RUNS_PATH, {
method: 'POST',
body: JSON.stringify({
session_id: sessionId,
request_id: requestId,
user_message: normalizeUserMessageForApi(userMessage),
}),
});
return result.run;
}
export async function getAgentRun(runId: string): Promise<AgentRun> {
const result = await apiFetch<{ run: AgentRun }>(`${AGENT_RUNS_PATH}/${encodeURIComponent(runId)}`);
return result.run;
}
export async function cancelRequest(sessionId: string, requestId: string): Promise<void> {