feat(drainage): execute frontend actions

This commit is contained in:
2026-07-13 17:26:03 +08:00
parent c2e1c937ae
commit 0b7e827024
8 changed files with 171 additions and 8 deletions
+18
View File
@@ -54,6 +54,8 @@ export type AgentSessionStreamDataEventType =
| "question_request"
| "question_response"
| "tool_call"
| "frontend_action"
| "frontend_action_result"
| "ui_envelope"
| "session_title"
| "done"
@@ -93,6 +95,8 @@ export type AgentApiClient = {
deleteSession: (sessionId: string) => Promise<void>;
getModels: () => Promise<AgentModelsResponse>;
getUiRegistry: () => Promise<unknown>;
getFrontendActionRegistry: () => Promise<unknown>;
submitFrontendActionResult: (sessionId: string, actionId: string, result: unknown) => Promise<void>;
resolveRenderRef: (renderRef: string, sessionId: string) => Promise<unknown>;
replyPermission: (
requestId: string,
@@ -139,6 +143,18 @@ export function createAgentApiClient(baseUrls: string | string[] = AGENT_API_BAS
return (payload.sessions ?? []).map(toSessionSummary).filter(isPresent).sort(compareSessionSummaries);
},
async getFrontendActionRegistry() {
return requestJsonWithFallback<unknown>(candidates, activeBaseUrl, (nextBaseUrl) => { activeBaseUrl = nextBaseUrl; }, "/frontend-action-registry");
},
async submitFrontendActionResult(sessionId, actionId, result) {
await requestJsonWithFallback<unknown>(candidates, activeBaseUrl, (nextBaseUrl) => { activeBaseUrl = nextBaseUrl; }, `/frontend-actions/${encodeURIComponent(actionId)}/result`, {
method: "POST",
headers: { "Content-Type": "application/json", "x-agent-session-id": sessionId },
body: JSON.stringify(result)
});
},
async loadSession(sessionId) {
const response = await fetchWithFallback(candidates, activeBaseUrl, (nextBaseUrl) => {
activeBaseUrl = nextBaseUrl;
@@ -540,6 +556,8 @@ function isSessionStreamDataEventType(value: string): value is AgentSessionStrea
value === "question_request" ||
value === "question_response" ||
value === "tool_call" ||
value === "frontend_action" ||
value === "frontend_action_result" ||
value === "ui_envelope" ||
value === "session_title" ||
value === "done" ||
+5 -1
View File
@@ -4,24 +4,28 @@ import type {
AgentModelOption
} from "./client";
import { parseUiRegistry, type UIRegistry } from "../ui-envelope";
import { parseFrontendActionRegistry, type FrontendActionRegistry } from "../frontend-action";
export const agentBootstrapKey = ["agent", "bootstrap"] as const;
export const agentSessionsKey = ["agent", "sessions"] as const;
export type AgentBootstrapData = {
registry: UIRegistry | null;
frontendActionRegistry?: FrontendActionRegistry | null;
models: AgentModelOption[];
defaultModel: string;
};
export async function fetchAgentBootstrap(client: AgentApiClient): Promise<AgentBootstrapData> {
const [rawRegistry, modelsResponse] = await Promise.all([
const [rawRegistry, rawFrontendActionRegistry, modelsResponse] = await Promise.all([
client.getUiRegistry(),
typeof client.getFrontendActionRegistry === "function" ? client.getFrontendActionRegistry() : Promise.resolve(undefined),
client.getModels()
]);
return {
registry: parseUiRegistry(rawRegistry),
...(rawFrontendActionRegistry === undefined ? {} : { frontendActionRegistry: parseFrontendActionRegistry(rawFrontendActionRegistry) }),
models: modelsResponse.models,
defaultModel: modelsResponse.defaultModel
};