chore: speed up tests and update opencode models

This commit is contained in:
Peter Steinberger
2026-01-23 11:36:28 +00:00
parent bb9bddebb4
commit 8b7b7e154f
7 changed files with 79 additions and 48 deletions

View File

@@ -1,4 +1,5 @@
import { spawn } from "node:child_process"; import { spawn } from "node:child_process";
import os from "node:os";
const pnpm = process.platform === "win32" ? "pnpm.cmd" : "pnpm"; const pnpm = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
@@ -7,17 +8,31 @@ const runs = [
name: "unit", name: "unit",
args: ["vitest", "run", "--config", "vitest.unit.config.ts"], args: ["vitest", "run", "--config", "vitest.unit.config.ts"],
}, },
{
name: "extensions",
args: ["vitest", "run", "--config", "vitest.extensions.config.ts"],
},
{ {
name: "gateway", name: "gateway",
args: ["vitest", "run", "--config", "vitest.gateway.config.ts"], args: ["vitest", "run", "--config", "vitest.gateway.config.ts"],
}, },
]; ];
const parallelRuns = runs.filter((entry) => entry.name !== "gateway");
const serialRuns = runs.filter((entry) => entry.name === "gateway");
const children = new Set(); const children = new Set();
const isCI = process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true";
const overrideWorkers = Number.parseInt(process.env.CLAWDBOT_TEST_WORKERS ?? "", 10);
const resolvedOverride = Number.isFinite(overrideWorkers) && overrideWorkers > 0 ? overrideWorkers : null;
const localWorkers = Math.max(4, Math.min(16, os.cpus().length));
const perRunWorkers = Math.max(1, Math.floor(localWorkers / parallelRuns.length));
const maxWorkers = isCI ? null : resolvedOverride ?? perRunWorkers;
const run = (entry) => const run = (entry) =>
new Promise((resolve) => { new Promise((resolve) => {
const child = spawn(pnpm, entry.args, { const args = maxWorkers ? [...entry.args, "--maxWorkers", String(maxWorkers)] : entry.args;
const child = spawn(pnpm, args, {
stdio: "inherit", stdio: "inherit",
env: { ...process.env, VITEST_GROUP: entry.name }, env: { ...process.env, VITEST_GROUP: entry.name },
shell: process.platform === "win32", shell: process.platform === "win32",
@@ -38,6 +53,18 @@ const shutdown = (signal) => {
process.on("SIGINT", () => shutdown("SIGINT")); process.on("SIGINT", () => shutdown("SIGINT"));
process.on("SIGTERM", () => shutdown("SIGTERM")); process.on("SIGTERM", () => shutdown("SIGTERM"));
const codes = await Promise.all(runs.map(run)); const parallelCodes = await Promise.all(parallelRuns.map(run));
const failed = codes.find((code) => code !== 0); const failedParallel = parallelCodes.find((code) => code !== 0);
process.exit(failed ?? 0); if (failedParallel !== undefined) {
process.exit(failedParallel);
}
for (const entry of serialRuns) {
// eslint-disable-next-line no-await-in-loop
const code = await run(entry);
if (code !== 0) {
process.exit(code);
}
}
process.exit(0);

View File

@@ -66,6 +66,10 @@ export function isModernModelRef(ref: ModelRef): boolean {
return matchesPrefix(id, XAI_PREFIXES); return matchesPrefix(id, XAI_PREFIXES);
} }
if (provider === "opencode" && id.endsWith("-free")) {
return false;
}
if (provider === "openrouter" || provider === "opencode") { if (provider === "openrouter" || provider === "opencode") {
return matchesAny(id, [ return matchesAny(id, [
...ANTHROPIC_PREFIXES, ...ANTHROPIC_PREFIXES,

View File

@@ -41,12 +41,11 @@ describe("resolveOpencodeZenAlias", () => {
describe("resolveOpencodeZenModelApi", () => { describe("resolveOpencodeZenModelApi", () => {
it("maps APIs by model family", () => { it("maps APIs by model family", () => {
expect(resolveOpencodeZenModelApi("claude-opus-4-5")).toBe("anthropic-messages"); expect(resolveOpencodeZenModelApi("claude-opus-4-5")).toBe("anthropic-messages");
expect(resolveOpencodeZenModelApi("minimax-m2.1-free")).toBe("anthropic-messages");
expect(resolveOpencodeZenModelApi("gemini-3-pro")).toBe("google-generative-ai"); expect(resolveOpencodeZenModelApi("gemini-3-pro")).toBe("google-generative-ai");
expect(resolveOpencodeZenModelApi("gpt-5.2")).toBe("openai-responses"); expect(resolveOpencodeZenModelApi("gpt-5.2")).toBe("openai-responses");
expect(resolveOpencodeZenModelApi("alpha-gd4")).toBe("openai-completions"); expect(resolveOpencodeZenModelApi("alpha-gd4")).toBe("openai-completions");
expect(resolveOpencodeZenModelApi("big-pickle")).toBe("openai-completions"); expect(resolveOpencodeZenModelApi("big-pickle")).toBe("openai-completions");
expect(resolveOpencodeZenModelApi("glm-4.7-free")).toBe("openai-completions"); expect(resolveOpencodeZenModelApi("glm-4.7")).toBe("openai-completions");
expect(resolveOpencodeZenModelApi("some-unknown-model")).toBe("openai-completions"); expect(resolveOpencodeZenModelApi("some-unknown-model")).toBe("openai-completions");
}); });
}); });
@@ -55,10 +54,10 @@ describe("getOpencodeZenStaticFallbackModels", () => {
it("returns an array of models", () => { it("returns an array of models", () => {
const models = getOpencodeZenStaticFallbackModels(); const models = getOpencodeZenStaticFallbackModels();
expect(Array.isArray(models)).toBe(true); expect(Array.isArray(models)).toBe(true);
expect(models.length).toBe(11); expect(models.length).toBe(10);
}); });
it("includes Claude, GPT, Gemini, GLM, and MiniMax models", () => { it("includes Claude, GPT, Gemini, and GLM models", () => {
const models = getOpencodeZenStaticFallbackModels(); const models = getOpencodeZenStaticFallbackModels();
const ids = models.map((m) => m.id); const ids = models.map((m) => m.id);
@@ -66,8 +65,7 @@ describe("getOpencodeZenStaticFallbackModels", () => {
expect(ids).toContain("gpt-5.2"); expect(ids).toContain("gpt-5.2");
expect(ids).toContain("gpt-5.1-codex"); expect(ids).toContain("gpt-5.1-codex");
expect(ids).toContain("gemini-3-pro"); expect(ids).toContain("gemini-3-pro");
expect(ids).toContain("glm-4.7-free"); expect(ids).toContain("glm-4.7");
expect(ids).toContain("minimax-m2.1-free");
}); });
it("returns valid ModelDefinitionConfig objects", () => { it("returns valid ModelDefinitionConfig objects", () => {
@@ -90,8 +88,7 @@ describe("OPENCODE_ZEN_MODEL_ALIASES", () => {
expect(OPENCODE_ZEN_MODEL_ALIASES.codex).toBe("gpt-5.1-codex"); expect(OPENCODE_ZEN_MODEL_ALIASES.codex).toBe("gpt-5.1-codex");
expect(OPENCODE_ZEN_MODEL_ALIASES.gpt5).toBe("gpt-5.2"); expect(OPENCODE_ZEN_MODEL_ALIASES.gpt5).toBe("gpt-5.2");
expect(OPENCODE_ZEN_MODEL_ALIASES.gemini).toBe("gemini-3-pro"); expect(OPENCODE_ZEN_MODEL_ALIASES.gemini).toBe("gemini-3-pro");
expect(OPENCODE_ZEN_MODEL_ALIASES.glm).toBe("glm-4.7-free"); expect(OPENCODE_ZEN_MODEL_ALIASES.glm).toBe("glm-4.7");
expect(OPENCODE_ZEN_MODEL_ALIASES.minimax).toBe("minimax-m2.1-free");
// Legacy aliases (kept for backward compatibility). // Legacy aliases (kept for backward compatibility).
expect(OPENCODE_ZEN_MODEL_ALIASES.sonnet).toBe("claude-opus-4-5"); expect(OPENCODE_ZEN_MODEL_ALIASES.sonnet).toBe("claude-opus-4-5");

View File

@@ -68,13 +68,9 @@ export const OPENCODE_ZEN_MODEL_ALIASES: Record<string, string> = {
"gemini-2.5-flash": "gemini-3-flash", "gemini-2.5-flash": "gemini-3-flash",
// GLM (free + alpha) // GLM (free + alpha)
glm: "glm-4.7-free", glm: "glm-4.7",
"glm-free": "glm-4.7-free", "glm-free": "glm-4.7",
"alpha-glm": "alpha-glm-4.7", "alpha-glm": "alpha-glm-4.7",
// MiniMax
minimax: "minimax-m2.1-free",
"minimax-free": "minimax-m2.1-free",
}; };
/** /**
@@ -134,7 +130,7 @@ const MODEL_COSTS: Record<
cacheWrite: 0, cacheWrite: 0,
}, },
"gpt-5.1": { input: 1.07, output: 8.5, cacheRead: 0.107, cacheWrite: 0 }, "gpt-5.1": { input: 1.07, output: 8.5, cacheRead: 0.107, cacheWrite: 0 },
"glm-4.7-free": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, "glm-4.7": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
"gemini-3-flash": { input: 0.5, output: 3, cacheRead: 0.05, cacheWrite: 0 }, "gemini-3-flash": { input: 0.5, output: 3, cacheRead: 0.05, cacheWrite: 0 },
"gpt-5.1-codex-max": { "gpt-5.1-codex-max": {
input: 1.25, input: 1.25,
@@ -142,7 +138,6 @@ const MODEL_COSTS: Record<
cacheRead: 0.125, cacheRead: 0.125,
cacheWrite: 0, cacheWrite: 0,
}, },
"minimax-m2.1-free": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
"gpt-5.2": { input: 1.75, output: 14, cacheRead: 0.175, cacheWrite: 0 }, "gpt-5.2": { input: 1.75, output: 14, cacheRead: 0.175, cacheWrite: 0 },
}; };
@@ -155,10 +150,9 @@ const MODEL_CONTEXT_WINDOWS: Record<string, number> = {
"alpha-glm-4.7": 204800, "alpha-glm-4.7": 204800,
"gpt-5.1-codex-mini": 400000, "gpt-5.1-codex-mini": 400000,
"gpt-5.1": 400000, "gpt-5.1": 400000,
"glm-4.7-free": 204800, "glm-4.7": 204800,
"gemini-3-flash": 1048576, "gemini-3-flash": 1048576,
"gpt-5.1-codex-max": 400000, "gpt-5.1-codex-max": 400000,
"minimax-m2.1-free": 204800,
"gpt-5.2": 400000, "gpt-5.2": 400000,
}; };
@@ -173,10 +167,9 @@ const MODEL_MAX_TOKENS: Record<string, number> = {
"alpha-glm-4.7": 131072, "alpha-glm-4.7": 131072,
"gpt-5.1-codex-mini": 128000, "gpt-5.1-codex-mini": 128000,
"gpt-5.1": 128000, "gpt-5.1": 128000,
"glm-4.7-free": 131072, "glm-4.7": 131072,
"gemini-3-flash": 65536, "gemini-3-flash": 65536,
"gpt-5.1-codex-max": 128000, "gpt-5.1-codex-max": 128000,
"minimax-m2.1-free": 131072,
"gpt-5.2": 128000, "gpt-5.2": 128000,
}; };
@@ -211,10 +204,9 @@ const MODEL_NAMES: Record<string, string> = {
"alpha-glm-4.7": "Alpha GLM-4.7", "alpha-glm-4.7": "Alpha GLM-4.7",
"gpt-5.1-codex-mini": "GPT-5.1 Codex Mini", "gpt-5.1-codex-mini": "GPT-5.1 Codex Mini",
"gpt-5.1": "GPT-5.1", "gpt-5.1": "GPT-5.1",
"glm-4.7-free": "GLM-4.7", "glm-4.7": "GLM-4.7",
"gemini-3-flash": "Gemini 3 Flash", "gemini-3-flash": "Gemini 3 Flash",
"gpt-5.1-codex-max": "GPT-5.1 Codex Max", "gpt-5.1-codex-max": "GPT-5.1 Codex Max",
"minimax-m2.1-free": "MiniMax M2.1",
"gpt-5.2": "GPT-5.2", "gpt-5.2": "GPT-5.2",
}; };
@@ -240,10 +232,9 @@ export function getOpencodeZenStaticFallbackModels(): ModelDefinitionConfig[] {
"alpha-glm-4.7", "alpha-glm-4.7",
"gpt-5.1-codex-mini", "gpt-5.1-codex-mini",
"gpt-5.1", "gpt-5.1",
"glm-4.7-free", "glm-4.7",
"gemini-3-flash", "gemini-3-flash",
"gpt-5.1-codex-max", "gpt-5.1-codex-max",
"minimax-m2.1-free",
"gpt-5.2", "gpt-5.2",
]; ];

View File

@@ -0,0 +1,14 @@
import { defineConfig } from "vitest/config";
import baseConfig from "./vitest.config.ts";
const baseTest = (baseConfig as { test?: { exclude?: string[] } }).test ?? {};
const exclude = baseTest.exclude ?? [];
export default defineConfig({
...baseConfig,
test: {
...baseTest,
include: ["extensions/**/*.test.ts"],
exclude,
},
});

View File

@@ -1,15 +1,14 @@
import { defineConfig, mergeConfig } from "vitest/config"; import { defineConfig } from "vitest/config";
import baseConfig from "./vitest.config.ts"; import baseConfig from "./vitest.config.ts";
const baseTest = (baseConfig as { test?: { exclude?: string[] } }).test ?? {}; const baseTest = (baseConfig as { test?: { exclude?: string[] } }).test ?? {};
const exclude = baseTest.exclude ?? []; const exclude = baseTest.exclude ?? [];
export default mergeConfig( export default defineConfig({
baseConfig, ...baseConfig,
defineConfig({
test: { test: {
include: ["src/gateway/**/*.test.ts", "extensions/**/*.test.ts"], ...baseTest,
include: ["src/gateway/**/*.test.ts"],
exclude, exclude,
}, },
}), });
);

View File

@@ -1,4 +1,4 @@
import { defineConfig, mergeConfig } from "vitest/config"; import { defineConfig } from "vitest/config";
import baseConfig from "./vitest.config.ts"; import baseConfig from "./vitest.config.ts";
const baseTest = (baseConfig as { test?: { include?: string[]; exclude?: string[] } }).test ?? {}; const baseTest = (baseConfig as { test?: { include?: string[]; exclude?: string[] } }).test ?? {};
@@ -9,12 +9,11 @@ const include = baseTest.include ?? [
]; ];
const exclude = baseTest.exclude ?? []; const exclude = baseTest.exclude ?? [];
export default mergeConfig( export default defineConfig({
baseConfig, ...baseConfig,
defineConfig({
test: { test: {
...baseTest,
include, include,
exclude: [...exclude, "src/gateway/**", "extensions/**"], exclude: [...exclude, "src/gateway/**", "extensions/**"],
}, },
}), });
);