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 useSuperAgentProfile from "../hooks/useSuperAgentProfile";
import useSuperAgentWorkspace from "../hooks/useSuperAgentWorkspace";
import {
cancelSuperAgentSession,
} from "../actions/api";
import { getCompatibleAuthUserInfo } from "../../../utils/authCompat";
import {
CHAT_WELCOME_CONTENT,
@ -1544,33 +1541,16 @@ const SuperAgent = () => {
};
const handleStopAndCancel = async () => {
const sessionId = activeSessionId || activeTaskId;
const stopOperations = [stream.stopRun()];
if (sessionId && currentUserId) {
stopOperations.push(
cancelSuperAgentSession({
sessionId,
userId: currentUserId,
})
);
}
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 || "停止后状态同步失败");
}
try {
// useStream 持有当前 thread_id/run_id;stop() 会取消该运行并中止 SSE。
await stream.stopRun();
await stream.refreshState();
} catch (error) {
superAgentError("container.stop", "failed", {
sessionId: activeSessionId,
error,
});
message.error(error?.message || "停止运行失败");
}
};

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

@ -254,8 +254,10 @@ class SingleSubscriberSseTransportAdapter extends ProtocolSseTransportAdapter {
async getState() {
const requestVersion = ++this.stateRequestVersion;
const requestedThreadId = String(this.threadId || "").trim();
const state = await super.getState();
// getState 可能在切换 thread 后才返回;旧请求不能覆盖新 thread 的快照。
// Agent 当前未提供 /threads/:threadId/state;useStream hydrate 只能使用
// 已从事件流获得的快照,不能为新会话触发一个必然失败的 HTTP 请求。
const state = this.latestState;
// 旧 state 快照不能在切换 thread 后覆盖新会话。
if (
requestVersion === this.stateRequestVersion &&
String(this.threadId || "").trim() === requestedThreadId
@ -379,13 +381,6 @@ BUSINESS_CHANNELS.forEach((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) =>
event?.data ??
event?.payload ??
@ -529,7 +524,7 @@ const useSuperAgentStream = ({
defaultHeaders,
callerOptions,
messagesKey: "messages",
optimistic: true,
optimistic: false,
transport: eventTransport,
});
@ -864,8 +859,7 @@ const useSuperAgentStream = ({
try {
await stream.submit({
messages: [{
type: "human",
id: createClientMessageId(),
role: "user",
content: String(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, /"custom"/);
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, /await stream\.stop\(\)/);
assert.match(streamHook, /disconnect:\s*stream\.disconnect/);
assert.match(streamHook, /eventRequestControllersRef/);
assert.match(streamHook, /isLifecycleWatcherRequest/);
@ -38,6 +44,7 @@ assert.match(streamHook, /runtimeInterrupts/);
assert.doesNotMatch(container, /useSuperAgentChat/);
assert.doesNotMatch(container, /respondSuperAgentCheckpoint/);
assert.doesNotMatch(container, /sendSuperAgentChat/);
assert.doesNotMatch(container, /cancelSuperAgentSession/);
const generatedDecisionStart = container.indexOf("const response = {");
const generatedDecisionEnd = container.indexOf("};", generatedDecisionStart);
const generatedDecision = container.slice(generatedDecisionStart, generatedDecisionEnd);
@ -63,5 +70,6 @@ assert.match(container, /syncSessionProgressSections/);
assert.match(conversation, /toolCalls=\{item\.toolCalls\}/);
assert.match(jsonRender, /toolCall\?\.name === "publish_ui"/);
assert.doesNotMatch(jsonRender, /```json-render|JSON_RENDER_FENCE/);
assert.match(apiRoutes, /'\/api\/chat'/);
assert.match(apiRoutes, /'\/api\/checkpoint\/respond'/);
assert.match(apiRoutes, /'\/super-agent\/threads\/:threadId\/commands'/);
assert.match(apiRoutes, /'\/super-agent\/threads\/:threadId\/stream\/events'/);
assert.match(apiRoutes, /'\/super-agent\/threads\/:threadId\/runs\/:runId\/cancel'/);

Loading…
Cancel
Save