Files
codex-game-studio/tests/codex-runtime.test.ts
T
7e8846a7fb feat: add deterministic model tier routing (#14)
* feat: add deterministic model tier routing

Define Sol, Terra, and Luna policies with fixed fallbacks, explicit surface assignments, runtime precedence, and Luna escalation. Validate generated metadata and expose tier overrides across role, task, and orchestration commands.

* fix: infer model tiers from surface models

* fix: rebalance prompt model routing

* fix: decouple prompt effort routing

---------

Co-authored-by: MerlinH <merlinh221@gmail.com>
2026-07-10 18:49:20 +10:00

77 lines
4.0 KiB
TypeScript

import { chmodSync, writeFileSync } from "node:fs";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { describe, test } from "node:test";
import { expect } from "expect";
import { buildCodexExecArgs, checkCodexAvailability, executeCodexCommandWithFallback, resolveCodexCommand } from "../src/codex-runtime.js";
describe("codex runtime", () => {
test("resolves Codex command from CODEX_BIN only", () => {
expect(resolveCodexCommand({ CODEX_BIN: "codex-custom" })).toBe("codex-custom");
expect(resolveCodexCommand({ OPEN_GAMESTUDIO_CODEX_BIN: "legacy-codex" })).toBe("codex");
expect(resolveCodexCommand({})).toBe("codex");
});
test("builds structured codex exec args with cwd, sandbox, model, reasoning, and stdin prompt", () => {
const args = buildCodexExecArgs({ projectRoot: "/repo", sandbox: "read-only", model: "gpt-5.6-sol", reasoningEffort: "high" });
expect(args).toContain("exec");
expect(args).toContain("--model");
expect(args).toContain("gpt-5.6-sol");
expect(args).toEqual(expect.arrayContaining(["-c", 'model_reasoning_effort="high"']));
expect(args).toContain("--cd");
expect(args).toContain("/repo");
expect(args).toContain("--sandbox");
expect(args).toContain("read-only");
expect(args).toContain("-");
});
test("reports availability failures without throwing", async () => {
const result = await checkCodexAvailability({ codexBin: "/missing/codex" });
expect(result.ok).toBe(false);
expect(result.command).toBe("/missing/codex");
expect(result.reason).toMatch(/not found|unavailable/i);
});
test("reports authenticated stub as available", async () => {
const cwd = mkdtempSync(path.join(tmpdir(), "ogs-codex-"));
const stub = path.join(cwd, "codex-stub.mjs");
writeFileSync(stub, "#!/usr/bin/env node\nconsole.log('codex 1.0.0');\n");
chmodSync(stub, 0o755);
const result = await checkCodexAvailability({ codexBin: stub });
expect(result.ok).toBe(true);
expect(result.command).toBe(stub);
});
test("uses the designated fallback only when the primary model is unavailable", async () => {
const cwd = mkdtempSync(path.join(tmpdir(), "ogs-codex-fallback-"));
const stub = path.join(cwd, "codex-stub.mjs");
writeFileSync(stub, `#!/usr/bin/env node\nconst model = process.argv[process.argv.indexOf('--model') + 1];\nif (model === 'gpt-5.6-sol') { console.error('model gpt-5.6-sol is not available for this account'); process.exit(1); }\nconsole.log(model);\n`);
chmodSync(stub, 0o755);
const primary = { command: stub, args: buildCodexExecArgs({ projectRoot: cwd, model: "gpt-5.6-sol", reasoningEffort: "high" }) };
const fallback = { command: stub, args: buildCodexExecArgs({ projectRoot: cwd, model: "gpt-5.5", reasoningEffort: "high" }) };
const result = await executeCodexCommandWithFallback({ primary, fallback, primaryModel: "gpt-5.6-sol", fallbackModel: "gpt-5.5" }, "prompt", { cwd });
expect(result.status).toBe(0);
expect(result.stdout.trim()).toBe("gpt-5.5");
expect(result.executedModel).toBe("gpt-5.5");
expect(result.fallbackFromModel).toBe("gpt-5.6-sol");
});
test("does not hide ordinary execution failures behind a fallback", async () => {
const cwd = mkdtempSync(path.join(tmpdir(), "ogs-codex-no-fallback-"));
const stub = path.join(cwd, "codex-stub.mjs");
writeFileSync(stub, "#!/usr/bin/env node\nconsole.error('tests failed'); process.exit(2);\n");
chmodSync(stub, 0o755);
const primary = { command: stub, args: buildCodexExecArgs({ projectRoot: cwd, model: "gpt-5.6-terra", reasoningEffort: "medium" }) };
const fallback = { command: stub, args: buildCodexExecArgs({ projectRoot: cwd, model: "gpt-5.4", reasoningEffort: "medium" }) };
const result = await executeCodexCommandWithFallback({ primary, fallback, primaryModel: "gpt-5.6-terra", fallbackModel: "gpt-5.4" }, "prompt", { cwd });
expect(result.status).toBe(2);
expect(result.executedModel).toBe("gpt-5.6-terra");
expect(result.fallbackFromModel).toBeUndefined();
});
});