Files
TJWaterAgent/src/config.ts
T
jiang ce04704af2
Generic Container CI/CD / test-build-publish (push) Successful in 2m2s
Agent CI/CD v2 / build-test-publish-and-deploy (push) Successful in 2m2s
fix(agent): isolate conversation workspaces
2026-08-25 13:18:39 +08:00

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);