171 lines
7.5 KiB
TypeScript
171 lines
7.5 KiB
TypeScript
import dotenv from "dotenv";
|
|
import { z } from "zod";
|
|
|
|
import {
|
|
defaultAgentModelOptionsJson,
|
|
getAgentModelIds,
|
|
parseAgentModelOptions,
|
|
} from "./chat/modelConfig.js";
|
|
|
|
export const RESULT_REF_IMPORT_DIRECTORY = "./data/conversation-workspaces";
|
|
|
|
// 本地开发可在项目根目录放 .local.env;已存在的系统环境变量优先级更高。
|
|
dotenv.config({ path: ".local.env", override: false });
|
|
|
|
const optionalString = () =>
|
|
z.preprocess(
|
|
(value) => {
|
|
if (typeof value !== "string") {
|
|
return value;
|
|
}
|
|
const normalized = value.trim();
|
|
return normalized.length > 0 ? normalized : undefined;
|
|
},
|
|
z.string().optional(),
|
|
);
|
|
|
|
// 统一在启动时解析环境变量,避免业务代码里散落字符串默认值。
|
|
const envSchema = z
|
|
.object({
|
|
// 运行环境标识,如 development / production。
|
|
NODE_ENV: z.string().default("development"),
|
|
// HTTP 服务监听端口。
|
|
PORT: z.coerce.number().int().positive().default(8787),
|
|
// HTTP 服务监听地址。
|
|
HOST: z.string().default("0.0.0.0"),
|
|
// Pino 日志级别。
|
|
LOG_LEVEL: z.string().default("info"),
|
|
// LLM 工具/技能调用审计日志路径。
|
|
LLM_REQUEST_AUDIT_LOG_PATH: z
|
|
.string()
|
|
.default("./logs/llm-request-audit.log"),
|
|
// 内部工具桥调用本服务时使用的鉴权 token;未显式配置时启动阶段会自动生成。
|
|
AGENT_INTERNAL_TOKEN: optionalString(),
|
|
// Agent 前置认证调用后端 /api/v1/agent/auth/context 的超时时间(毫秒)。
|
|
AGENT_AUTH_TIMEOUT_MS: z.coerce.number().int().positive().default(5000),
|
|
// 当前仅支持 embedded;保留字段用于让旧 client 配置在启动时明确失败。
|
|
OPENCODE_MODE: z.literal("embedded").default("embedded"),
|
|
// embedded opencode server 的监听地址。
|
|
OPENCODE_HOSTNAME: z.string().default("127.0.0.1"),
|
|
// embedded opencode server 的监听端口。
|
|
OPENCODE_PORT: z.coerce.number().int().positive().default(4096),
|
|
// opencode SDK 启动或连接运行时时的超时时间(毫秒)。
|
|
OPENCODE_TIMEOUT_MS: z.coerce.number().int().positive().default(5000),
|
|
// 默认使用的 opencode 模型标识。
|
|
OPENCODE_MODEL: z.string().default("deepseek/deepseek-v4-flash"),
|
|
// 聊天 UI 和 /stream 允许选择的 opencode 模型完整配置,JSON 数组。
|
|
OPENCODE_MODEL_OPTIONS: z.string().default(defaultAgentModelOptionsJson),
|
|
// opencode skills 树目录;会在运行时解析为绝对路径,避免工具 cwd 偏移。
|
|
OPENCODE_SKILLS_ROOT_DIR: z.string().default("./.opencode/skills"),
|
|
// tjwater-cli 可执行文件路径。
|
|
TJWATER_CLI_PATH: z.string().default("./cli/tjwater-cli"),
|
|
// TJWater 后端 API 的基础地址。
|
|
TJWATER_API_BASE_URL: z.string().default("http://127.0.0.1:8000"),
|
|
// 代理调用 TJWater 后端 API 的超时时间(毫秒)。
|
|
TJWATER_API_TIMEOUT_MS: z.coerce.number().int().positive().default(30000),
|
|
// OpenCode 工具结果以内联形式返回给模型的阈值;更大的结果由 OpenCode 落盘。
|
|
MAX_INLINE_RESULT_BYTES: z.coerce.number().int().positive().default(12000),
|
|
// 单次 tjwater-cli stdout 的硬上限;超过后终止子进程。
|
|
MAX_CLI_OUTPUT_BYTES: z.coerce
|
|
.number()
|
|
.int()
|
|
.positive()
|
|
.default(128 * 1024 * 1024),
|
|
// 单次 tjwater-cli stderr 最多保留的字节数;超过后截断但不终止进程。
|
|
MAX_CLI_STDERR_BYTES: z.coerce
|
|
.number()
|
|
.int()
|
|
.positive()
|
|
.default(256 * 1024),
|
|
// 生成结果 preview 时最多抽样的条目数。
|
|
MAX_PREVIEW_SAMPLE_ITEMS: z.coerce.number().int().positive().default(3),
|
|
// memory 持久化存储目录。
|
|
MEMORY_STORAGE_DIR: z.string().default("./data/memory"),
|
|
// 持久化文件写入前保留备份版本的目录。
|
|
PERSISTENCE_BACKUP_DIR: z.string().default("./data/backup"),
|
|
// 注入到 prompt 的 memory 快照最大字符数,避免上下文过大。
|
|
MEMORY_MAX_PROMPT_CHARS: z.coerce.number().int().positive().default(1800),
|
|
// session transcript 持久化目录。
|
|
SESSION_TRANSCRIPT_STORAGE_DIR: z.string().default("./data/session-transcripts"),
|
|
// session metadata 持久化目录。
|
|
SESSION_METADATA_STORAGE_DIR: z.string().default("./data/session-metadata"),
|
|
// session UI state 持久化目录。
|
|
SESSION_UI_STATE_STORAGE_DIR: z.string().default("./data/session-ui-states"),
|
|
// 每个会话最多保留多少轮 transcript,超过后裁剪旧记录。
|
|
SESSION_TRANSCRIPT_MAX_TURNS_PER_SESSION: z.coerce
|
|
.number()
|
|
.int()
|
|
.positive()
|
|
.default(120),
|
|
// session_search 工具默认返回的最大命中数。
|
|
SESSION_SEARCH_MAX_RESULTS: z.coerce.number().int().positive().default(8),
|
|
// session_search 查询文本最大长度。
|
|
SESSION_SEARCH_MAX_QUERY_CHARS: z.coerce.number().int().positive().default(240),
|
|
// 当前 session 的 learning 进度状态目录。
|
|
SESSION_LEARNING_STATE_STORAGE_DIR: z.string().default("./data/session-learning-state"),
|
|
// learning audit 日志路径。
|
|
LEARNING_AUDIT_LOG_PATH: z
|
|
.string()
|
|
.default("./logs/learning-audit.log"),
|
|
// learning gate 的最小 turn 冷却间隔;这是运行时节流,不参与内容判断。
|
|
LEARNING_GATE_TURN_COOLDOWN: z.coerce.number().int().positive().default(2),
|
|
// gate 结果被提升为 review 前的最低置信度。
|
|
LEARNING_GATE_MIN_CONFIDENCE: z.coerce.number().min(0).max(1).default(0.65),
|
|
// review prompt 最多携带多少轮最近 transcript。
|
|
LEARNING_REVIEW_MAX_RECENT_TURNS: z.coerce.number().int().positive().default(8),
|
|
// review proposal 的最低置信度阈值。
|
|
LEARNING_MIN_PROPOSAL_CONFIDENCE: z.coerce.number().min(0).max(1).default(0.8),
|
|
// result_ref 持久化存储目录。
|
|
RESULT_REF_STORAGE_DIR: z.string().default("./data/result-refs"),
|
|
// 仅允许 store_render_ref 从该目录导入受控 JSON 包装文件。
|
|
RESULT_REF_IMPORT_DIR: z
|
|
.literal(RESULT_REF_IMPORT_DIRECTORY)
|
|
.default(RESULT_REF_IMPORT_DIRECTORY),
|
|
// 单个渲染包装 JSON 的最大导入字节数。
|
|
RESULT_REF_IMPORT_MAX_BYTES: z.coerce
|
|
.number()
|
|
.int()
|
|
.positive()
|
|
.default(128 * 1024 * 1024),
|
|
// result_ref 保留时长(小时)。
|
|
RESULT_REF_TTL_HOURS: z.coerce.number().int().positive().default(168),
|
|
// 定时清理过期 result_ref 的扫描周期(毫秒)。
|
|
RESULT_REF_CLEANUP_INTERVAL_MS: z.coerce
|
|
.number()
|
|
.int()
|
|
.positive()
|
|
.default(3600000),
|
|
})
|
|
.superRefine((env, ctx) => {
|
|
let modelOptions;
|
|
try {
|
|
modelOptions = parseAgentModelOptions(env.OPENCODE_MODEL_OPTIONS);
|
|
} catch (error) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
path: ["OPENCODE_MODEL_OPTIONS"],
|
|
message: error instanceof Error ? error.message : String(error),
|
|
});
|
|
return;
|
|
}
|
|
const supportedModels = getAgentModelIds(modelOptions);
|
|
if (supportedModels.length === 0) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
path: ["OPENCODE_MODEL_OPTIONS"],
|
|
message: "OPENCODE_MODEL_OPTIONS must include at least one model",
|
|
});
|
|
}
|
|
if (!supportedModels.includes(env.OPENCODE_MODEL)) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
path: ["OPENCODE_MODEL"],
|
|
message: "OPENCODE_MODEL must be included in OPENCODE_MODEL_OPTIONS",
|
|
});
|
|
}
|
|
});
|
|
|
|
export type AppConfig = z.infer<typeof envSchema>;
|
|
|
|
export const config: AppConfig = envSchema.parse(process.env);
|