feat: add project-aware agent workspace
This commit is contained in:
@@ -39,12 +39,15 @@ export type AgentModelsResponse = {
|
||||
};
|
||||
|
||||
export type AgentInputDelivery = "steer" | "queue";
|
||||
export type AgentResponseInput = { id: string; text: string; delivery: AgentInputDelivery; status: "pending" | "delivered" };
|
||||
export type AgentResponseInput = { id: string; text: string; delivery: AgentInputDelivery; status: "pending" | "delivered" | "cancelled" };
|
||||
|
||||
export type AgentTopic = { id: string; name: string; createdAt: string; updatedAt: string };
|
||||
|
||||
export type AgentChatSession = {
|
||||
id?: string;
|
||||
session_id: string;
|
||||
title?: string;
|
||||
topic_id?: string | null;
|
||||
status?: string;
|
||||
run_status?: AgentRunStatus;
|
||||
is_streaming?: boolean;
|
||||
@@ -64,12 +67,16 @@ export type AgentChatSessionSummary = {
|
||||
trigger?: { subjectId: string; workflow: "water_quality_complaint"; occurredAt: string };
|
||||
id: string;
|
||||
title: string;
|
||||
topicId?: string | null;
|
||||
topicName?: string;
|
||||
searchSnippet?: string;
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
status?: string;
|
||||
parentSessionId?: string;
|
||||
isStreaming?: boolean;
|
||||
runStatus?: AgentRunStatus;
|
||||
needsInput?: boolean;
|
||||
purpose: AgentSessionPurpose;
|
||||
taskId?: string;
|
||||
};
|
||||
@@ -89,10 +96,15 @@ export type AgentPromptSuggestion = {
|
||||
activityId: string | null;
|
||||
};
|
||||
|
||||
export type AgentMessagePagination = { hasMore: boolean; beforeCursor: string | null };
|
||||
export type AgentSessionPage = { sessions: AgentChatSessionSummary[]; hasMore: boolean; nextCursor: string | null };
|
||||
export type AgentHistoryMessagePage = { messages: unknown[]; messagePage: AgentMessagePagination };
|
||||
|
||||
export type AgentLoadedChatSession = AgentChatSessionSummary & {
|
||||
sessionId: string;
|
||||
isTitleManuallyEdited: boolean;
|
||||
messages: unknown[];
|
||||
messagePage?: AgentMessagePagination;
|
||||
};
|
||||
|
||||
export type AgentSessionStreamDataEventType =
|
||||
@@ -124,6 +136,7 @@ export type AgentSessionStreamEvent =
|
||||
| {
|
||||
type: "state";
|
||||
executionPlans?: unknown[];
|
||||
messagePage?: AgentMessagePagination;
|
||||
sessionId: string;
|
||||
messages: unknown[];
|
||||
isStreaming: boolean;
|
||||
@@ -136,20 +149,30 @@ export type AgentSessionStreamEvent =
|
||||
};
|
||||
|
||||
export type AgentApiClient = {
|
||||
downloadFile: (path: string) => Promise<void>;
|
||||
submitResponseInput: (sessionId: string, text: string, delivery: AgentInputDelivery, id: string) => Promise<AgentResponseInput>;
|
||||
listResponseInputs: (sessionId: string) => Promise<AgentResponseInput[]>;
|
||||
updateResponseInput: (sessionId: string, id: string, delivery: AgentInputDelivery) => Promise<AgentResponseInput>;
|
||||
cancelResponseInput: (sessionId: string, id: string) => Promise<AgentResponseInput>;
|
||||
listAgentMessages: (sessionId: string, options?: { cursor?: string; signal?: AbortSignal }) => Promise<AgentMessagePage>;
|
||||
streamAgentMessages: (sessionId: string, options: { signal?: AbortSignal; onMessage: () => void }) => Promise<void>;
|
||||
generatePromptSuggestions: (activities: AgentRecentActivity[], model: string, signal?: AbortSignal) => Promise<AgentPromptSuggestion[]>;
|
||||
createSession: (options?: { triggerContext?: AgentEventTriggerContext }) => Promise<AgentChatSession>;
|
||||
createSession: (options?: { triggerContext?: AgentEventTriggerContext; topicId?: string }) => Promise<AgentChatSession>;
|
||||
listSessions: (options?: { purpose?: AgentSessionPurpose; taskId?: string }) => Promise<AgentChatSessionSummary[]>;
|
||||
listSessionPage: (options?: { q?: string; cursor?: string; limit?: number; purpose?: AgentSessionPurpose; topicId?: string; includeCollaboration?: boolean; signal?: AbortSignal }) => Promise<AgentSessionPage>;
|
||||
listTopics: () => Promise<AgentTopic[]>;
|
||||
createTopic: (name: string) => Promise<AgentTopic>;
|
||||
renameTopic: (id: string, name: string) => Promise<AgentTopic>;
|
||||
deleteTopic: (id: string) => Promise<void>;
|
||||
moveSessionToTopic: (sessionId: string, topicId: string | null) => Promise<void>;
|
||||
loadOlderMessages: (sessionId: string, before: string, signal?: AbortSignal) => Promise<AgentHistoryMessagePage>;
|
||||
createTaskCollaborationSession: (taskId: string) => Promise<AgentChatSession>;
|
||||
loadSession: (sessionId: string) => Promise<AgentLoadedChatSession | null>;
|
||||
loadSession: (sessionId: string, options?: { messageLimit?: number; signal?: AbortSignal }) => Promise<AgentLoadedChatSession | null>;
|
||||
streamSession: (
|
||||
sessionId: string,
|
||||
options: {
|
||||
signal?: AbortSignal;
|
||||
messageLimit?: number;
|
||||
onEvent: (event: AgentSessionStreamEvent) => void;
|
||||
}
|
||||
) => Promise<void>;
|
||||
@@ -218,6 +241,19 @@ export function createAgentApiClient(
|
||||
};
|
||||
|
||||
return {
|
||||
async downloadFile(path) {
|
||||
const match = /^\/api\/v1(\/session\/[^/?#]+\/files\/mcp-[a-f0-9-]{36}\.(?:zip|xlsx))$/.exec(path);
|
||||
if (!match) throw new Error("无效的会话文件地址");
|
||||
const response = await request(match[1], { redirect: "error" });
|
||||
if (!response.ok) throw new Error("文件不可用或当前账号无权下载");
|
||||
const blob = await response.blob();
|
||||
const url = URL.createObjectURL(blob);
|
||||
const anchor = document.createElement("a");
|
||||
anchor.href = url;
|
||||
anchor.download = path.split("/").at(-1) ?? "export.zip";
|
||||
anchor.click();
|
||||
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
||||
},
|
||||
async submitResponseInput(sessionId, text, delivery, id) {
|
||||
return requestJson<AgentResponseInput>("/responses", {
|
||||
method: "POST", headers: { "Content-Type": "application/json" },
|
||||
@@ -233,6 +269,11 @@ export function createAgentApiClient(
|
||||
method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ delivery })
|
||||
});
|
||||
},
|
||||
async cancelResponseInput(sessionId, id) {
|
||||
return requestJson<AgentResponseInput>(`/session/${encodeURIComponent(sessionId)}/inputs/${encodeURIComponent(id)}`, {
|
||||
method: "DELETE"
|
||||
});
|
||||
},
|
||||
async generatePromptSuggestions(activities, model, signal) {
|
||||
const result = await requestJson<unknown>("/suggestions", {
|
||||
method: "POST",
|
||||
@@ -260,7 +301,7 @@ export function createAgentApiClient(
|
||||
return requestJson<AgentChatSession>("/session", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ trigger_context: options?.triggerContext })
|
||||
body: JSON.stringify({ trigger_context: options?.triggerContext, topic_id: options?.topicId })
|
||||
});
|
||||
},
|
||||
|
||||
@@ -274,6 +315,31 @@ export function createAgentApiClient(
|
||||
return (payload.sessions ?? []).map(toSessionSummary).filter(isPresent).sort(compareSessionSummaries);
|
||||
},
|
||||
|
||||
async listSessionPage(options) {
|
||||
const params = new URLSearchParams({ purpose: options?.purpose ?? "ordinary_chat", limit: String(options?.limit ?? 30) });
|
||||
if (options?.q) params.set("q", options.q);
|
||||
if (options?.topicId) params.set("topic_id", options.topicId);
|
||||
if (options?.includeCollaboration) params.set("include_collaboration", "true");
|
||||
if (options?.cursor) params.set("cursor", options.cursor);
|
||||
const payload = await requestJson<{ sessions?: unknown[]; has_more?: boolean; next_cursor?: string | null }>(
|
||||
`/sessions?${params}`, { signal: options?.signal });
|
||||
return { sessions: (payload.sessions ?? []).map(toSessionSummary).filter(isPresent),
|
||||
hasMore: payload.has_more === true, nextCursor: typeof payload.next_cursor === "string" ? payload.next_cursor : null };
|
||||
},
|
||||
async listTopics() { return (await requestJson<{ topics: AgentTopic[] }>("/topics")).topics; },
|
||||
async createTopic(name) { return requestJson<AgentTopic>("/topics", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name }) }); },
|
||||
async renameTopic(id, name) { return requestJson<AgentTopic>(`/topics/${encodeURIComponent(id)}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name }) }); },
|
||||
async deleteTopic(id) { await requestJson(`/topics/${encodeURIComponent(id)}`, { method: "DELETE" }); },
|
||||
async moveSessionToTopic(sessionId, topicId) { await requestJson(`/session/${encodeURIComponent(sessionId)}/topic`, {
|
||||
method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ topic_id: topicId }),
|
||||
}); },
|
||||
async loadOlderMessages(sessionId, before, signal) {
|
||||
const query = new URLSearchParams({ limit: "40", before });
|
||||
const payload = await requestJson<{ messages?: unknown[]; message_page?: unknown }>(
|
||||
`/session/${encodeURIComponent(sessionId)}/messages?${query}`, { signal });
|
||||
return { messages: payload.messages ?? [], messagePage: readMessagePagination(payload.message_page) ?? { hasMore: false, beforeCursor: null } };
|
||||
},
|
||||
|
||||
async createTaskCollaborationSession(taskId) {
|
||||
return requestJson<AgentChatSession>(
|
||||
`/task/${encodeURIComponent(taskId)}/collaboration/session`,
|
||||
@@ -293,8 +359,9 @@ export function createAgentApiClient(
|
||||
});
|
||||
},
|
||||
|
||||
async loadSession(sessionId) {
|
||||
const response = await request(`/session/${encodeURIComponent(sessionId)}`);
|
||||
async loadSession(sessionId, options) {
|
||||
const query = options?.messageLimit ? `?message_limit=${options.messageLimit}` : "";
|
||||
const response = await request(`/session/${encodeURIComponent(sessionId)}${query}`, { signal: options?.signal });
|
||||
const text = await response.text();
|
||||
const data = text ? JSON.parse(text) : null;
|
||||
|
||||
@@ -310,7 +377,7 @@ export function createAgentApiClient(
|
||||
|
||||
async streamSession(sessionId, options) {
|
||||
const response = await request(
|
||||
`/session/${encodeURIComponent(sessionId)}/stream`,
|
||||
`/session/${encodeURIComponent(sessionId)}/stream${options.messageLimit ? `?message_limit=${options.messageLimit}` : ""}`,
|
||||
{ signal: options.signal }
|
||||
);
|
||||
|
||||
@@ -564,6 +631,9 @@ function toSessionSummary(value: unknown): AgentChatSessionSummary | null {
|
||||
return {
|
||||
id,
|
||||
title: normalizeTitle(value.title),
|
||||
topicId: typeof value.topic_id === "string" ? value.topic_id : null,
|
||||
topicName: typeof value.topic_name === "string" ? value.topic_name : undefined,
|
||||
searchSnippet: typeof value.search_snippet === "string" ? value.search_snippet : undefined,
|
||||
createdAt: toMillis(value.created_at),
|
||||
updatedAt: toMillis(value.updated_at),
|
||||
status: typeof value.status === "string" ? value.status : undefined,
|
||||
@@ -591,7 +661,8 @@ function toLoadedSession(value: unknown): AgentLoadedChatSession {
|
||||
sessionId: typeof value.session_id === "string" ? value.session_id : summary.id,
|
||||
isTitleManuallyEdited:
|
||||
typeof value.is_title_manually_edited === "boolean" ? value.is_title_manually_edited : false,
|
||||
messages: Array.isArray(value.messages) ? value.messages : []
|
||||
messages: Array.isArray(value.messages) ? value.messages : [],
|
||||
messagePage: readMessagePagination(value.message_page)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -657,6 +728,7 @@ function emitSessionSseEvent(
|
||||
onEvent({
|
||||
type: "state",
|
||||
executionPlans: Array.isArray(data.execution_plans) ? data.execution_plans : undefined,
|
||||
messagePage: readMessagePagination(data.message_page),
|
||||
sessionId,
|
||||
messages: Array.isArray(data.messages) ? data.messages : [],
|
||||
isStreaming: data.is_streaming === true,
|
||||
@@ -717,3 +789,8 @@ function compareSessionSummaries(left: AgentChatSessionSummary, right: AgentChat
|
||||
|
||||
return right.id.localeCompare(left.id);
|
||||
}
|
||||
|
||||
function readMessagePagination(value: unknown): AgentMessagePagination | undefined {
|
||||
if (!isRecord(value)) return undefined;
|
||||
return { hasMore: value.has_more === true, beforeCursor: typeof value.before_cursor === "string" ? value.before_cursor : null };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user