chore: migrate to oxlint and oxfmt

Co-authored-by: Christoph Nakazawa <christoph.pojer@gmail.com>
This commit is contained in:
Peter Steinberger
2026-01-14 14:31:43 +00:00
parent 912ebffc63
commit c379191f80
1480 changed files with 28608 additions and 43547 deletions

View File

@@ -5,10 +5,7 @@ import os from "node:os";
import path from "node:path";
import type { Api, Model } from "@mariozechner/pi-ai";
import {
discoverAuthStorage,
discoverModels,
} from "@mariozechner/pi-coding-agent";
import { discoverAuthStorage, discoverModels } from "@mariozechner/pi-coding-agent";
import { describe, it } from "vitest";
import { resolveClawdbotAgentDir } from "../agents/agent-paths.js";
import { resolveAgentWorkspaceDir } from "../agents/agent-scope.js";
@@ -27,10 +24,7 @@ import { getApiKeyForModel } from "../agents/model-auth.js";
import { ensureClawdbotModelsJson } from "../agents/models-config.js";
import { loadConfig } from "../config/config.js";
import type { ClawdbotConfig, ModelProviderConfig } from "../config/types.js";
import {
GATEWAY_CLIENT_MODES,
GATEWAY_CLIENT_NAMES,
} from "../utils/message-channel.js";
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js";
import { GatewayClient } from "./client.js";
import { renderCatNoncePngBase64 } from "./live-image-probe.js";
import { startGatewayServer } from "./server.js";
@@ -67,8 +61,7 @@ function assertNoReasoningTags(params: {
}): void {
if (!params.text) return;
if (THINKING_TAG_RE.test(params.text) || FINAL_TAG_RE.test(params.text)) {
const snippet =
params.text.length > 200 ? `${params.text.slice(0, 200)}` : params.text;
const snippet = params.text.length > 200 ? `${params.text.slice(0, 200)}` : params.text;
throw new Error(
`[${params.label}] reasoning tag leak (${params.model} / ${params.phase}): ${snippet}`,
);
@@ -79,11 +72,7 @@ function extractPayloadText(result: unknown): string {
const record = result as Record<string, unknown>;
const payloads = Array.isArray(record.payloads) ? record.payloads : [];
const texts = payloads
.map((p) =>
p && typeof p === "object"
? (p as Record<string, unknown>).text
: undefined,
)
.map((p) => (p && typeof p === "object" ? (p as Record<string, unknown>).text : undefined))
.filter((t): t is string => typeof t === "string" && t.trim().length > 0);
return texts.join("\n").trim();
}
@@ -196,9 +185,9 @@ async function getFreeGatewayPort(): Promise<number> {
for (let attempt = 0; attempt < 25; attempt += 1) {
const port = await getFreePort();
const candidates = [port, port + 1, port + 2, port + 4];
const ok = (
await Promise.all(candidates.map((candidate) => isPortFree(candidate)))
).every(Boolean);
const ok = (await Promise.all(candidates.map((candidate) => isPortFree(candidate)))).every(
Boolean,
);
if (ok) return port;
}
throw new Error("failed to acquire a free gateway port block");
@@ -231,10 +220,7 @@ async function connectClient(params: { url: string; token: string }) {
onClose: (code, reason) =>
stop(new Error(`gateway closed during connect (${code}): ${reason}`)),
});
const timer = setTimeout(
() => stop(new Error("gateway connect timeout")),
10_000,
);
const timer = setTimeout(() => stop(new Error("gateway connect timeout")), 10_000);
timer.unref();
client.start();
});
@@ -270,8 +256,7 @@ function buildLiveGatewayConfig(params: {
: {}),
...providerOverrides,
};
const providers =
Object.keys(nextProviders).length > 0 ? nextProviders : baseProviders;
const providers = Object.keys(nextProviders).length > 0 ? nextProviders : baseProviders;
return {
...params.cfg,
agents: {
@@ -285,15 +270,11 @@ function buildLiveGatewayConfig(params: {
// Live tests should avoid Docker sandboxing so tool probes can
// operate on the temporary probe files we create in the host workspace.
sandbox: { mode: "off" },
models: Object.fromEntries(
params.candidates.map((m) => [`${m.provider}/${m.id}`, {}]),
),
models: Object.fromEntries(params.candidates.map((m) => [`${m.provider}/${m.id}`, {}])),
},
},
models:
Object.keys(providers).length > 0
? { ...params.cfg.models, providers }
: params.cfg.models,
Object.keys(providers).length > 0 ? { ...params.cfg.models, providers } : params.cfg.models,
};
}
@@ -342,12 +323,7 @@ function buildMinimaxProviderOverride(params: {
baseUrl: string;
}): ModelProviderConfig | null {
const existing = params.cfg.models?.providers?.minimax;
if (
!existing ||
!Array.isArray(existing.models) ||
existing.models.length === 0
)
return null;
if (!existing || !Array.isArray(existing.models) || existing.models.length === 0) return null;
return {
...existing,
api: params.api,
@@ -392,9 +368,7 @@ async function runGatewayModelSuite(params: GatewayModelSuiteParams) {
lastGood: hostStore.lastGood ? { ...hostStore.lastGood } : undefined,
usageStats: hostStore.usageStats ? { ...hostStore.usageStats } : undefined,
};
tempStateDir = await fs.mkdtemp(
path.join(os.tmpdir(), "clawdbot-live-state-"),
);
tempStateDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-live-state-"));
process.env.CLAWDBOT_STATE_DIR = tempStateDir;
tempAgentDir = path.join(tempStateDir, "agents", "main", "agent");
saveAuthProfileStore(sanitizedStore, tempAgentDir);
@@ -405,10 +379,7 @@ async function runGatewayModelSuite(params: GatewayModelSuiteParams) {
await fs.mkdir(workspaceDir, { recursive: true });
const nonceA = randomUUID();
const nonceB = randomUUID();
const toolProbePath = path.join(
workspaceDir,
`.clawdbot-live-tool-probe.${nonceA}.txt`,
);
const toolProbePath = path.join(workspaceDir, `.clawdbot-live-tool-probe.${nonceA}.txt`);
await fs.writeFile(toolProbePath, `nonceA=${nonceA}\nnonceB=${nonceB}\n`);
const agentDir = resolveClawdbotAgentDir();
@@ -447,9 +418,7 @@ async function runGatewayModelSuite(params: GatewayModelSuiteParams) {
const anthropicKeys = collectAnthropicApiKeys();
if (anthropicKeys.length > 0) {
process.env.ANTHROPIC_API_KEY = anthropicKeys[0];
logProgress(
`[${params.label}] anthropic keys loaded: ${anthropicKeys.length}`,
);
logProgress(`[${params.label}] anthropic keys loaded: ${anthropicKeys.length}`);
}
const sessionKey = `agent:${agentId}:${params.label}`;
const failures: Array<{ model: string; error: string }> = [];
@@ -461,9 +430,7 @@ async function runGatewayModelSuite(params: GatewayModelSuiteParams) {
const progressLabel = `[${params.label}] ${index + 1}/${total} ${modelKey}`;
const attemptMax =
model.provider === "anthropic" && anthropicKeys.length > 0
? anthropicKeys.length
: 1;
model.provider === "anthropic" && anthropicKeys.length > 0 ? anthropicKeys.length : 1;
for (let attempt = 0; attempt < attemptMax; attempt += 1) {
if (model.provider === "anthropic" && anthropicKeys.length > 0) {
@@ -504,9 +471,7 @@ async function runGatewayModelSuite(params: GatewayModelSuiteParams) {
isEmptyStreamText(text) &&
(model.provider === "minimax" || model.provider === "openai-codex")
) {
logProgress(
`${progressLabel}: skip (${model.provider} empty response)`,
);
logProgress(`${progressLabel}: skip (${model.provider} empty response)`);
break;
}
if (model.provider === "google" && isGoogleModelNotFoundText(text)) {
@@ -522,10 +487,7 @@ async function runGatewayModelSuite(params: GatewayModelSuiteParams) {
label: params.label,
});
if (!isMeaningful(text)) throw new Error(`not meaningful: ${text}`);
if (
!/\bmicro\s*-?\s*tasks?\b/i.test(text) ||
!/\bmacro\s*-?\s*tasks?\b/i.test(text)
) {
if (!/\bmicro\s*-?\s*tasks?\b/i.test(text) || !/\bmacro\s*-?\s*tasks?\b/i.test(text)) {
throw new Error(`missing required keywords: ${text}`);
}
@@ -547,18 +509,14 @@ async function runGatewayModelSuite(params: GatewayModelSuiteParams) {
{ expectFinal: true },
);
if (toolProbe?.status !== "ok") {
throw new Error(
`tool probe failed: status=${String(toolProbe?.status)}`,
);
throw new Error(`tool probe failed: status=${String(toolProbe?.status)}`);
}
const toolText = extractPayloadText(toolProbe?.result);
if (
isEmptyStreamText(toolText) &&
(model.provider === "minimax" || model.provider === "openai-codex")
) {
logProgress(
`${progressLabel}: skip (${model.provider} empty response)`,
);
logProgress(`${progressLabel}: skip (${model.provider} empty response)`);
break;
}
assertNoReasoningTags({
@@ -593,19 +551,14 @@ async function runGatewayModelSuite(params: GatewayModelSuiteParams) {
{ expectFinal: true },
);
if (execReadProbe?.status !== "ok") {
throw new Error(
`exec+read probe failed: status=${String(execReadProbe?.status)}`,
);
throw new Error(`exec+read probe failed: status=${String(execReadProbe?.status)}`);
}
const execReadText = extractPayloadText(execReadProbe?.result);
if (
isEmptyStreamText(execReadText) &&
(model.provider === "minimax" ||
model.provider === "openai-codex")
(model.provider === "minimax" || model.provider === "openai-codex")
) {
logProgress(
`${progressLabel}: skip (${model.provider} empty response)`,
);
logProgress(`${progressLabel}: skip (${model.provider} empty response)`);
break;
}
assertNoReasoningTags({
@@ -652,19 +605,14 @@ async function runGatewayModelSuite(params: GatewayModelSuiteParams) {
// Best-effort: do not fail the whole live suite on flaky image handling.
// (We still keep prompt + tool probes as hard checks.)
if (imageProbe?.status !== "ok") {
logProgress(
`${progressLabel}: image skip (status=${String(imageProbe?.status)})`,
);
logProgress(`${progressLabel}: image skip (status=${String(imageProbe?.status)})`);
} else {
const imageText = extractPayloadText(imageProbe?.result);
if (
isEmptyStreamText(imageText) &&
(model.provider === "minimax" ||
model.provider === "openai-codex")
(model.provider === "minimax" || model.provider === "openai-codex")
) {
logProgress(
`${progressLabel}: image skip (${model.provider} empty response)`,
);
logProgress(`${progressLabel}: image skip (${model.provider} empty response)`);
} else {
assertNoReasoningTags({
text: imageText,
@@ -675,11 +623,9 @@ async function runGatewayModelSuite(params: GatewayModelSuiteParams) {
if (!/\bcat\b/i.test(imageText)) {
logProgress(`${progressLabel}: image skip (missing 'cat')`);
} else {
const candidates =
imageText.toUpperCase().match(/[A-Z0-9]{6,20}/g) ?? [];
const candidates = imageText.toUpperCase().match(/[A-Z0-9]{6,20}/g) ?? [];
const bestDistance = candidates.reduce((best, cand) => {
if (Math.abs(cand.length - imageCode.length) > 2)
return best;
if (Math.abs(cand.length - imageCode.length) > 2) return best;
return Math.min(best, editDistance(cand, imageCode));
}, Number.POSITIVE_INFINITY);
// OCR / image-read flake: allow a small edit distance, but still require the "cat" token above.
@@ -694,8 +640,7 @@ async function runGatewayModelSuite(params: GatewayModelSuiteParams) {
// Regression: tool-call-only turn followed by a user message (OpenAI responses bug class).
if (
(model.provider === "openai" && model.api === "openai-responses") ||
(model.provider === "openai-codex" &&
model.api === "openai-codex-responses")
(model.provider === "openai-codex" && model.api === "openai-codex-responses")
) {
logProgress(`${progressLabel}: tool-only regression`);
const runId2 = randomUUID();
@@ -711,9 +656,7 @@ async function runGatewayModelSuite(params: GatewayModelSuiteParams) {
{ expectFinal: true },
);
if (first?.status !== "ok") {
throw new Error(
`tool-only turn failed: status=${String(first?.status)}`,
);
throw new Error(`tool-only turn failed: status=${String(first?.status)}`);
}
const firstText = extractPayloadText(first?.result);
assertNoReasoningTags({
@@ -735,9 +678,7 @@ async function runGatewayModelSuite(params: GatewayModelSuiteParams) {
{ expectFinal: true },
);
if (second?.status !== "ok") {
throw new Error(
`post-tool message failed: status=${String(second?.status)}`,
);
throw new Error(`post-tool message failed: status=${String(second?.status)}`);
}
const reply = extractPayloadText(second?.result);
assertNoReasoningTags({
@@ -763,14 +704,9 @@ async function runGatewayModelSuite(params: GatewayModelSuiteParams) {
logProgress(`${progressLabel}: rate limit, retrying with next key`);
continue;
}
if (
model.provider === "anthropic" &&
isAnthropicBillingError(message)
) {
if (model.provider === "anthropic" && isAnthropicBillingError(message)) {
if (attempt + 1 < attemptMax) {
logProgress(
`${progressLabel}: billing issue, retrying with next key`,
);
logProgress(`${progressLabel}: billing issue, retrying with next key`);
continue;
}
logProgress(`${progressLabel}: skip (anthropic billing)`);
@@ -781,9 +717,7 @@ async function runGatewayModelSuite(params: GatewayModelSuiteParams) {
isEmptyStreamText(message) &&
attempt + 1 < attemptMax
) {
logProgress(
`${progressLabel}: empty response, retrying with next key`,
);
logProgress(`${progressLabel}: empty response, retrying with next key`);
continue;
}
if (model.provider === "anthropic" && isEmptyStreamText(message)) {
@@ -792,10 +726,7 @@ async function runGatewayModelSuite(params: GatewayModelSuiteParams) {
break;
}
// OpenAI Codex refresh tokens can become single-use; skip instead of failing all live tests.
if (
model.provider === "openai-codex" &&
isRefreshTokenReused(message)
) {
if (model.provider === "openai-codex" && isRefreshTokenReused(message)) {
logProgress(`${progressLabel}: skip (codex refresh token reused)`);
break;
}
@@ -821,9 +752,7 @@ async function runGatewayModelSuite(params: GatewayModelSuiteParams) {
.slice(0, 20)
.map((f) => `- ${f.model}: ${f.error}`)
.join("\n");
throw new Error(
`gateway live model failures (${failures.length}):\n${preview}`,
);
throw new Error(`gateway live model failures (${failures.length}):\n${preview}`);
}
if (skippedCount === total) {
logProgress(`[${params.label}] skipped all models (missing profiles)`);
@@ -868,15 +797,12 @@ describeLive("gateway live (dev agent, profile keys)", () => {
const all = modelRegistry.getAll() as Array<Model<Api>>;
const rawModels = process.env.CLAWDBOT_LIVE_GATEWAY_MODELS?.trim();
const useModern =
!rawModels || rawModels === "modern" || rawModels === "all";
const useModern = !rawModels || rawModels === "modern" || rawModels === "all";
const useExplicit = Boolean(rawModels) && !useModern;
const filter = useExplicit ? parseFilter(rawModels) : null;
const wanted = filter
? all.filter((m) => filter.has(`${m.provider}/${m.id}`))
: all.filter((m) =>
isModernModelRef({ provider: m.provider, id: m.id }),
);
: all.filter((m) => isModernModelRef({ provider: m.provider, id: m.id }));
const candidates: Array<Model<Api>> = [];
for (const model of wanted) {
@@ -902,16 +828,10 @@ describeLive("gateway live (dev agent, profile keys)", () => {
logProgress("[all-models] no API keys found; skipping");
return;
}
logProgress(
`[all-models] selection=${useExplicit ? "explicit" : "modern"}`,
);
const imageCandidates = candidates.filter((m) =>
m.input?.includes("image"),
);
logProgress(`[all-models] selection=${useExplicit ? "explicit" : "modern"}`);
const imageCandidates = candidates.filter((m) => m.input?.includes("image"));
if (imageCandidates.length === 0) {
logProgress(
"[all-models] no image-capable models selected; image probe will be skipped",
);
logProgress("[all-models] no image-capable models selected; image probe will be skipped");
}
await runGatewayModelSuite({
label: "all-models",
@@ -922,13 +842,9 @@ describeLive("gateway live (dev agent, profile keys)", () => {
thinkingLevel: THINKING_LEVEL,
});
const minimaxCandidates = candidates.filter(
(model) => model.provider === "minimax",
);
const minimaxCandidates = candidates.filter((model) => model.provider === "minimax");
if (minimaxCandidates.length === 0) {
logProgress(
"[minimax] no candidates with keys; skipping dual endpoint probes",
);
logProgress("[minimax] no candidates with keys; skipping dual endpoint probes");
return;
}
@@ -948,9 +864,7 @@ describeLive("gateway live (dev agent, profile keys)", () => {
providerOverrides: { minimax: minimaxAnthropic },
});
} else {
logProgress(
"[minimax-anthropic] missing minimax provider config; skipping",
);
logProgress("[minimax-anthropic] missing minimax provider config; skipping");
}
},
20 * 60 * 1000,
@@ -981,10 +895,7 @@ describeLive("gateway live (dev agent, profile keys)", () => {
const agentDir = resolveClawdbotAgentDir();
const authStorage = discoverAuthStorage(agentDir);
const modelRegistry = discoverModels(authStorage, agentDir);
const anthropic = modelRegistry.find(
"anthropic",
"claude-opus-4-5",
) as Model<Api> | null;
const anthropic = modelRegistry.find("anthropic", "claude-opus-4-5") as Model<Api> | null;
const zai = modelRegistry.find("zai", "glm-4.7") as Model<Api> | null;
if (!anthropic || !zai) return;
@@ -1000,10 +911,7 @@ describeLive("gateway live (dev agent, profile keys)", () => {
await fs.mkdir(workspaceDir, { recursive: true });
const nonceA = randomUUID();
const nonceB = randomUUID();
const toolProbePath = path.join(
workspaceDir,
`.clawdbot-live-zai-fallback.${nonceA}.txt`,
);
const toolProbePath = path.join(workspaceDir, `.clawdbot-live-zai-fallback.${nonceA}.txt`);
await fs.writeFile(toolProbePath, `nonceA=${nonceA}\nnonceB=${nonceB}\n`);
const port = await getFreeGatewayPort();
@@ -1044,9 +952,7 @@ describeLive("gateway live (dev agent, profile keys)", () => {
{ expectFinal: true },
);
if (toolProbe?.status !== "ok") {
throw new Error(
`anthropic tool probe failed: status=${String(toolProbe?.status)}`,
);
throw new Error(`anthropic tool probe failed: status=${String(toolProbe?.status)}`);
}
const toolText = extractPayloadText(toolProbe?.result);
assertNoReasoningTags({
@@ -1079,9 +985,7 @@ describeLive("gateway live (dev agent, profile keys)", () => {
{ expectFinal: true },
);
if (followup?.status !== "ok") {
throw new Error(
`zai followup failed: status=${String(followup?.status)}`,
);
throw new Error(`zai followup failed: status=${String(followup?.status)}`);
}
const followupText = extractPayloadText(followup?.result);
assertNoReasoningTags({