Implement BackendAdapter interface with four CLI backends: - ClaudeCodeBackend (extracted from AgentRuntime) - CodexBackend (OpenAI Codex CLI) - GeminiBackend (Google Gemini CLI) - OpenCodeBackend (OpenCode CLI) Add BackendRegistry for resolution/creation via AGENT_BACKEND env var. Refactor AgentRuntime to delegate to BackendAdapter instead of hardcoding Claude CLI. Update GatewayConfig with new env vars (AGENT_BACKEND, BACKEND_CLI_PATH, BACKEND_MODEL, BACKEND_MAX_TURNS). Includes 10 property-based test files and unit tests for edge cases.
120 lines
4.4 KiB
TypeScript
120 lines
4.4 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
import { loadConfig } from "../../src/config.js";
|
|
|
|
describe("loadConfig", () => {
|
|
const originalEnv = process.env;
|
|
|
|
beforeEach(() => {
|
|
process.env = { ...originalEnv };
|
|
// Set required vars by default
|
|
process.env.DISCORD_BOT_TOKEN = "test-discord-token";
|
|
});
|
|
|
|
afterEach(() => {
|
|
process.env = originalEnv;
|
|
});
|
|
|
|
it("should load required environment variables", () => {
|
|
const config = loadConfig();
|
|
expect(config.discordBotToken).toBe("test-discord-token");
|
|
});
|
|
|
|
it("should apply default values for optional config", () => {
|
|
const config = loadConfig();
|
|
expect(config.claudeCliPath).toBe("claude");
|
|
expect(config.allowedTools).toEqual(["Read", "Write", "Edit", "Glob", "Grep", "WebSearch", "WebFetch"]);
|
|
expect(config.permissionMode).toBe("bypassPermissions");
|
|
expect(config.queryTimeoutMs).toBe(120_000);
|
|
expect(config.maxConcurrentQueries).toBe(5);
|
|
expect(config.configDir).toBe("./config");
|
|
expect(config.maxQueueDepth).toBe(100);
|
|
expect(config.outputChannelId).toBeUndefined();
|
|
expect(config.agentBackend).toBe("claude");
|
|
expect(config.backendCliPath).toBe("claude");
|
|
expect(config.backendModel).toBeUndefined();
|
|
expect(config.backendMaxTurns).toBe(25);
|
|
});
|
|
|
|
it("should parse ALLOWED_TOOLS from comma-separated string", () => {
|
|
process.env.ALLOWED_TOOLS = "Read,Write,Bash";
|
|
const config = loadConfig();
|
|
expect(config.allowedTools).toEqual(["Read", "Write", "Bash"]);
|
|
});
|
|
|
|
it("should trim whitespace from ALLOWED_TOOLS entries", () => {
|
|
process.env.ALLOWED_TOOLS = " Read , Write , Bash ";
|
|
const config = loadConfig();
|
|
expect(config.allowedTools).toEqual(["Read", "Write", "Bash"]);
|
|
});
|
|
|
|
it("should read all optional environment variables", () => {
|
|
process.env.PERMISSION_MODE = "default";
|
|
process.env.QUERY_TIMEOUT_MS = "60000";
|
|
process.env.MAX_CONCURRENT_QUERIES = "10";
|
|
process.env.CONFIG_DIR = "/custom/config";
|
|
process.env.MAX_QUEUE_DEPTH = "200";
|
|
process.env.OUTPUT_CHANNEL_ID = "123456789";
|
|
process.env.CLAUDE_CLI_PATH = "/usr/local/bin/claude";
|
|
|
|
const config = loadConfig();
|
|
expect(config.permissionMode).toBe("default");
|
|
expect(config.queryTimeoutMs).toBe(60_000);
|
|
expect(config.maxConcurrentQueries).toBe(10);
|
|
expect(config.configDir).toBe("/custom/config");
|
|
expect(config.maxQueueDepth).toBe(200);
|
|
expect(config.outputChannelId).toBe("123456789");
|
|
expect(config.claudeCliPath).toBe("/usr/local/bin/claude");
|
|
});
|
|
|
|
it("should read new backend environment variables", () => {
|
|
process.env.AGENT_BACKEND = "codex";
|
|
process.env.BACKEND_CLI_PATH = "/usr/local/bin/codex";
|
|
process.env.BACKEND_MODEL = "gpt-4";
|
|
process.env.BACKEND_MAX_TURNS = "10";
|
|
|
|
const config = loadConfig();
|
|
expect(config.agentBackend).toBe("codex");
|
|
expect(config.backendCliPath).toBe("/usr/local/bin/codex");
|
|
expect(config.backendModel).toBe("gpt-4");
|
|
expect(config.backendMaxTurns).toBe(10);
|
|
});
|
|
|
|
it("should default backendCliPath to backend name when no CLI path env vars are set", () => {
|
|
process.env.AGENT_BACKEND = "gemini";
|
|
const config = loadConfig();
|
|
expect(config.backendCliPath).toBe("gemini");
|
|
});
|
|
|
|
it("should use CLAUDE_CLI_PATH as backendCliPath when backend is claude and BACKEND_CLI_PATH is not set", () => {
|
|
process.env.CLAUDE_CLI_PATH = "/custom/claude";
|
|
const config = loadConfig();
|
|
expect(config.agentBackend).toBe("claude");
|
|
expect(config.backendCliPath).toBe("/custom/claude");
|
|
expect(config.claudeCliPath).toBe("/custom/claude");
|
|
});
|
|
|
|
it("should prefer BACKEND_CLI_PATH over CLAUDE_CLI_PATH", () => {
|
|
process.env.CLAUDE_CLI_PATH = "/old/claude";
|
|
process.env.BACKEND_CLI_PATH = "/new/backend";
|
|
const config = loadConfig();
|
|
expect(config.backendCliPath).toBe("/new/backend");
|
|
});
|
|
|
|
it("should throw for invalid AGENT_BACKEND value", () => {
|
|
process.env.AGENT_BACKEND = "invalid-backend";
|
|
expect(() => loadConfig()).toThrow('Invalid backend name "invalid-backend"');
|
|
});
|
|
|
|
it("should throw when DISCORD_BOT_TOKEN is missing", () => {
|
|
delete process.env.DISCORD_BOT_TOKEN;
|
|
expect(() => loadConfig()).toThrow("DISCORD_BOT_TOKEN");
|
|
});
|
|
|
|
it("should list all missing required variables in error message", () => {
|
|
delete process.env.DISCORD_BOT_TOKEN;
|
|
expect(() => loadConfig()).toThrow(
|
|
"Missing required environment variables: DISCORD_BOT_TOKEN"
|
|
);
|
|
});
|
|
});
|