aps-agent/server/index.ts

81 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 平台服务入口(架构设计第 2 节)。
*
* 组装:应用服务 + HTTP / SSE 入口。控制面(core)与引擎适配(agent)在后续阶段接入。
*/
import { randomUUID } from 'node:crypto';
import type { Server } from 'node:http';
import type { AddressInfo } from 'node:net';
import type { AgentRuntime } from './agent/runtime.js';
import { loadConfig } from './config.js';
import { openDatabase, type Database } from './core/storage/db.js';
import { SessionRepository } from './core/storage/sessions.js';
import { ApplicationService } from './app/service.js';
import { createHttpServer } from './entry/http.js';
export { ApplicationService } from './app/service.js';
export type { Subject } from './app/subject.js';
export { createHttpServer } from './entry/http.js';
export interface StartOptions {
host?: string;
port?: number;
version?: string;
/** 引擎实现:默认假引擎;A3.3 起可换成 Pi 子进程,测试也可注入脚本化假引擎 */
runtime?: AgentRuntime;
/** 数据库路径:默认取配置;测试传 ':memory:' 以隔离 */
databasePath?: string;
}
export interface RunningServer {
url: string;
port: number;
service: ApplicationService;
/** 引擎工具回调的内部令牌:由平台生成,启动引擎子进程时注入(A3.11) */
internalToken: string;
close(): Promise<void>;
}
export async function startServer(options: StartOptions = {}): Promise<RunningServer> {
const config = loadConfig();
const host = options.host ?? config.server.host;
const port = options.port ?? config.server.port;
const database: Database = openDatabase(options.databasePath ?? config.database.path);
const service = new ApplicationService({
...(options.version === undefined ? {} : { version: options.version }),
...(options.runtime === undefined ? {} : { runtime: options.runtime }),
sessionRepository: new SessionRepository(database),
});
// 开发期可选:APS_DEV_PROVIDER=1 时注册假 Provider,用于本地联调与演示(默认不注册,能力清单为空)
if (process.env.APS_DEV_PROVIDER === '1') {
const { createFakeProvider } = await import('@aps/provider-sdk/testing');
await service.registerProvider(createFakeProvider());
}
const internalToken = randomUUID();
const server: Server = createHttpServer(service, internalToken);
await new Promise<void>((resolve, reject) => {
server.once('error', reject);
server.listen(port, host, () => resolve());
});
const address = server.address() as AddressInfo | null;
const actualPort = address ? address.port : port;
return {
url: `http://${host}:${actualPort}`,
port: actualPort,
service,
internalToken,
close: () =>
new Promise<void>((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));
// 事件流是长连接,关停时必须主动断开,否则 close 会一直等它们
// 客户端断线后按 Last-Event-ID 重连,不会丢事件(协议文档第 5 节)
server.closeAllConnections();
database.close();
}),
};
}