From 0e1a1bb5c0f02d1e2af1ee3800750561f4fed161 Mon Sep 17 00:00:00 2001 From: zh Date: Mon, 28 Sep 2026 16:01:04 +0800 Subject: [PATCH] =?UTF-8?q?feat(config):=20=E6=A8=A1=E5=9E=8B=E4=B8=8E?= =?UTF-8?q?=E7=AB=AF=E7=82=B9=E9=85=8D=E7=BD=AE=E5=B1=82=EF=BC=88ADR-0011?= =?UTF-8?q?=EF=BC=8C=E5=86=B3=E7=AD=96=20D15=20/=20D16=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit server/config.ts 按 ADR-0011 读取配置:环境变量 > ~/.aps/config.json > 默认值;默认 DeepSeek(https://api.deepseek.com/v1,deepseek-chat);密钥只在配置对象里出现一次,日志与启动横幅一律走 redactConfig。.env.example 改为平台自己的变量并写清优先级。serve 启动打印脱敏配置与「模型是否已配置」。数据库默认 ~/.aps/aps.sqlite(决策 D16,驱动用 Node 内置 node:sqlite,实测 22.19 免 flag、零原生依赖)。顺带收紧跨库访问检查的边界:平台自己的 APS_DB_PATH 不算越界(补一条用例锁住),避免误报。测试 47 → 54 全绿。 --- .env.example | 103 ++++++------------------- docs/adr/0011-model-endpoint.md | 42 ++++++++++ docs/adr/README.md | 1 + scripts/check-cross-database.mjs | 10 ++- scripts/check-cross-database.test.mjs | 5 ++ scripts/config.test.mjs | 84 ++++++++++++++++++++ server/bin/serve.ts | 11 +-- server/config.ts | 107 ++++++++++++++++++++++++++ server/index.ts | 6 +- server/package.json | 4 + server/tsconfig.json | 2 +- 11 files changed, 285 insertions(+), 90 deletions(-) create mode 100644 docs/adr/0011-model-endpoint.md create mode 100644 scripts/config.test.mjs create mode 100644 server/config.ts diff --git a/.env.example b/.env.example index 4acd2de..f4ffd00 100644 --- a/.env.example +++ b/.env.example @@ -1,86 +1,31 @@ # ============================================================ -# APS 智能体 · 环境配置模板(复制为 .env 后填写,.env 已被 gitignore) -# 密钥只存环境,绝不入库(plan.md §14.4) +# 多系统智能体平台 · 环境变量模板 +# 复制为 .env 或写进 ~/.aps/config.json;.env 已被 gitignore +# 优先级:环境变量 > 配置文件 > 默认值(ADR-0011) +# 密钥只进配置文件或环境变量,绝不入库、不进日志 # ============================================================ -# ---- 大模型 Provider(OpenAI 兼容;配置后开放问答/意图语义更强)---- -# 可选:deepseek | kimi | moonshot | doubao | openai | compatible -# 留空或密钥缺失时:快路 + 项目盘点离线作答(仍可分析数据,不甩「听不懂」) -LLM_PROVIDER= -# API 密钥 -LLM_API_KEY= -# 接口地址(可覆盖默认): -# deepseek → https://api.deepseek.com/v1 -# kimi → https://api.moonshot.cn/v1 -# doubao → https://ark.cn-beijing.volces.com/api/v3 (模型填接入点 ID) -# openai → https://api.openai.com/v1 -# 其它兼容网关 → 填完整 /v1 根路径,LLM_PROVIDER=compatible -LLM_BASE_URL= -# 模型名:deepseek-chat / moonshot-v1-8k / gpt-4o-mini / 豆包 endpoint id 等 -LLM_MODEL= +# ---- 模型与端点(ADR-0011;开发与首版用 DeepSeek)---- +# deepseek | openai | compatible +APS_LLM_PROVIDER=deepseek +APS_LLM_BASE_URL=https://api.deepseek.com/v1 +APS_LLM_MODEL=deepseek-chat +# 也写在 ~/.aps/config.json 的 llm.apiKey 里;这里留空表示未配置模型(平台仍可启动,走假引擎) +APS_LLM_API_KEY= -# ---- 运行形态(桌面 ~/.aps,Web 用仓库 data)---- -# desktop | web(默认 web) -APS_MODE=web -# 显式主目录(覆盖默认:desktop→%USERPROFILE%\.aps,web→server/data) -# APS_HOME= -# Skill 单文件兼容(测试用);正式为 APS_HOME/skills//manifest.json -# APS_SKILLS_PATH= -# Web 端演示数据懒播种:1=租户首次打开工作区且无项目时自动创建演示项目(镜像默认开) -# APS_WEB_DEMO_SEED=1 +# ---- 配置文件位置(默认 ~/.aps/config.json)---- +# APS_CONFIG_FILE= -# ---- 后端服务 ---- -APS_PORT=8000 -APS_WORLD_PATH=server/data/world.json +# ---- 服务端 ---- +APS_HOST=127.0.0.1 +APS_PORT=5199 +# 客户端静态产物目录(默认仓库内 client/app/dist) +# APS_CLIENT_DIST= -# ---- 用户认证与租户隔离(JMS) ---- -# jms:调用真实 JMS 登录接口;unconfigured:关闭 Web 认证和业务 API -# 统一登录开关:1=启用 Web 登录/桌面授权; 0=本地免登录管理员(仅限开发/演示) -APS_AUTH_ENABLED=1 -APS_AUTH_PROVIDER=jms -APS_AUTH_COOKIE_SECURE=0 -APS_AUTH_TTL_SECONDS=28800 -# 门禁审批队列与一次性执行凭据(file 默认兼容;database 显式启用共享 MySQL) -APS_APPROVAL_BACKEND=file -# APS_APPROVAL_PATH= -# database 后端必须显式配置 APS_DATABASE_URL,且不得同时配置 APS_APPROVAL_PATH;连接失败不回退 file -# SQLite 仅限测试:APS_APPROVAL_DATABASE_ALLOW_SQLITE=1(共享部署禁止使用) -APS_APPROVAL_TTL_SECONDS=86400 -APS_EXECUTION_GRANT_TTL_SECONDS=300 -APS_APPROVAL_INITIATOR_ROLES=planner,approver,admin -APS_APPROVAL_ROLES=planner,approver,admin -APS_APPROVAL_VIEW_ROLES=planner,approver,admin,auditor -JMS_AUTH_BASE_URL= -# 可选:固定为单一租户(部署时写死企业,用户仍可手填名称校验) -# 公开租户列表已取消;登录页手填企业名称直接提交 -JMS_AUTH_TENANT_CODE= -JMS_AUTH_TENANT_NAME= -# 至少 32 个随机字符,只用于签名本系统的 HttpOnly 会话,不发送给 JMS -JMS_AUTH_SESSION_SECRET= -# 可选:固定 JMS 登录响应中的 tokenName;配置后会严格校验 -# JMS_AUTH_TOKEN_NAME= -JMS_AUTH_TIMEOUT_SECONDS=10 -# 可选:企业自己的账号申请页面;未配置时提示联系企业管理员开通 -# JMS_AUTH_ACCOUNT_REQUEST_URL=https://example.com/account-request +# ---- 数据库(决策 D16:首版用 SQLite)---- +# 默认 ~/.aps/aps.sqlite;服务端部署时显式指定 +# APS_DB_PATH= -# ---- 桌面客户端授权 ---- -# Web 用户认证与桌面授权相互独立。正式环境接入授权中心前保持 unconfigured(默认拒绝)。 -# mock 仅用于本地桌面开发,内置六类可跨开发设备复用的 APS-*-DEMO 授权码。 -APS_LICENSE_PROVIDER=unconfigured -# APS_MOCK_LICENSE_CODES={"MY-HOUR-CODE":"hour","MY-PERM-CODE":"permanent"} -# mock 模式必须显式配置随机签名密钥和租户标识;npm run dev:desktop 会为本地开发自动生成。 -# APS_MOCK_LICENSE_SECRET= -# APS_MOCK_LICENSE_TENANT_UUID= - -# ---- 现场数据目录(可选;未配置时不绑定任何机器目录)---- -# KANGNI_DATA_DIR= -# KANGNI_ROUTE_XLSX= -# RUIYANG_DEMO_DIR= - -# ---- 数据库 ---- -# 未配置时继续使用 APS_DB_PATH 指定的 SQLite;接入 jms_mes 后填写 MySQL URL -# APS_DATABASE_URL=mysql+pymysql://user:password@127.0.0.1:3306/jms_mes?charset=utf8mb4 -APS_DB_PATH=server/data/master.db - -# 生产环境填写实际前端来源,逗号分隔 -APS_CORS_ORIGINS=http://localhost:5173,http://127.0.0.1:5173 +# ---- 开发期开关 ---- +# 1=注册假 Provider(用于联调与演示;默认不注册,能力清单为空) +# APS_DEV_PROVIDER=1 \ No newline at end of file diff --git a/docs/adr/0011-model-endpoint.md b/docs/adr/0011-model-endpoint.md new file mode 100644 index 0000000..c3f4110 --- /dev/null +++ b/docs/adr/0011-model-endpoint.md @@ -0,0 +1,42 @@ +# ADR-0011:模型与端点(DeepSeek + 本机配置文件) + +- **状态**:已接受 +- **日期**:2026-09-28 +- **决策人**:技术负责人 +- **相关**:任务表 A0(决策 D15);ADR-0006(引擎运行形态);ADR-0009(交付形态) + +## 背景 + +引擎要真跑起来就必须调模型。这件事有两个必须现在定的点:用哪个端点,以及密钥放在哪。密钥的处置还牵动一条硬规则——凭据不进模型上下文、不进日志、不进仓库(架构设计 5.1)。 + +## 选项 + +| 选项 | 做法 | 代价 | 不选的理由 | +| --- | --- | --- | --- | +| A DeepSeek + 本机配置文件 | 端点用 DeepSeek 的 OpenAI 兼容接口;密钥写在本机配置文件里 | 依赖外部服务可用性 | 选定 | +| B 内网自建网关 | 部署兼容网关,统一收口 | 需要运维与卡资源 | 首版没有现成网关;接口按 OpenAI 兼容写,将来换端点只改配置 | +| C 密钥进环境变量 | 全部走 env | CI 里方便 | 本地开发与桌面端每次都要设环境变量;env 还会被子进程继承,容易外泄 | + +## 结论 + +选 A,并定下配置的三条规则: + +| 项 | 结论 | +| --- | --- | +| 端点 | `https://api.deepseek.com/v1`(OpenAI 兼容),模型 `deepseek-chat` | +| 密钥位置 | 本机配置文件 `~/.aps/config.json` 的 `llm.apiKey`;也可用 `APS_CONFIG_FILE` 指定其它路径 | +| 优先级 | 环境变量 > 配置文件 > 默认值(方便 CI 与临时覆盖) | +| 泄露防线 | 密钥不进仓库、不进日志、不进 API 响应;配置对象提供脱敏视图供日志使用 | +| 不可用时 | 模型未配置不阻塞平台启动:默认走假引擎,需要真引擎时报明确错误(A3.3 落地) | + +## 影响 + +- **对代码**:新增 `server/config.ts`;引擎适配层从它取端点与密钥,不自己读环境 +- **对任务**:A3.3 子进程接入时把配置注入 Pi 子进程的启动环境;A10.8 发版清单要核对密钥不随包分发 +- **对文档**:`.env.example` 改为平台自己的变量;术语表无需改 +- **不这么做的后果**:密钥散在代码与 shell 历史里,且子进程继承全部环境变量,泄露面变大 + +## 遗留 + +- 生产是否改用内网网关:A10 之前定,届时只改 `llm.baseUrl` +- 是否支持多模型切换(同一个平台配置多个端点):首版不做,等有第二个使用场景再评估 \ No newline at end of file diff --git a/docs/adr/README.md b/docs/adr/README.md index 7de96fe..6e68800 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -48,5 +48,6 @@ ADR 写完不再修改。要改结论就**新写一份**,并在两份里互相 | [0008](./0008-database-ownership-and-topology.md) | 数据库归属与实例拓扑 | 已接受 | 任务表 A0(决策 D13) | | [0009](./0009-delivery-shape.md) | 交付形态:纯平台包 + APS 单独部署 | 已接受 | 任务表 A0(决策 D14) | | [0010](./0010-aps-dev-shape-in-repo.md) | APS 开发期与本仓库同仓不同目录 | 已接受 | 任务表 B0 | +| [0011](./0011-model-endpoint.md) | 模型与端点:DeepSeek + 本机配置文件 | 已接受 | 任务表 A0(决策 D15) | 编号在写之前先在本表登记,避免多人并行时撞号。 diff --git a/scripts/check-cross-database.mjs b/scripts/check-cross-database.mjs index 2d08a09..c3f43de 100644 --- a/scripts/check-cross-database.mjs +++ b/scripts/check-cross-database.mjs @@ -28,8 +28,12 @@ export const APS_TABLES = [ 'aps_schedule_results', ]; -/** 指向 APS 库的连接串特征 */ -export const APS_DATABASE_HINTS = [/aps[_-]?db/i, /database\s*=\s*aps/i, /jms_mes/i]; +/** + * 指向 APS 库的连接串特征。 + * 注意边界:平台自己的环境变量名(APS_DB_PATH、APS_DATABASE_URL)不算越界, + * 只有真正指向业务库的写法(jms_mes、aps_db 这种库名)才拦。 + */ +export const APS_DATABASE_HINTS = [/aps[_-]?db\b/, /\bjms_mes\b/]; const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.mts', '.cts', '.js', '.mjs', '.cjs']); const SKIP_DIRS = new Set(['node_modules', 'dist', '.git', 'coverage']); @@ -90,4 +94,4 @@ async function main() { if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { await main(); -} \ No newline at end of file +} diff --git a/scripts/check-cross-database.test.mjs b/scripts/check-cross-database.test.mjs index 8f77028..b7d0a11 100644 --- a/scripts/check-cross-database.test.mjs +++ b/scripts/check-cross-database.test.mjs @@ -38,6 +38,11 @@ test('负例:平台代码里出现 APS 表名被拦下', async () => { ); }); +test('正例:平台自己的 APS_DB_PATH 变量不算越界', async () => { + await withFixture({ 'server/config.ts': 'const path = process.env.APS_DB_PATH ?? "~/.aps/aps.sqlite";\nexport const p = path;\n' }, async (dir) => { + assert.deepEqual(await findCrossDatabaseAccess(dir), [], '平台自己的库配置不应被当成访问 APS 业务库'); + }); +}); test('负例:平台代码里出现 APS 库连接串被拦下', async () => { await withFixture( { 'server/app/config.ts': 'export const url = "postgres://user:pw@db/aps_db";\n' }, diff --git a/scripts/config.test.mjs b/scripts/config.test.mjs new file mode 100644 index 0000000..5a1237d --- /dev/null +++ b/scripts/config.test.mjs @@ -0,0 +1,84 @@ +/** 配置加载(ADR-0011 / 决策 D15、D16)的用例。 */ +import assert from 'node:assert/strict'; +import { mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { test } from 'node:test'; + +import { configFilePath, isEngineConfigured, loadConfig, redactConfig } from '@aps/server/config'; + +async function withConfigFile(content, run) { + const dir = await mkdtemp(join(tmpdir(), 'aps-config-')); + const file = join(dir, 'config.json'); + try { + if (content !== undefined) await writeFile(file, content, 'utf8'); + return await run({ dir, file }); + } finally { + await rm(dir, { recursive: true, force: true }); + } +} + +test('没有配置文件与环境变量时用默认值:DeepSeek,密钥为空,引擎不可用', async () => { + await withConfigFile(undefined, async ({ dir, file }) => { + const config = loadConfig({ env: { APS_CONFIG_FILE: file }, homeDir: dir }); + assert.equal(config.llm.provider, 'deepseek'); + assert.equal(config.llm.baseUrl, 'https://api.deepseek.com/v1'); + assert.equal(config.llm.model, 'deepseek-chat'); + assert.equal(config.llm.apiKey, ''); + assert.equal(isEngineConfigured(config), false, '没有密钥时引擎不应被视为可用'); + assert.equal(config.database.path, join(dir, '.aps', 'aps.sqlite'), '默认库在用户目录下'); + assert.equal(config.server.port, 5199); + }); +}); + +test('配置文件提供端点与密钥', async () => { + const content = JSON.stringify({ + llm: { provider: 'compatible', baseUrl: 'http://gw.internal/v1', model: 'internal-chat', apiKey: 'sk-file-123' }, + database: { path: 'D:/data/aps.sqlite' }, + server: { port: 6000 }, + }); + await withConfigFile(content, async ({ dir, file }) => { + const config = loadConfig({ env: { APS_CONFIG_FILE: file }, homeDir: dir }); + assert.equal(config.llm.provider, 'compatible'); + assert.equal(config.llm.baseUrl, 'http://gw.internal/v1'); + assert.equal(config.llm.model, 'internal-chat'); + assert.equal(config.llm.apiKey, 'sk-file-123'); + assert.equal(isEngineConfigured(config), true); + assert.equal(config.database.path, 'D:/data/aps.sqlite'); + assert.equal(config.server.port, 6000); + }); +}); + +test('环境变量优先于配置文件', async () => { + const content = JSON.stringify({ llm: { model: 'from-file', apiKey: 'sk-file' }, server: { port: 6000 } }); + await withConfigFile(content, async ({ dir, file }) => { + const config = loadConfig({ + env: { APS_CONFIG_FILE: file, APS_LLM_MODEL: 'from-env', APS_PORT: '7000' }, + homeDir: dir, + }); + assert.equal(config.llm.model, 'from-env'); + assert.equal(config.llm.apiKey, 'sk-file', '未覆盖的字段仍取配置文件'); + assert.equal(config.server.port, 7000); + }); +}); + +test('脱敏视图不含密钥明文', async () => { + const content = JSON.stringify({ llm: { apiKey: 'sk-secret-abcdef' } }); + await withConfigFile(content, async ({ dir, file }) => { + const config = loadConfig({ env: { APS_CONFIG_FILE: file }, homeDir: dir }); + const redacted = JSON.stringify(redactConfig(config)); + assert.ok(!redacted.includes('sk-secret-abcdef'), `脱敏后仍能看到密钥:${redacted}`); + assert.ok(redacted.includes('已配置'), redacted); + }); +}); + +test('配置文件损坏时报错,不静默降级', async () => { + await withConfigFile('{ 这不是 JSON', async ({ dir, file }) => { + assert.throws(() => loadConfig({ env: { APS_CONFIG_FILE: file }, homeDir: dir }), /配置文件解析失败/); + }); +}); + +test('配置文件路径:默认在用户目录,可用 APS_CONFIG_FILE 覆盖', () => { + assert.equal(configFilePath({ env: {}, homeDir: 'C:/Users/test' }).replace(/\\/g, '/'), 'C:/Users/test/.aps/config.json'); + assert.equal(configFilePath({ env: { APS_CONFIG_FILE: 'D:/cfg/aps.json' }, homeDir: 'C:/Users/test' }), 'D:/cfg/aps.json'); +}); \ No newline at end of file diff --git a/server/bin/serve.ts b/server/bin/serve.ts index fae0dbf..a147dba 100644 --- a/server/bin/serve.ts +++ b/server/bin/serve.ts @@ -1,16 +1,17 @@ /** * 本地启动平台服务:node server/dist/bin/serve.js(或 pnpm dev:server)。 - * A1.5 会把这条命令与界面、数据库串成一条一键启动。 + * 配置来源见 ADR-0011:环境变量 > ~/.aps/config.json > 默认值。 */ +import { loadConfig, isEngineConfigured, redactConfig } from '../config.js'; import { startServer } from '../index.js'; -const port = Number.parseInt(process.env.APS_PORT ?? '5199', 10); -const host = process.env.APS_HOST ?? '127.0.0.1'; - -const server = await startServer({ host, port }); +const config = loadConfig(); +const server = await startServer({ host: config.server.host, port: config.server.port }); console.log(`平台服务已启动:${server.url}(协议 v1)`); console.log(`健康检查:${server.url}/v1/health`); +console.log(`配置:${JSON.stringify(redactConfig(config))}`); +console.log(isEngineConfigured(config) ? '模型:已配置(真引擎在 A3.3 接入)' : '模型:未配置,当前走假引擎'); for (const signal of ['SIGINT', 'SIGTERM'] as const) { process.on(signal, () => { diff --git a/server/config.ts b/server/config.ts new file mode 100644 index 0000000..ba48c54 --- /dev/null +++ b/server/config.ts @@ -0,0 +1,107 @@ +/** + * 平台配置(ADR-0011)。 + * + * 读取顺序:环境变量 > 配置文件 > 默认值。 + * 配置文件默认在 ~/.aps/config.json(桌面端与本地开发的惯例位置),可用 APS_CONFIG_FILE 指向别处。 + * 密钥只在这里出现一次,其余模块拿到的都是配置对象;日志一律用 redactConfig()。 + */ +import { existsSync, readFileSync } from 'node:fs'; +import { homedir } from 'node:os'; +import { join } from 'node:path'; + +export type LlmProvider = 'deepseek' | 'openai' | 'compatible'; + +export interface LlmConfig { + provider: LlmProvider; + baseUrl: string; + model: string; + /** 未配置时为空字符串:引擎不可用,但平台照常启动 */ + apiKey: string; +} + +export interface PlatformConfig { + llm: LlmConfig; + database: { path: string }; + server: { host: string; port: number }; + /** 客户端构建产物目录,可覆盖(默认仓库内 client/app/dist) */ + clientDist?: string; +} + +export interface LoadConfigInput { + env?: NodeJS.ProcessEnv; + /** 便于测试:覆盖用户主目录 */ + homeDir?: string; +} + +const DEFAULT_BASE_URLS: Record = { + deepseek: 'https://api.deepseek.com/v1', + openai: 'https://api.openai.com/v1', +}; + +export function configFilePath(input: LoadConfigInput = {}): string { + const env = input.env ?? process.env; + const explicit = env.APS_CONFIG_FILE; + if (explicit && explicit.trim() !== '') return explicit; + return join(input.homeDir ?? homedir(), '.aps', 'config.json'); +} + +interface FileConfig { + llm?: Partial; + database?: { path?: string }; + server?: { host?: string; port?: number }; + clientDist?: string; +} + +function readFileConfig(path: string): FileConfig { + if (!existsSync(path)) return {}; + try { + return JSON.parse(readFileSync(path, 'utf8')) as FileConfig; + } catch (error) { + // 配置文件坏了要吵:静默忽略会让密钥问题变得难以排查 + throw new Error(`配置文件解析失败:${path}(${error instanceof Error ? error.message : '未知错误'})`, { cause: error }); + } +} + +export function loadConfig(input: LoadConfigInput = {}): PlatformConfig { + const env = input.env ?? process.env; + const file = readFileConfig(configFilePath(input)); + + const provider = (env.APS_LLM_PROVIDER ?? file.llm?.provider ?? 'deepseek') as LlmProvider; + const baseUrl = env.APS_LLM_BASE_URL ?? file.llm?.baseUrl ?? DEFAULT_BASE_URLS[provider] ?? ''; + const model = env.APS_LLM_MODEL ?? file.llm?.model ?? 'deepseek-chat'; + const apiKey = env.APS_LLM_API_KEY ?? file.llm?.apiKey ?? ''; + + const defaultDbPath = join(input.homeDir ?? homedir(), '.aps', 'aps.sqlite'); + const dbPath = env.APS_DB_PATH ?? file.database?.path ?? defaultDbPath; + + const port = Number.parseInt(env.APS_PORT ?? '', 10); + + return { + llm: { provider, baseUrl, model, apiKey }, + database: { path: dbPath }, + server: { + host: env.APS_HOST ?? file.server?.host ?? '127.0.0.1', + port: Number.isFinite(port) ? port : (file.server?.port ?? 5199), + }, + ...(env.APS_CLIENT_DIST ?? file.clientDist ?? '' ? { clientDist: env.APS_CLIENT_DIST ?? file.clientDist ?? '' } : {}), + }; +} + +/** 引擎是否可用:只有配了密钥才算可用 */ +export function isEngineConfigured(config: PlatformConfig): boolean { + return config.llm.apiKey.trim() !== ''; +} + +/** 脱敏视图:日志与启动横幅只能用它 */ +export function redactConfig(config: PlatformConfig): Record { + return { + llm: { + provider: config.llm.provider, + baseUrl: config.llm.baseUrl, + model: config.llm.model, + apiKey: config.llm.apiKey === '' ? '(未配置)' : `(已配置,长度 ${config.llm.apiKey.length})`, + }, + database: { path: config.database.path }, + server: { host: config.server.host, port: config.server.port }, + }; +} diff --git a/server/index.ts b/server/index.ts index 6bfbf40..0c9de0f 100644 --- a/server/index.ts +++ b/server/index.ts @@ -8,6 +8,7 @@ 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 { ApplicationService } from './app/service.js'; import { createHttpServer } from './entry/http.js'; @@ -33,8 +34,9 @@ export interface RunningServer { } export async function startServer(options: StartOptions = {}): Promise { - const host = options.host ?? '127.0.0.1'; - const port = options.port ?? 5199; + const config = loadConfig(); + const host = options.host ?? config.server.host; + const port = options.port ?? config.server.port; const service = new ApplicationService({ ...(options.version === undefined ? {} : { version: options.version }), ...(options.runtime === undefined ? {} : { runtime: options.runtime }), diff --git a/server/package.json b/server/package.json index e00c6c5..a6eb325 100644 --- a/server/package.json +++ b/server/package.json @@ -10,6 +10,10 @@ "types": "./dist/index.d.ts", "default": "./dist/index.js" }, + "./config": { + "types": "./dist/config.d.ts", + "default": "./dist/config.js" + }, "./agent/runtime": { "types": "./dist/agent/runtime.d.ts", "default": "./dist/agent/runtime.js" diff --git a/server/tsconfig.json b/server/tsconfig.json index b2d656f..9b89b34 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -6,7 +6,7 @@ "tsBuildInfoFile": "dist/.tsbuildinfo", "types": ["node"] }, - "include": ["index.ts", "bin", "entry", "app", "agent", "core", "workflows"], + "include": ["index.ts", "config.ts", "bin", "entry", "app", "agent", "core", "workflows"], "exclude": ["provider-sdk"], "references": [ { "path": "../contracts" },