fix: unify chat process and reply presentation
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import { createInteractionRequestApi, type InteractionRequestApi } from "./interaction-requests";
|
||||
import { env } from "@/shared/config/env";
|
||||
import type { AccessTokenProvider } from "@/shared/auth/keycloak-auth";
|
||||
import { agentMessagePageSchema, type AgentMessagePage } from "./agent-messages";
|
||||
|
||||
export type AgentRunStatus = "running" | "completed" | "error" | "aborted";
|
||||
export type AgentRunStatus = "running" | "completed" | "error" | "aborted" | "waiting_input";
|
||||
export type AgentSessionPurpose = "ordinary_chat" | "task_execution" | "task_collaboration";
|
||||
|
||||
export type AgentEventTriggerContext = {
|
||||
@@ -40,6 +41,10 @@ export type AgentModelsResponse = {
|
||||
|
||||
export type AgentInputDelivery = "steer" | "queue";
|
||||
export type AgentResponseInput = { id: string; text: string; delivery: AgentInputDelivery; status: "pending" | "delivered" | "cancelled" };
|
||||
export class AgentResponseFinishedError extends Error {}
|
||||
export class AgentSessionStreamError extends Error {
|
||||
constructor(message: string, readonly status: number) { super(message); }
|
||||
}
|
||||
|
||||
export type AgentTopic = { id: string; name: string; createdAt: string; updatedAt: string };
|
||||
|
||||
@@ -115,6 +120,7 @@ export type AgentSessionStreamDataEventType =
|
||||
| "todo_update"
|
||||
| "permission_request"
|
||||
| "permission_response"
|
||||
| "interaction_request"
|
||||
| "question_request"
|
||||
| "question_response"
|
||||
| "tool_call"
|
||||
@@ -148,7 +154,8 @@ export type AgentSessionStreamEvent =
|
||||
data: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type AgentApiClient = {
|
||||
export type AgentApiClient = InteractionRequestApi & {
|
||||
getHealth: (signal: AbortSignal) => Promise<void>;
|
||||
downloadFile: (path: string) => Promise<void>;
|
||||
submitResponseInput: (sessionId: string, text: string, delivery: AgentInputDelivery, id: string) => Promise<AgentResponseInput>;
|
||||
listResponseInputs: (sessionId: string) => Promise<AgentResponseInput[]>;
|
||||
@@ -191,6 +198,7 @@ export type AgentApiClient = {
|
||||
requestId: string,
|
||||
options: { sessionId: string; reply: AgentPermissionReply; message?: string }
|
||||
) => Promise<void>;
|
||||
getQuestionStatus: (sessionId: string, requestId: string) => Promise<{ status: "pending" | "answered" | "rejected" | "invalidated" }>;
|
||||
replyQuestion: (
|
||||
requestId: string,
|
||||
options: { sessionId: string; answers: string[][] }
|
||||
@@ -239,8 +247,14 @@ export function createAgentApiClient(
|
||||
|
||||
return data as T;
|
||||
};
|
||||
|
||||
return {
|
||||
...createInteractionRequestApi(request),
|
||||
async getHealth(signal) {
|
||||
const response = await fetchWithFallback(candidates, activeBaseUrl, setActiveBaseUrl, "/health", { signal, cache: "no-store" }, undefined, undefined, "");
|
||||
if (!response.ok) throw new Error("Agent 服务暂不可用");
|
||||
const data: unknown = await response.json();
|
||||
if (!isRecord(data) || data.ok !== true || data.ready !== true) throw new Error("Agent 服务尚未就绪");
|
||||
},
|
||||
async downloadFile(path) {
|
||||
const match = /^\/api\/v1(\/session\/[^/?#]+\/files\/mcp-[a-f0-9-]{36}\.(?:zip|xlsx))$/.exec(path);
|
||||
if (!match) throw new Error("无效的会话文件地址");
|
||||
@@ -255,10 +269,14 @@ export function createAgentApiClient(
|
||||
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
||||
},
|
||||
async submitResponseInput(sessionId, text, delivery, id) {
|
||||
return requestJson<AgentResponseInput>("/responses", {
|
||||
const response = await request("/responses", {
|
||||
method: "POST", headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ input: text, conversation: sessionId, stream: false, tjwater: { delivery, input_id: id } })
|
||||
});
|
||||
const data = await response.json();
|
||||
if (response.status === 409 && data?.code === "RESPONSE_FINISHED") throw new AgentResponseFinishedError("当前回复已结束");
|
||||
if (!response.ok) throw new Error(getResponseErrorMessage(data, response.status));
|
||||
return data as AgentResponseInput;
|
||||
},
|
||||
async listResponseInputs(sessionId) {
|
||||
const result = await requestJson<{ items: AgentResponseInput[] }>(`/session/${encodeURIComponent(sessionId)}/inputs`);
|
||||
@@ -384,7 +402,7 @@ export function createAgentApiClient(
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
const data = text ? JSON.parse(text) : null;
|
||||
throw new Error(getResponseErrorMessage(data, response.status));
|
||||
throw new AgentSessionStreamError(getResponseErrorMessage(data, response.status), response.status);
|
||||
}
|
||||
|
||||
await readSessionSseStream(response, options.onEvent);
|
||||
@@ -460,6 +478,11 @@ export function createAgentApiClient(
|
||||
});
|
||||
},
|
||||
|
||||
async getQuestionStatus(sessionId, requestId) {
|
||||
const value = await requestJson<{ status: "pending" | "answered" | "rejected" | "invalidated" }>(`/session/${encodeURIComponent(sessionId)}/questions/${encodeURIComponent(requestId)}`, { cache: "no-store" });
|
||||
if (!["pending", "answered", "rejected", "invalidated"].includes(value.status)) throw new Error("问题状态核对失败");
|
||||
return value;
|
||||
},
|
||||
async replyQuestion(requestId, options) {
|
||||
await requestJson<unknown>(`/question/${encodeURIComponent(requestId)}/reply`, {
|
||||
method: "POST",
|
||||
@@ -505,7 +528,8 @@ async function fetchWithFallback(
|
||||
path: string,
|
||||
init?: RequestInit,
|
||||
getAccessToken?: AccessTokenProvider,
|
||||
getProjectId?: () => Promise<string | undefined>
|
||||
getProjectId?: () => Promise<string | undefined>,
|
||||
prefix = API_PATH
|
||||
) {
|
||||
const orderedBaseUrls = [activeBaseUrl, ...baseUrls.filter((item) => item !== activeBaseUrl)];
|
||||
let lastError: unknown;
|
||||
@@ -520,7 +544,7 @@ async function fetchWithFallback(
|
||||
|
||||
for (const baseUrl of orderedBaseUrls) {
|
||||
try {
|
||||
const response = await fetch(`${baseUrl}${API_PATH}${path}`, requestInit);
|
||||
const response = await fetch(`${baseUrl}${prefix}${path}`, requestInit);
|
||||
if (response.ok) {
|
||||
setActiveBaseUrl(baseUrl);
|
||||
return response;
|
||||
@@ -609,7 +633,7 @@ function normalizeTitle(value: unknown) {
|
||||
}
|
||||
|
||||
function readRunStatus(value: unknown): AgentRunStatus | undefined {
|
||||
return value === "running" || value === "completed" || value === "error" || value === "aborted"
|
||||
return value === "waiting_input" || value === "running" || value === "completed" || value === "error" || value === "aborted"
|
||||
? value
|
||||
: undefined;
|
||||
}
|
||||
@@ -757,6 +781,7 @@ function isSessionStreamDataEventType(value: string): value is AgentSessionStrea
|
||||
value === "execution_plan" ||
|
||||
value === "permission_request" ||
|
||||
value === "permission_response" ||
|
||||
value === "interaction_request" ||
|
||||
value === "question_request" ||
|
||||
value === "question_response" ||
|
||||
value === "tool_call" ||
|
||||
|
||||
Reference in New Issue
Block a user