feat: 完善 Agent 标准交互与安全运行时
- 接入 AG-UI 运行投影、Turn 时间线和审批隔离 - 增加 Agent Skill 冻结绑定与运行时消费闭环 - 增加受控工作区、内置工具和私有 Artifact 生命周期
This commit is contained in:
127
easyflow-ui-admin/app/src/views/ai/shared/agent-agui/client.ts
Normal file
127
easyflow-ui-admin/app/src/views/ai/shared/agent-agui/client.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import type {
|
||||
AgentSubscriber,
|
||||
Message,
|
||||
RunAgentInput,
|
||||
State,
|
||||
} from '@ag-ui/client';
|
||||
|
||||
import { EventSchemas, EventType, HttpAgent, randomUUID } from '@ag-ui/client';
|
||||
|
||||
import { createEventStreamHeaders, resolveApiUrl } from '#/api/request';
|
||||
|
||||
export interface EasyFlowAguiRunOptions {
|
||||
forwardedProps?: Record<string, unknown>;
|
||||
onEvent: (event: AguiEvent) => void;
|
||||
onMessagesChanged?: (messages: ReadonlyArray<Readonly<Message>>) => void;
|
||||
onStateChanged?: (state: Readonly<State>) => void;
|
||||
threadId: string;
|
||||
url: string;
|
||||
userMessage: Message;
|
||||
}
|
||||
|
||||
export type AguiEvent = ReturnType<(typeof EventSchemas)['parse']>;
|
||||
|
||||
interface ActiveAguiRun {
|
||||
aborted: boolean;
|
||||
agent: HttpAgent;
|
||||
}
|
||||
|
||||
function toTransportJson<T>(value: T): T {
|
||||
// AG-UI SDK 会在请求前 structuredClone;先以真实传输格式解除 Vue Proxy,避免草稿对象克隆失败。
|
||||
// eslint-disable-next-line unicorn/prefer-structured-clone -- structuredClone 无法复制 Proxy
|
||||
return JSON.parse(JSON.stringify(value)) as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* EasyFlow 的无头 AG-UI 运行客户端。
|
||||
*
|
||||
* <p>SDK 持有标准 messages/state;页面只消费投影回调。每次运行都会在出站边界再次裁剪
|
||||
* tools、context、state 和历史消息,服务端仍会独立执行同样的安全校验。</p>
|
||||
*/
|
||||
export class EasyFlowAguiClient {
|
||||
private activeRun?: ActiveAguiRun;
|
||||
|
||||
abort() {
|
||||
if (!this.activeRun) return;
|
||||
this.activeRun.aborted = true;
|
||||
this.activeRun.agent.abortRun();
|
||||
this.activeRun = undefined;
|
||||
}
|
||||
|
||||
async run(options: EasyFlowAguiRunOptions) {
|
||||
this.abort();
|
||||
const requestUrl = options.url;
|
||||
const agent = new EasyFlowHttpAgent({
|
||||
headers: createEventStreamHeaders(requestUrl),
|
||||
initialMessages: [options.userMessage],
|
||||
threadId: options.threadId,
|
||||
url: resolveApiUrl(requestUrl),
|
||||
});
|
||||
const activeRun: ActiveAguiRun = { aborted: false, agent };
|
||||
this.activeRun = activeRun;
|
||||
let terminalReceived = false;
|
||||
let cancelledReceived = false;
|
||||
const subscriber: AgentSubscriber = {
|
||||
onEvent: ({ event }) => {
|
||||
if (
|
||||
event.type === EventType.RUN_FINISHED ||
|
||||
event.type === EventType.RUN_ERROR
|
||||
) {
|
||||
terminalReceived = true;
|
||||
}
|
||||
if (
|
||||
event.type === EventType.RUN_ERROR &&
|
||||
event.code === 'RUN_CANCELLED'
|
||||
) {
|
||||
cancelledReceived = true;
|
||||
}
|
||||
options.onEvent(event as AguiEvent);
|
||||
},
|
||||
onMessagesChanged: ({ messages }) => {
|
||||
options.onMessagesChanged?.(messages);
|
||||
},
|
||||
onStateChanged: ({ state }) => {
|
||||
options.onStateChanged?.(state);
|
||||
},
|
||||
};
|
||||
try {
|
||||
await agent.runAgent(
|
||||
{
|
||||
context: [],
|
||||
forwardedProps: options.forwardedProps
|
||||
? toTransportJson(options.forwardedProps)
|
||||
: undefined,
|
||||
runId: `run_${randomUUID()}`,
|
||||
tools: [],
|
||||
},
|
||||
subscriber,
|
||||
);
|
||||
if (!terminalReceived) {
|
||||
if (activeRun.aborted) return;
|
||||
throw new Error('Agent 事件流缺少终态,请重试');
|
||||
}
|
||||
} catch (error) {
|
||||
if (activeRun.aborted || cancelledReceived) return;
|
||||
throw error;
|
||||
} finally {
|
||||
if (this.activeRun === activeRun) {
|
||||
this.activeRun = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EasyFlowHttpAgent extends HttpAgent {
|
||||
protected override requestInit(input: RunAgentInput): RequestInit {
|
||||
const latestUserMessage = [...input.messages]
|
||||
.reverse()
|
||||
.find((message) => message.role === 'user');
|
||||
return super.requestInit({
|
||||
...input,
|
||||
context: [],
|
||||
messages: latestUserMessage ? [latestUserMessage] : [],
|
||||
state: {},
|
||||
tools: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user