Browse Source

feat: 对话交互适配

master
Senor-Liu 1 month ago
parent
commit
92ac9c48d6
  1. 40
      web/client/src/sections/superAgent/container/index.jsx
  2. 18
      web/client/src/sections/superAgent/hooks/useSuperAgentStream.js
  3. 12
      web/tests/superAgentFlowContract.test.mjs

40
web/client/src/sections/superAgent/container/index.jsx

@ -32,9 +32,6 @@ import useSuperAgentSession from "../hooks/useSuperAgentSession";
import useSuperAgentStream from "../hooks/useSuperAgentStream"; import useSuperAgentStream from "../hooks/useSuperAgentStream";
import useSuperAgentProfile from "../hooks/useSuperAgentProfile"; import useSuperAgentProfile from "../hooks/useSuperAgentProfile";
import useSuperAgentWorkspace from "../hooks/useSuperAgentWorkspace"; import useSuperAgentWorkspace from "../hooks/useSuperAgentWorkspace";
import {
cancelSuperAgentSession,
} from "../actions/api";
import { getCompatibleAuthUserInfo } from "../../../utils/authCompat"; import { getCompatibleAuthUserInfo } from "../../../utils/authCompat";
import { import {
CHAT_WELCOME_CONTENT, CHAT_WELCOME_CONTENT,
@ -1544,33 +1541,16 @@ const SuperAgent = () => {
}; };
const handleStopAndCancel = async () => { const handleStopAndCancel = async () => {
const sessionId = activeSessionId || activeTaskId; try {
const stopOperations = [stream.stopRun()]; // useStream 持有当前 thread_id/run_id;stop() 会取消该运行并中止 SSE。
if (sessionId && currentUserId) { await stream.stopRun();
stopOperations.push( await stream.refreshState();
cancelSuperAgentSession({ } catch (error) {
sessionId, superAgentError("container.stop", "failed", {
userId: currentUserId, sessionId: activeSessionId,
}) error,
); });
} message.error(error?.message || "停止运行失败");
const results = await Promise.allSettled(stopOperations);
const failedOperation = results.find((result) => result.status === "rejected");
if (failedOperation) {
message.error(failedOperation.reason?.message || "停止运行失败");
return;
}
if (sessionId) {
try {
await stream.refreshState();
} catch (error) {
superAgentError("container.stop", "state.refresh_failed", {
sessionId,
error,
});
message.error(error?.message || "停止后状态同步失败");
}
} }
}; };

18
web/client/src/sections/superAgent/hooks/useSuperAgentStream.js

@ -254,8 +254,10 @@ class SingleSubscriberSseTransportAdapter extends ProtocolSseTransportAdapter {
async getState() { async getState() {
const requestVersion = ++this.stateRequestVersion; const requestVersion = ++this.stateRequestVersion;
const requestedThreadId = String(this.threadId || "").trim(); const requestedThreadId = String(this.threadId || "").trim();
const state = await super.getState(); // Agent 当前未提供 /threads/:threadId/state;useStream hydrate 只能使用
// getState 可能在切换 thread 后才返回;旧请求不能覆盖新 thread 的快照。 // 已从事件流获得的快照,不能为新会话触发一个必然失败的 HTTP 请求。
const state = this.latestState;
// 旧 state 快照不能在切换 thread 后覆盖新会话。
if ( if (
requestVersion === this.stateRequestVersion && requestVersion === this.stateRequestVersion &&
String(this.threadId || "").trim() === requestedThreadId String(this.threadId || "").trim() === requestedThreadId
@ -379,13 +381,6 @@ BUSINESS_CHANNELS.forEach((channel) => {
if (!ROOT_PUMP_CHANNELS.includes(channel)) ROOT_PUMP_CHANNELS.push(channel); if (!ROOT_PUMP_CHANNELS.includes(channel)) ROOT_PUMP_CHANNELS.push(channel);
}); });
const createClientMessageId = () => {
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
return crypto.randomUUID();
}
return `client-${Date.now()}-${Math.random().toString(16).slice(2)}`;
};
const getEventPayload = (event) => const getEventPayload = (event) =>
event?.data ?? event?.data ??
event?.payload ?? event?.payload ??
@ -529,7 +524,7 @@ const useSuperAgentStream = ({
defaultHeaders, defaultHeaders,
callerOptions, callerOptions,
messagesKey: "messages", messagesKey: "messages",
optimistic: true, optimistic: false,
transport: eventTransport, transport: eventTransport,
}); });
@ -864,8 +859,7 @@ const useSuperAgentStream = ({
try { try {
await stream.submit({ await stream.submit({
messages: [{ messages: [{
type: "human", role: "user",
id: createClientMessageId(),
content: String(content || ""), content: String(content || ""),
}], }],
display_content: String(displayContent ?? content ?? ""), display_content: String(displayContent ?? content ?? ""),

12
web/tests/superAgentFlowContract.test.mjs

@ -27,7 +27,13 @@ assert.doesNotMatch(streamHook, /onCustomEvent/);
assert.match(streamHook, /ROOT_PUMP_CHANNELS/); assert.match(streamHook, /ROOT_PUMP_CHANNELS/);
assert.match(streamHook, /"custom"/); assert.match(streamHook, /"custom"/);
assert.match(streamHook, /attachment_ids:/); assert.match(streamHook, /attachment_ids:/);
assert.match(streamHook, /stream\.submit\(/);
assert.match(streamHook, /stream\.respond\(/);
assert.match(streamHook, /role:\s*"user"/);
assert.doesNotMatch(streamHook, /type:\s*"human"/);
assert.doesNotMatch(streamHook, /super\.getState\(\)/);
assert.match(streamHook, /const stopRun =/); assert.match(streamHook, /const stopRun =/);
assert.match(streamHook, /await stream\.stop\(\)/);
assert.match(streamHook, /disconnect:\s*stream\.disconnect/); assert.match(streamHook, /disconnect:\s*stream\.disconnect/);
assert.match(streamHook, /eventRequestControllersRef/); assert.match(streamHook, /eventRequestControllersRef/);
assert.match(streamHook, /isLifecycleWatcherRequest/); assert.match(streamHook, /isLifecycleWatcherRequest/);
@ -38,6 +44,7 @@ assert.match(streamHook, /runtimeInterrupts/);
assert.doesNotMatch(container, /useSuperAgentChat/); assert.doesNotMatch(container, /useSuperAgentChat/);
assert.doesNotMatch(container, /respondSuperAgentCheckpoint/); assert.doesNotMatch(container, /respondSuperAgentCheckpoint/);
assert.doesNotMatch(container, /sendSuperAgentChat/); assert.doesNotMatch(container, /sendSuperAgentChat/);
assert.doesNotMatch(container, /cancelSuperAgentSession/);
const generatedDecisionStart = container.indexOf("const response = {"); const generatedDecisionStart = container.indexOf("const response = {");
const generatedDecisionEnd = container.indexOf("};", generatedDecisionStart); const generatedDecisionEnd = container.indexOf("};", generatedDecisionStart);
const generatedDecision = container.slice(generatedDecisionStart, generatedDecisionEnd); const generatedDecision = container.slice(generatedDecisionStart, generatedDecisionEnd);
@ -63,5 +70,6 @@ assert.match(container, /syncSessionProgressSections/);
assert.match(conversation, /toolCalls=\{item\.toolCalls\}/); assert.match(conversation, /toolCalls=\{item\.toolCalls\}/);
assert.match(jsonRender, /toolCall\?\.name === "publish_ui"/); assert.match(jsonRender, /toolCall\?\.name === "publish_ui"/);
assert.doesNotMatch(jsonRender, /```json-render|JSON_RENDER_FENCE/); assert.doesNotMatch(jsonRender, /```json-render|JSON_RENDER_FENCE/);
assert.match(apiRoutes, /'\/api\/chat'/); assert.match(apiRoutes, /'\/super-agent\/threads\/:threadId\/commands'/);
assert.match(apiRoutes, /'\/api\/checkpoint\/respond'/); assert.match(apiRoutes, /'\/super-agent\/threads\/:threadId\/stream\/events'/);
assert.match(apiRoutes, /'\/super-agent\/threads\/:threadId\/runs\/:runId\/cancel'/);

Loading…
Cancel
Save