import { readFileSync } from "node:fs"; import { homedir } from "node:os"; import { join } from "node:path"; // ── 配置嵌套类型 ── export interface L0Config { enabled: boolean; expireMinutes: number; bashTruncateChars: number; thinkingExpireMinutes: number; protectRecentTurns: number; keepRecent: number; } export interface L1Config { enabled: boolean; summaryThresholdChars: number; keepHeadLines: number; keepTailLines: number; protectRecentTurns: number; } export interface L2Config { enabled: boolean; emergencyThreshold: number; protectRecentTurns: number; } export interface McConfig { enabled: boolean; gapThresholdMinutes: number; keepRecent: number; } export interface BudgetConfig { enabled: boolean; maxToolResultCharsPerMessage: number; previewSize: number; } export interface ContextEngineeringConfig { enabled: boolean; l0: L0Config; l1: L1Config; l2: L2Config; mc: McConfig; budget: BudgetConfig; } // ── 默认配置 ── export const DEFAULT_CONFIG: ContextEngineeringConfig = { enabled: true, l0: { enabled: true, expireMinutes: 30, bashTruncateChars: 4000, thinkingExpireMinutes: 5, protectRecentTurns: 2, keepRecent: 5, }, l1: { enabled: true, summaryThresholdChars: 8000, keepHeadLines: 10, keepTailLines: 5, protectRecentTurns: 2, }, l2: { enabled: true, emergencyThreshold: 0.9, protectRecentTurns: 3, }, mc: { enabled: true, gapThresholdMinutes: 60, keepRecent: 5, }, budget: { enabled: true, maxToolResultCharsPerMessage: 200_000, previewSize: 2000, }, }; // ── 深合并工具 ── function deepMerge( base: T, override: Record, ): T { const result = { ...base } as Record; for (const key of Object.keys(override)) { const baseVal = result[key]; const overVal = override[key]; if ( baseVal != null && overVal != null && typeof baseVal === "object" && typeof overVal === "object" && !Array.isArray(baseVal) && !Array.isArray(overVal) ) { result[key] = deepMerge( baseVal as Record, overVal as Record, ); } else { result[key] = overVal; } } return result as T; } // ── 读取配置 ── export function loadConfig( settingsPath?: string, ): ContextEngineeringConfig { const filePath = settingsPath ?? join(homedir(), ".pi", "agent", "extensions", "context-engineering", "config.json"); let raw: string; try { raw = readFileSync(filePath, "utf-8"); } catch (err) { if ((err as NodeJS.ErrnoException).code === "ENOENT") return { ...DEFAULT_CONFIG }; throw new Error(`Failed to read config ${filePath}: ${(err as Error).message}`); } let parsed: Record; try { parsed = JSON.parse(raw) as Record; } catch (err) { throw new Error(`Invalid JSON in ${filePath}: ${(err as Error).message}`); } const override = parsed["context-engineering"]; if (override == null || typeof override !== "object") { return { ...DEFAULT_CONFIG }; } return deepMerge( DEFAULT_CONFIG, override as Record, ); } // ── 命令参数解析 ── // 命令参数最小部分数(target + action) const MIN_COMMAND_TOKENS = 2; export function parseLevelArgs( args: string, ): { target: "global" | "l0" | "l1" | "l2" | "mc" | "budget"; action: "on" | "off" } | null { const tokens = args.trim().split(/\s+/); if (tokens.length < MIN_COMMAND_TOKENS) { return null; } const [rawTarget, rawAction] = tokens; const validTargets = new Set(["global", "l0", "l1", "l2", "mc", "budget"]); const validActions = new Set(["on", "off"]); if (!validTargets.has(rawTarget) || !validActions.has(rawAction)) { return null; } return { target: rawTarget as "global" | "l0" | "l1" | "l2" | "mc" | "budget", action: rawAction as "on" | "off", }; }