Add ACP-backed role sessions
This commit is contained in:
+53
-61
@@ -1,73 +1,59 @@
|
||||
import express from "express";
|
||||
import type { Server } from "node:http";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { loadConfig, type AppConfig } from "./config.js";
|
||||
import { AgentRegistry } from "./agents/agent-registry.js";
|
||||
import { CliAgent } from "./agents/cli-agent.js";
|
||||
import { AcpBackendRegistry } from "./acp/backend-registry.js";
|
||||
import { AcpSessionManager } from "./acp/session-manager.js";
|
||||
import { defaultStateFile, loadConfig, type AppConfig } from "./config.js";
|
||||
import type { PlatformAdapter } from "./core/adapter.js";
|
||||
import { DurableSessionStore } from "./core/durable-session-store.js";
|
||||
import { Gateway } from "./core/gateway.js";
|
||||
import { PlatformRegistry } from "./core/platform-registry.js";
|
||||
import { SessionStore } from "./core/session-store.js";
|
||||
import type { PlatformAdapter } from "./core/adapter.js";
|
||||
import { FeishuAdapter } from "./platforms/feishu/adapter.js";
|
||||
import { WeComAdapter } from "./platforms/wecom/adapter.js";
|
||||
import { QqAdapter } from "./platforms/qq/adapter.js";
|
||||
import { QqGatewayClient } from "./platforms/qq/gateway-client.js";
|
||||
import { WeComAdapter } from "./platforms/wecom/adapter.js";
|
||||
import { GenericWebhookAdapter } from "./platforms/webhook/adapter.js";
|
||||
import { WeixinAdapter } from "./platforms/weixin/adapter.js";
|
||||
import { RoleRegistry } from "./roles/role-registry.js";
|
||||
|
||||
export interface GatewayRuntime {
|
||||
app: express.Express;
|
||||
gateway: Gateway;
|
||||
sessionManager: AcpSessionManager;
|
||||
store: DurableSessionStore;
|
||||
qqGatewayClient?: QqGatewayClient;
|
||||
shutdown(): Promise<void>;
|
||||
}
|
||||
|
||||
export function createGatewayRuntime(config: AppConfig): GatewayRuntime {
|
||||
const sessions = new SessionStore();
|
||||
const agents = new AgentRegistry(config.defaultAgent);
|
||||
for (const agentConfig of config.agents) {
|
||||
agents.register(new CliAgent(agentConfig));
|
||||
}
|
||||
|
||||
const gateway = new Gateway(config.policy, agents, sessions);
|
||||
export async function createGatewayRuntime(config: AppConfig): Promise<GatewayRuntime> {
|
||||
const store = new DurableSessionStore(defaultStateFile(config));
|
||||
await store.open();
|
||||
const roles = new RoleRegistry(config);
|
||||
const sessionManager = new AcpSessionManager(config.acp, new AcpBackendRegistry(config.backends), roles, store);
|
||||
const gateway = new Gateway(config.policy, sessionManager, roles);
|
||||
const platforms = new PlatformRegistry();
|
||||
const qqAdapter = new QqAdapter(config.platforms.qq, gateway);
|
||||
const adapters: PlatformAdapter[] = [
|
||||
new FeishuAdapter(config.platforms.feishu, gateway),
|
||||
new WeComAdapter(config.platforms.wecom),
|
||||
qqAdapter,
|
||||
new GenericWebhookAdapter(config.platforms.webhook, gateway),
|
||||
new WeixinAdapter(config.platforms.weixin, gateway)
|
||||
new FeishuAdapter(config.platforms.feishu, gateway), new WeComAdapter(config.platforms.wecom), qqAdapter,
|
||||
new GenericWebhookAdapter(config.platforms.webhook, gateway), new WeixinAdapter(config.platforms.weixin, gateway)
|
||||
];
|
||||
for (const adapter of adapters) platforms.register(adapter);
|
||||
|
||||
const app = express();
|
||||
app.use(express.json({
|
||||
limit: "1mb",
|
||||
verify: (req, _res, buf) => {
|
||||
(req as express.Request & { rawBody?: Buffer }).rawBody = Buffer.from(buf);
|
||||
}
|
||||
}));
|
||||
|
||||
app.get("/health", (_req, res) => {
|
||||
res.json({ ok: true, gateway: gateway.stats() });
|
||||
});
|
||||
|
||||
app.get("/platforms", (_req, res) => {
|
||||
res.json({ ok: true, platforms: platforms.list(), agents: agents.list() });
|
||||
});
|
||||
app.use(express.json({ limit: "1mb", verify: (req, _res, buf) => {
|
||||
(req as express.Request & { rawBody?: Buffer }).rawBody = Buffer.from(buf);
|
||||
} }));
|
||||
app.get("/health", (_req, res) => res.json({ ok: true, acp: gateway.stats() }));
|
||||
app.get("/platforms", (_req, res) => res.json({ ok: true, platforms: platforms.list(), roles: roles.list(), backends: config.backends.map(({ id }) => id) }));
|
||||
|
||||
function mountWebhook(routeName: string, adapterName = routeName): void {
|
||||
app.post(`/webhook/${routeName}`, async (req, res) => {
|
||||
try {
|
||||
const response = await platforms.get(adapterName).handleWebhook({
|
||||
req,
|
||||
body: req.body,
|
||||
headers: req.headers,
|
||||
query: req.query,
|
||||
req, body: req.body, headers: req.headers, query: req.query,
|
||||
rawBody: (req as express.Request & { rawBody?: Buffer }).rawBody
|
||||
});
|
||||
if (response.headers) {
|
||||
for (const [key, value] of Object.entries(response.headers)) res.setHeader(key, value);
|
||||
}
|
||||
if (response.headers) for (const [key, value] of Object.entries(response.headers)) res.setHeader(key, value);
|
||||
res.status(response.status || 200).json(response.body ?? { ok: true });
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
@@ -76,36 +62,42 @@ export function createGatewayRuntime(config: AppConfig): GatewayRuntime {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (const platformName of ["feishu", "wecom", "qq", "weixin"]) {
|
||||
mountWebhook(platformName);
|
||||
}
|
||||
for (const name of ["feishu", "wecom", "qq", "weixin"]) mountWebhook(name);
|
||||
mountWebhook("generic", "webhook");
|
||||
|
||||
const qqGatewayClient = config.platforms.qq.enabled && config.platforms.qq.connectionMode === "websocket"
|
||||
? new QqGatewayClient(config.platforms.qq, qqAdapter)
|
||||
: undefined;
|
||||
|
||||
return { app, qqGatewayClient };
|
||||
? new QqGatewayClient(config.platforms.qq, qqAdapter) : undefined;
|
||||
let closing: Promise<void> | undefined;
|
||||
const runtime: GatewayRuntime = {
|
||||
app, gateway, sessionManager, store, qqGatewayClient,
|
||||
shutdown: () => closing ||= (async () => {
|
||||
qqGatewayClient?.stop();
|
||||
await sessionManager.shutdown();
|
||||
await store.close();
|
||||
})()
|
||||
};
|
||||
return runtime;
|
||||
}
|
||||
|
||||
export function createApp(config: AppConfig): express.Express {
|
||||
return createGatewayRuntime(config).app;
|
||||
}
|
||||
export async function createApp(config: AppConfig): Promise<express.Express> { return (await createGatewayRuntime(config)).app; }
|
||||
|
||||
export function startServer(config: AppConfig): Server {
|
||||
const runtime = createGatewayRuntime(config);
|
||||
export interface RunningServer { server: Server; runtime: GatewayRuntime; shutdown(): Promise<void> }
|
||||
|
||||
export async function startServer(config: AppConfig): Promise<RunningServer> {
|
||||
const runtime = await createGatewayRuntime(config);
|
||||
const server = runtime.app.listen(config.server.port, config.server.host, () => {
|
||||
console.log(`gori-agent-gateway listening on ${config.server.host}:${config.server.port}`);
|
||||
if (runtime.qqGatewayClient) {
|
||||
console.log("QQ websocket gateway enabled; connecting to QQ...");
|
||||
runtime.qqGatewayClient.start();
|
||||
}
|
||||
console.log(`gori-agent listening on ${config.server.host}:${config.server.port}`);
|
||||
if (runtime.qqGatewayClient) { console.log("QQ websocket gateway enabled; connecting to QQ..."); runtime.qqGatewayClient.start(); }
|
||||
});
|
||||
server.on("close", () => runtime.qqGatewayClient?.stop());
|
||||
return server;
|
||||
let closing: Promise<void> | undefined;
|
||||
return { server, runtime, shutdown: () => closing ||= (async () => {
|
||||
runtime.qqGatewayClient?.stop();
|
||||
const runtimeShutdown = runtime.shutdown();
|
||||
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
|
||||
await runtimeShutdown;
|
||||
})() };
|
||||
}
|
||||
|
||||
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
||||
startServer(loadConfig());
|
||||
void startServer(loadConfig()).catch((error) => { console.error(error); process.exitCode = 1; });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user