import { readFile } from "node:fs/promises"; import path from "node:path"; import YAML from "yaml"; import { z } from "zod"; const modelSchema = z.object({ cli_model: z.string().min(1), effort: z.string().min(1), }); const configSchema = z.object({ host: z.literal("127.0.0.1").default("127.0.0.1"), port: z.number().int().min(1).max(65535).default(18000), command_code_executable: z.string().min(1).default("command-code"), command_code_working_directory: z.string().min(1).default("."), timeout_seconds: z.number().int().positive().default(1800), max_request_bytes: z.number().int().positive().default(20 * 1024 * 1024), max_queue_size: z.number().int().min(0).max(1000).default(8), max_turns: z.number().int().positive().default(100), stream_thinking: z.boolean().default(false), response_store_directory: z.string().min(1).default(".command-code-openai-bridge/responses"), permission_mode: z.enum(["default", "standard", "plan", "auto-accept", "dont-ask"]).default("auto-accept"), dangerously_skip_permissions: z.boolean().default(false), models: z.record(z.string().min(1), modelSchema).refine( (models) => Object.keys(models).length > 0, "At least one model mapping is required", ), }); export type BridgeConfig = z.infer & { configDirectory: string; resolvedResponseStoreDirectory: string; resolvedWorkingDirectory: string; }; export async function loadConfig(configPath: string): Promise { const absolutePath = path.resolve(configPath); const source = await readFile(absolutePath, "utf8"); const parsed = configSchema.parse(YAML.parse(source)); const configDirectory = path.dirname(absolutePath); return { ...parsed, configDirectory, resolvedResponseStoreDirectory: path.resolve(configDirectory, parsed.response_store_directory), resolvedWorkingDirectory: path.resolve(configDirectory, parsed.command_code_working_directory), }; }