feat: add pluggable multi-CLI backend system
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.
This commit is contained in:
95
tests/property/codex-backend.property.test.ts
Normal file
95
tests/property/codex-backend.property.test.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { describe, it } from "vitest";
|
||||
import fc from "fast-check";
|
||||
import { CodexBackend } from "../../src/backends/codex-backend.js";
|
||||
import type { BackendAdapterConfig } from "../../src/backends/types.js";
|
||||
|
||||
// Feature: multi-cli-backend, Property 2: Codex backend required flags
|
||||
// **Validates: Requirements 3.2, 3.3, 3.4, 3.5**
|
||||
|
||||
/**
|
||||
* Arbitrary for non-empty strings that won't break CLI arg parsing.
|
||||
*/
|
||||
const nonEmptyString = fc.string({ minLength: 1, maxLength: 200 });
|
||||
|
||||
/**
|
||||
* Arbitrary for working directory paths (non-empty, path-like).
|
||||
*/
|
||||
const workingDir = fc.stringMatching(/^\/[A-Za-z0-9_/.-]{1,100}$/);
|
||||
|
||||
function createBackend(workDir: string): CodexBackend {
|
||||
const config: BackendAdapterConfig = {
|
||||
cliPath: "codex",
|
||||
workingDir: workDir,
|
||||
queryTimeoutMs: 60000,
|
||||
allowedTools: [],
|
||||
maxTurns: 25,
|
||||
};
|
||||
return new CodexBackend(config);
|
||||
}
|
||||
|
||||
describe("Property 2: Codex backend required flags", () => {
|
||||
it("generated args always contain the exec subcommand", () => {
|
||||
fc.assert(
|
||||
fc.property(
|
||||
nonEmptyString,
|
||||
workingDir,
|
||||
(prompt, workDir) => {
|
||||
const backend = createBackend(workDir);
|
||||
const args = backend.buildArgs(prompt);
|
||||
|
||||
return args[0] === "exec";
|
||||
},
|
||||
),
|
||||
{ numRuns: 100 },
|
||||
);
|
||||
});
|
||||
|
||||
it("generated args always contain --json", () => {
|
||||
fc.assert(
|
||||
fc.property(
|
||||
nonEmptyString,
|
||||
workingDir,
|
||||
(prompt, workDir) => {
|
||||
const backend = createBackend(workDir);
|
||||
const args = backend.buildArgs(prompt);
|
||||
|
||||
return args.includes("--json");
|
||||
},
|
||||
),
|
||||
{ numRuns: 100 },
|
||||
);
|
||||
});
|
||||
|
||||
it("generated args always contain --dangerously-bypass-approvals-and-sandbox", () => {
|
||||
fc.assert(
|
||||
fc.property(
|
||||
nonEmptyString,
|
||||
workingDir,
|
||||
(prompt, workDir) => {
|
||||
const backend = createBackend(workDir);
|
||||
const args = backend.buildArgs(prompt);
|
||||
|
||||
return args.includes("--dangerously-bypass-approvals-and-sandbox");
|
||||
},
|
||||
),
|
||||
{ numRuns: 100 },
|
||||
);
|
||||
});
|
||||
|
||||
it("generated args always contain --cd with the configured working directory", () => {
|
||||
fc.assert(
|
||||
fc.property(
|
||||
nonEmptyString,
|
||||
workingDir,
|
||||
(prompt, workDir) => {
|
||||
const backend = createBackend(workDir);
|
||||
const args = backend.buildArgs(prompt);
|
||||
|
||||
const cdIndex = args.indexOf("--cd");
|
||||
return cdIndex !== -1 && args[cdIndex + 1] === workDir;
|
||||
},
|
||||
),
|
||||
{ numRuns: 100 },
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user