chore: apply PR 1495 and update changelog

This commit is contained in:
diegosouzapw
2026-04-21 21:12:29 -03:00
parent 98c5b250b9
commit a7b6dfb06e
204 changed files with 2515 additions and 1652 deletions

View File

@@ -8,17 +8,61 @@
### ✨ New Features
- **feat(providers):** Add ModelScope provider (Chinese AI marketplace) with Kimi K2.5, GLM-5, and Step-3.5-Flash integration. (#1430)
- **feat(providers):** Add ModelScope provider (Chinese AI marketplace) with Kimi K2.5, GLM-5, and Step-3.5-Flash integration. (#1430 — thanks @clousky2020)
- **feat(providers):** Add LM Studio as an OpenAI-compatible local provider for self-hosted model inference.
- **feat(providers):** Add Grok 4.3 thinking model support for xAI web executor requests.
- **feat(core):** Implement provider-level Circuit Breaker to prevent cascading failures across connections, enforcing a 10-minute cooldown after 5 consecutive transient failures. (#1430)
- **feat(core):** Add daily quota exhaustion lock to detect "quota exceeded" signals and lock the specific model until midnight. (#1430)
- **feat(core):** Auto-inject `stream_options.include_usage = true` for OpenAI format streams to guarantee token usage is reported correctly during streaming. (#1423)
- **feat(core):** Add OpenAI Batch Processing API support — submit, monitor, and manage batch jobs through the proxy with full lifecycle tracking.
- **feat(vision-bridge):** Add automatic image description fallback for non-vision models via `VisionBridgeGuardrail` (priority 5). Intercepts image-bearing requests to non-vision models, extracts descriptions via a configurable vision model (default: gpt-4o-mini), and replaces images with text before forwarding. Fails open on any error. (#1476)
- **feat(dashboard):** Introduce real-time model status badges with countdown timers in the provider detail and combo panel interfaces. (#1430)
- **feat(dashboard):** Add Batch/File management data grid to the Dashboard. (#1479)
- **feat(dashboard):** Add Batch/File management data grid with full i18n translations for batch processing workflows. (#1479)
### 🐛 Bug Fixes
- **fix(dashboard):** Correct TOML round-trip corruption in Codex config serializer by dequoting keys and preserving array/boolean structures properly. (#1438)
- **fix(security):** Resolve CodeQL alert 164 (ReDoS in extraction) and 163 (incomplete URL sanitization) (#163, #164)
- **fix(batch):** Add batch item dispatching to specific handlers based on URL to support embeddings and other modalities (#1495 — thanks @hartmark)
- **fix(dashboard):** Correct TOML round-trip corruption in Codex config serializer by dequoting keys and preserving array/boolean structures properly. (#1438 — thanks @benzntech)
- **fix(security):** Resolve CodeQL alert 164 (ReDoS in extraction) and 163 (incomplete URL sanitization). (#163, #164)
- **fix(providers):** Add optional chaining to connection object before accessing `providerSpecificData`, preventing runtime errors when the connection is null/undefined.
- **fix(codex):** Preserve namespace MCP tools forwarded to Codex Responses API, preventing tool name stripping during translation. (#1483)
- **fix(codex):** Deduplicate case-variant `anthropic-version` header in Claude Code patch to prevent duplicate header injection. (#1481)
- **fix(fallback):** Use shared `CircuitBreaker` instead of undefined constants, fixing runtime errors in provider failure handling. (#1485)
- **fix(fallback):** Merge new provider failure threshold fields (`providerFailureThreshold`, `providerFailureWindowMs`, `providerCooldownMs`) into resilience profiles.
- **fix(fallback):** Remove 429 from `PROVIDER_FAILURE_ERROR_CODES` — rate limits are already handled by model-level and account-level locks; including them in the provider-wide circuit breaker caused premature cooldown.
- **fix(sse):** Enable tool calling for GPT OSS and DeepSeek Reasoner models. (#1455)
- **fix(encryption):** Return null on decryption failure to prevent sending encrypted tokens to providers. (#1462)
- **fix(combo):** Resolve cross-provider thinking 400 errors and HTTP clipboard issues during combo routing. (#1444)
- **fix(core):** Resolve skills, memory, and encryption system issues affecting startup and runtime stability. (#1456)
- **fix(core):** Fix model ID parsing for providers with slashes in model names — use `indexOf`/`substring` instead of `split` to handle models like `modelscope/moonshotai/Kimi-K2.5`.
- **fix(core):** Fix reference counting in `ModelStatusContext` — changed `registeredModels` from `Set` to `Map<string, number>` to prevent polling stop when one component unmounts while others still track the same model.
- **fix(security):** Prompt injection guard failures now return an explicit 500 response instead of silently passing through (fail-closed policy).
- **fix(security):** Encryption now derives new keys from a secret-based salt while falling back to the legacy static-salt key during decryption, preserving existing stored credentials.
### ♻️ Refactoring
- **refactor(fallback):** Make provider failure thresholds configurable via `PROVIDER_PROFILES` instead of hardcoded constants, supporting different failure tolerance per provider type. (#1449)
- **refactor(resilience):** Unify resilience controls across the codebase for consistent circuit breaker and fallback behavior. (#1449)
- **refactor(core):** Implement shared path utilities, add custom date formatting, improve type safety, and unify database imports across modules.
- **refactor(security):** Harden backup archive creation by switching to `execFileSync`, validate ACP agent IDs, expand shared CORS handling.
### 🧪 Tests
- **test(vision-bridge):** Add 51 unit tests covering all VisionBridge spec scenarios (VB-S01 through VB-S10), including helper functions for `callVisionModel`, `extractImageParts`, `replaceImageParts`, and `resolveImageAsDataUri`.
- **test(batch-api):** Isolate batch API unit tests with temp `DATA_DIR` to prevent schema state collisions.
- **test(settings-api):** Add test harness with `createSettingsApiHarness` function for proper temp directory setup and storage reset between tests.
- **test(security):** Update prompt injection test for fail-closed policy alignment.
- **test(core):** Restore local test fixes for encryption and resilience modules.
### 📚 Documentation
- **docs:** Add Arch Linux AUR install notes for community package support. (#1478)
- **docs(i18n):** Improve Ukrainian (uk-UA) translation quality — full Ukrainian translation for README, SECURITY, A2A-SERVER, API_REFERENCE, AUTO-COMBO, and USER_GUIDE documents. Fix mixed Latin/Cyrillic typos, translate model table entries, and standardize section headers.
### 📦 Dependencies
- **deps:** Bump the development group with 4 updates. (#1464)
- **deps:** Bump the production group with 4 updates. (#1463)
---

View File

@@ -45,7 +45,8 @@
"files": [
"main.js",
"preload.js",
"package.json"
"package.json",
"node_modules/**/*"
],
"extraResources": [
{

View File

@@ -339,12 +339,14 @@ function normalizeCodexTools(body: Record<string, unknown>): void {
// Preserve namespace tools (MCP tool groups used by Codex/OpenAI Responses API).
// Codex API supports them natively; register sub-tool names for tool_choice validation.
if (tool.type === "namespace" && Array.isArray(tool.tools)) {
for (const st of tool.tools as unknown[]) {
if (st && typeof st === "object" && !Array.isArray(st)) {
const subTool = st as Record<string, unknown>;
const name = typeof subTool.name === "string" ? subTool.name.trim() : "";
if (name) validToolNames.add(name);
if (tool.type === "namespace") {
if (Array.isArray(tool.tools)) {
for (const st of tool.tools as unknown[]) {
if (st && typeof st === "object" && !Array.isArray(st)) {
const subTool = st as Record<string, unknown>;
const name = typeof subTool.name === "string" ? subTool.name.trim() : "";
if (name) validToolNames.add(name);
}
}
}
return true;

View File

@@ -1,24 +1,31 @@
import { v4 as uuidv4 } from "uuid";
import {
createFile,
deleteFile,
getApiKeyById,
getBatch,
getFileContent,
getPendingBatches,
getTerminalBatches,
listFiles,
updateBatch,
getFileContent,
createFile,
getApiKeyById,
getBatch,
listFiles,
deleteFile,
updateFileStatus,
} from "@/lib/localDb";
import { handleChat } from "@/sse/handlers/chat";
import { POST as handleEmbeddingsRoute } from "@/app/api/v1/embeddings/route";
import { POST as handleModerationsRoute } from "@/app/api/v1/moderations/route";
import { POST as handleImagesGenerationsRoute } from "@/app/api/v1/images/generations/route";
import { POST as handleVideosGenerationsRoute } from "@/app/api/v1/videos/generations/route";
import type { BatchRecord } from "@/lib/localDb";
import { dispatchBatchApiRequest } from "@/lib/batches/dispatch";
import type { SupportedBatchEndpoint } from "@/shared/constants/batchEndpoints";
let isProcessing = false;
let pollInterval: NodeJS.Timeout | null = null;
const DEFAULT_BATCH_WINDOW_SECONDS = 24 * 60 * 60;
interface BatchRequestItem {
body: Record<string, unknown>;
customId: string | null;
lineNumber: number;
method: "POST";
url: SupportedBatchEndpoint;
}
export function initBatchProcessor() {
if (pollInterval) return pollInterval;
@@ -91,39 +98,125 @@ export async function processPendingBatches() {
await cleanupExpiredBatches();
}
function parseBatchWindowSeconds(window: string | null | undefined): number {
if (!window) return DEFAULT_BATCH_WINDOW_SECONDS;
const match = /^(\d+)([hdm])$/.exec(window);
if (!match) return DEFAULT_BATCH_WINDOW_SECONDS;
const value = Number.parseInt(match[1], 10);
const unit = match[2];
if (unit === "h") return value * 3600;
if (unit === "d") return value * 86400;
if (unit === "m") return value * 60;
return DEFAULT_BATCH_WINDOW_SECONDS;
}
function getBatchOutputExpiresAt(batch: BatchRecord): number | null {
if (
batch.outputExpiresAfterAnchor === "created_at" &&
typeof batch.outputExpiresAfterSeconds === "number" &&
batch.outputExpiresAfterSeconds > 0
) {
return batch.createdAt + batch.outputExpiresAfterSeconds;
}
const completionTime =
batch.completedAt || batch.failedAt || batch.cancelledAt || batch.expiredAt;
if (!completionTime) return null;
return completionTime + parseBatchWindowSeconds(batch.completionWindow);
}
function resolveBatchApiKeyValue(batch: Pick<BatchRecord, "apiKeyId">, apiKeyRow: any) {
if (typeof apiKeyRow?.key === "string" && apiKeyRow.key.length > 0) {
return apiKeyRow.key;
}
if (batch.apiKeyId === "env-key") {
return process.env.OMNIROUTE_API_KEY || process.env.ROUTER_API_KEY || null;
}
return null;
}
function parseBatchItems(
content: Buffer,
batchEndpoint: SupportedBatchEndpoint
): { items: BatchRequestItem[]; error: null } | { items: null; error: string } {
const lines = content
.toString()
.split("\n")
.map((line) => line.trim())
.filter(Boolean);
const items: BatchRequestItem[] = [];
for (const [index, line] of lines.entries()) {
let parsed: Record<string, unknown>;
try {
parsed = JSON.parse(line);
} catch {
return { items: null, error: `Line ${index + 1} is not valid JSON` };
}
const method = String(parsed.method || "POST").toUpperCase();
const url = parsed.url;
const body = parsed.body;
if (method !== "POST") {
return {
items: null,
error: `Line ${index + 1} uses unsupported method ${method}; only POST is supported`,
};
}
if (url !== batchEndpoint) {
return {
items: null,
error: `Line ${index + 1} url ${String(url)} does not match batch endpoint ${batchEndpoint}`,
};
}
if (!body || typeof body !== "object" || Array.isArray(body)) {
return { items: null, error: `Line ${index + 1} must include a JSON object body` };
}
items.push({
body: body as Record<string, unknown>,
customId: typeof parsed.custom_id === "string" ? parsed.custom_id : null,
lineNumber: index + 1,
method: "POST",
url: batchEndpoint,
});
}
return { items, error: null };
}
async function cleanupExpiredBatches() {
try {
const now = Math.floor(Date.now() / 1000);
const batches = getTerminalBatches();
const parseWindow = (window: string): number => {
if (!window) return 86400;
const match = new RegExp(/^(\d+)([hdm])$/).exec(window);
if (!match) return 86400;
const val = Number.parseInt(match[1]);
const unit = match[2];
if (unit === "h") return val * 3600;
if (unit === "d") return val * 86400;
if (unit === "m") return val * 60;
return 86400;
};
// Delete files for terminal batches that have exceeded their completion window
for (const batch of batches) {
const windowSeconds = parseWindow(batch.completionWindow);
const completionTime =
batch.completedAt || batch.failedAt || batch.cancelledAt || batch.expiredAt;
if (completionTime && now - completionTime > windowSeconds) {
if (batch.inputFileId) deleteFile(batch.inputFileId);
if (batch.outputFileId) deleteFile(batch.outputFileId);
if (batch.errorFileId) deleteFile(batch.errorFileId);
const inputExpiresAt =
completionTime && batch.inputFileId
? completionTime + parseBatchWindowSeconds(batch.completionWindow)
: null;
const outputExpiresAt = getBatchOutputExpiresAt(batch);
if (batch.inputFileId && inputExpiresAt && now > inputExpiresAt) {
deleteFile(batch.inputFileId);
}
if (batch.outputFileId && outputExpiresAt && now > outputExpiresAt) {
deleteFile(batch.outputFileId);
}
if (batch.errorFileId && outputExpiresAt && now > outputExpiresAt) {
deleteFile(batch.errorFileId);
}
}
// Expire validating batches that have exceeded their completion window
for (const batch of getPendingBatches()) {
if (batch.status === "validating") {
const windowSeconds = parseWindow(batch.completionWindow);
const windowSeconds = parseBatchWindowSeconds(batch.completionWindow);
if (now - batch.createdAt > windowSeconds) {
updateBatch(batch.id, { status: "expired", expiredAt: now });
}
@@ -157,8 +250,13 @@ async function startBatch(batch: any) {
}
try {
const lines = content.toString().split("\n").filter((l) => l.trim());
const total = lines.length;
const parsedItems = parseBatchItems(content, batch.endpoint);
if (parsedItems.error) {
updateFileStatus(batch.inputFileId, "processed");
failBatch(batch.id, parsedItems.error);
return;
}
const total = parsedItems.items.length;
updateFileStatus(batch.inputFileId, "validating");
updateBatch(batch.id, {
@@ -169,7 +267,7 @@ async function startBatch(batch: any) {
// Fire-and-forget: process items in the background so the poll loop isn't blocked.
// isProcessing prevents a second poll tick from overlapping.
processBatchItems(batch, lines).catch((err) => {
processBatchItems(batch, parsedItems.items).catch((err) => {
console.error(`[BATCH] Critical error in processBatchItems for ${batch.id}:`, err);
failBatch(batch.id, String(err));
});
@@ -179,50 +277,7 @@ async function startBatch(batch: any) {
}
}
/**
* Dispatch a single batch item to the correct handler based on the URL.
* Each batch item may target a different API endpoint (chat, embeddings, etc.).
*/
async function dispatchBatchItem(
url: string,
headers: Headers,
body: Record<string, unknown>
): Promise<Response> {
const request = new Request(`http://localhost${url}`, {
method: "POST",
headers,
body: JSON.stringify(body),
});
switch (url) {
case "/v1/embeddings":
return handleEmbeddingsRoute(request);
case "/v1/moderations":
return handleModerationsRoute(request);
case "/v1/images/generations":
case "/v1/images/edits":
return handleImagesGenerationsRoute(request);
case "/v1/videos":
case "/v1/videos/generations":
return handleVideosGenerationsRoute(request);
// /v1/chat/completions, /v1/completions, /v1/responses
default: {
return handleChat(
new Request(`http://localhost${url}`, {
method: "POST",
headers,
body: JSON.stringify({
...body,
// BATCH-SPECIFIC: Force stream: false — batches don't support SSE responses
stream: body.stream ?? false,
}),
})
);
}
}
}
async function processBatchItems(batch: any, lines: string[]) {
async function processBatchItems(batch: BatchRecord, items: BatchRequestItem[]) {
const results: any[] = [];
const errors: any[] = [];
let completedCount = 0;
@@ -233,8 +288,9 @@ async function processBatchItems(batch: any, lines: string[]) {
let usedModel = batch.model || null;
const apiKeyRow = batch.apiKeyId ? await getApiKeyById(batch.apiKeyId) : null;
const apiKeyValue = resolveBatchApiKeyValue(batch, apiKeyRow);
for (const line of lines) {
for (const item of items) {
// Check if cancelled mid-process
const current = getBatch(batch.id);
if (!current || current.status === "cancelling" || current.status === "cancelled") {
@@ -242,16 +298,13 @@ async function processBatchItems(batch: any, lines: string[]) {
}
try {
const item = JSON.parse(line);
const { custom_id: customId, url, body } = item;
const headers = new Headers();
if (apiKeyRow?.key) {
headers.set("Authorization", `Bearer ${apiKeyRow.key}`);
}
headers.set("Content-Type", "application/json");
const response = await dispatchBatchItem(url, headers, body);
// BATCH-SPECIFIC: Force stream: false — batches don't support SSE responses
const batchItemBody = { ...item.body, stream: false };
const response = await dispatchBatchApiRequest({
endpoint: item.url,
body: batchItemBody,
apiKey: apiKeyValue,
});
let responseData: { error: any; id?: any; usage?: any; model?: any };
let statusCode = 200;
@@ -266,7 +319,9 @@ async function processBatchItems(batch: any, lines: string[]) {
try {
responseData = JSON.parse(text);
} catch {
responseData = { error: { message: text || "Unknown error", type: "invalid_response" } };
responseData = {
error: { message: text || "Unknown error", type: "invalid_response" },
};
}
}
} else {
@@ -278,7 +333,7 @@ async function processBatchItems(batch: any, lines: string[]) {
results.push({
id: requestId,
custom_id: customId,
custom_id: item.customId,
response: {
status_code: statusCode,
request_id: responseData?.id || "req_unknown",
@@ -303,14 +358,8 @@ async function processBatchItems(batch: any, lines: string[]) {
}
} catch (err) {
console.error(`[BATCH] Item failed in ${batch.id}:`, err);
let customId = "unknown";
try {
customId = JSON.parse(line).custom_id || "unknown";
} catch {
// line was malformed JSON
}
errors.push({
custom_id: customId,
custom_id: item.customId || `line-${item.lineNumber}`,
error: err instanceof Error ? err.message : String(err),
});
failedCount++;
@@ -365,9 +414,8 @@ async function finalizeBatch(batchId: string, results: any[], itemsWithErrors: a
}
let outputFileId: string | null = null;
const successes = results.filter(
(r) => r.response.status_code < 400 && !r.response.body?.error
);
const outputExpiresAt = current ? getBatchOutputExpiresAt(current) : null;
const successes = results.filter((r) => r.response.status_code < 400 && !r.response.body?.error);
if (successes.length > 0) {
const outputContent = successes.map((r) => JSON.stringify(r)).join("\n");
const file = createFile({
@@ -377,14 +425,13 @@ async function finalizeBatch(batchId: string, results: any[], itemsWithErrors: a
content: Buffer.from(outputContent),
apiKeyId: current?.apiKeyId,
status: "completed",
expiresAt: outputExpiresAt,
});
outputFileId = file.id;
}
let errorFileId: string | null = null;
const failures = results.filter(
(r) => r.response.status_code >= 400 || r.response.body?.error
);
const failures = results.filter((r) => r.response.status_code >= 400 || r.response.body?.error);
const allFailures = [
...failures,
...itemsWithErrors.map((e) => ({
@@ -404,6 +451,7 @@ async function finalizeBatch(batchId: string, results: any[], itemsWithErrors: a
content: Buffer.from(errorContent),
apiKeyId: current?.apiKeyId,
status: "completed",
expiresAt: outputExpiresAt,
});
errorFileId = file.id;
}

View File

@@ -1982,7 +1982,7 @@ async function handleRoundRobinCombo({
}
}
if (isProviderBreakerOpenResponse(result, errorBody)) {
if (isProviderBreakerOpenResponse(result, errorBody as any)) {
lastError = errorText || String(result.status);
if (!lastStatus) lastStatus = result.status;
if (offset > 0) fallbackCount++;

View File

@@ -1,12 +1,16 @@
"use server";
import { NextResponse } from "next/server";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import { getMitmAlias, setMitmAliasAll } from "@/models";
import { cliMitmAliasUpdateSchema } from "@/shared/validation/schemas";
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
// GET - Get MITM aliases for a tool
export async function GET(request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const { searchParams } = new URL(request.url);
const toolName = searchParams.get("tool");
@@ -20,6 +24,9 @@ export async function GET(request) {
// PUT - Save MITM aliases for a specific tool
export async function PUT(request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();

View File

@@ -3,12 +3,16 @@
export const runtime = "nodejs";
import { NextResponse } from "next/server";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import { cliMitmStartSchema, cliMitmStopSchema } from "@/shared/validation/schemas";
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
import { resolveApiKey } from "@/shared/services/apiKeyResolver";
// GET - Check MITM status
export async function GET() {
export async function GET(request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const { getMitmStatus, getCachedPassword } = await import("@/mitm/manager");
const status = await getMitmStatus();
@@ -27,6 +31,9 @@ export async function GET() {
// POST - Start MITM proxy
export async function POST(request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();
@@ -81,6 +88,9 @@ export async function POST(request) {
// DELETE - Stop MITM proxy
export async function DELETE(request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();

View File

@@ -1,6 +1,7 @@
"use server";
import { NextResponse } from "next/server";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import { listBackups, restoreBackup, deleteBackup } from "@/shared/services/backupService";
import { ensureCliConfigWriteAllowed } from "@/shared/services/cliRuntime";
import { cliBackupMutationSchema } from "@/shared/validation/schemas";
@@ -10,6 +11,9 @@ const VALID_TOOLS = ["claude", "codex", "droid", "openclaw", "cline", "kilo", "q
// GET /api/cli-tools/backups?tool=claude — list backups
export async function GET(request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const { searchParams } = new URL(request.url);
const tool = searchParams.get("tool") || searchParams.get("toolId");
@@ -37,6 +41,9 @@ export async function GET(request) {
// POST /api/cli-tools/backups { tool, backupId } — restore a backup
export async function POST(request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();
@@ -86,6 +93,9 @@ export async function POST(request) {
// DELETE /api/cli-tools/backups { tool, backupId } — delete a backup
export async function DELETE(request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();

View File

@@ -3,6 +3,7 @@
import { NextResponse } from "next/server";
import fs from "fs/promises";
import path from "path";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import {
ensureCliConfigWriteAllowed,
getCliPrimaryConfigPath,
@@ -32,7 +33,10 @@ const readSettings = async () => {
};
// GET - Check claude CLI and read current settings
export async function GET() {
export async function GET(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const runtime = await getCliRuntimeStatus("claude");
@@ -74,6 +78,9 @@ export async function GET() {
// POST - Backup old fields and write new settings
export async function POST(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();
@@ -186,7 +193,10 @@ const RESET_ENV_KEYS = [
];
// DELETE - Reset settings (remove env fields)
export async function DELETE() {
export async function DELETE(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const writeGuard = ensureCliConfigWriteAllowed();
if (writeGuard) {

View File

@@ -4,6 +4,7 @@ import { NextResponse } from "next/server";
import fs from "fs/promises";
import path from "path";
import os from "os";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import { ensureCliConfigWriteAllowed, getCliRuntimeStatus } from "@/shared/services/cliRuntime";
import { createBackup } from "@/shared/services/backupService";
import { saveCliToolLastConfigured, deleteCliToolLastConfigured } from "@/lib/db/cliToolState";
@@ -52,7 +53,10 @@ const hasOmniRouteConfig = (globalState: any) => {
};
// GET - Check cline CLI and read current settings
export async function GET() {
export async function GET(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const runtime = await getCliRuntimeStatus("cline");
@@ -101,6 +105,9 @@ export async function GET() {
// POST - Configure Cline to use OmniRoute as OpenAI-compatible provider
export async function POST(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();
@@ -193,7 +200,10 @@ export async function POST(request: Request) {
}
// DELETE - Remove OmniRoute OpenAI-compatible provider config
export async function DELETE() {
export async function DELETE(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const writeGuard = ensureCliConfigWriteAllowed();
if (writeGuard) {

View File

@@ -3,6 +3,7 @@
import { NextResponse } from "next/server";
import fs from "fs/promises";
import path from "path";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import { ensureCliConfigWriteAllowed, getCliConfigPaths } from "@/shared/services/cliRuntime";
import { resolveDataDir } from "@/lib/dataPaths";
import { codexProfileIdSchema, codexProfileNameSchema } from "@/shared/validation/schemas";
@@ -52,7 +53,10 @@ function extractAuthLabel(authJson) {
}
// GET - List all saved profiles
export async function GET() {
export async function GET(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
await ensureProfilesDir();
@@ -94,6 +98,9 @@ export async function GET() {
// POST - Save current config as a named profile
export async function POST(request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();
@@ -180,6 +187,9 @@ export async function POST(request) {
// PUT - Activate a saved profile (restore its config + auth)
export async function PUT(request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();
@@ -251,6 +261,9 @@ export async function PUT(request) {
// DELETE - Remove a saved profile
export async function DELETE(request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();

View File

@@ -3,6 +3,7 @@
import { NextResponse } from "next/server";
import fs from "fs/promises";
import path from "path";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import {
ensureCliConfigWriteAllowed,
getCliConfigPaths,
@@ -120,7 +121,10 @@ const hasOmniRouteConfig = (config: string | null) => {
};
// GET - Check codex CLI and read current settings
export async function GET() {
export async function GET(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const runtime = await getCliRuntimeStatus("codex");
@@ -161,6 +165,9 @@ export async function GET() {
// POST - Update OmniRoute settings (merge with existing config)
export async function POST(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();
@@ -301,7 +308,10 @@ export async function POST(request: Request) {
}
// DELETE - Remove OmniRoute settings only (keep other settings)
export async function DELETE() {
export async function DELETE(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const writeGuard = ensureCliConfigWriteAllowed();
if (writeGuard) {

View File

@@ -3,6 +3,7 @@
import { NextResponse } from "next/server";
import fs from "fs/promises";
import path from "path";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import {
ensureCliConfigWriteAllowed,
getCliPrimaryConfigPath,
@@ -36,7 +37,10 @@ const hasOmniRouteConfig = (settings: any) => {
};
// GET - Check droid CLI and read current settings
export async function GET() {
export async function GET(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const runtime = await getCliRuntimeStatus("droid");
@@ -77,6 +81,9 @@ export async function GET() {
// POST - Update OmniRoute customModels (merge with existing settings)
export async function POST(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();
@@ -174,7 +181,10 @@ export async function POST(request: Request) {
}
// DELETE - Remove OmniRoute customModels only (keep other settings)
export async function DELETE() {
export async function DELETE(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const writeGuard = ensureCliConfigWriteAllowed();
if (writeGuard) {

View File

@@ -2,6 +2,7 @@ import { NextResponse } from "next/server";
import fs from "fs/promises";
import path from "path";
import os from "os";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import { getRuntimePorts } from "@/lib/runtime/ports";
import { getOpenCodeConfigPath } from "@/shared/services/cliRuntime";
import { mergeOpenCodeConfig } from "@/shared/services/opencodeConfig";
@@ -16,6 +17,9 @@ import { resolveApiKey } from "@/shared/services/apiKeyResolver";
* Currently supports: continue, opencode
*/
export async function POST(request, { params }) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();

View File

@@ -4,6 +4,7 @@ import { NextResponse } from "next/server";
import fs from "fs/promises";
import path from "path";
import os from "os";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import { ensureCliConfigWriteAllowed, getCliRuntimeStatus } from "@/shared/services/cliRuntime";
import { createBackup } from "@/shared/services/backupService";
import { saveCliToolLastConfigured, deleteCliToolLastConfigured } from "@/lib/db/cliToolState";
@@ -38,7 +39,10 @@ const hasOmniRouteConfig = (auth) => {
};
// GET - Check kilo CLI and read current settings
export async function GET() {
export async function GET(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const runtime = await getCliRuntimeStatus("kilo");
@@ -109,6 +113,9 @@ export async function GET() {
// POST - Configure Kilo Code to use OmniRoute as OpenAI-compatible provider
export async function POST(request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();
@@ -217,7 +224,10 @@ export async function POST(request) {
}
// DELETE - Remove OmniRoute config from Kilo
export async function DELETE() {
export async function DELETE(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const writeGuard = ensureCliConfigWriteAllowed();
if (writeGuard) {

View File

@@ -3,6 +3,7 @@
import { NextResponse } from "next/server";
import fs from "fs/promises";
import path from "path";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import {
ensureCliConfigWriteAllowed,
getCliPrimaryConfigPath,
@@ -36,7 +37,10 @@ const hasOmniRouteConfig = (settings: any) => {
};
// GET - Check openclaw CLI and read current settings
export async function GET() {
export async function GET(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const runtime = await getCliRuntimeStatus("openclaw");
@@ -77,6 +81,9 @@ export async function GET() {
// POST - Update OmniRoute settings (merge with existing settings)
export async function POST(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();
@@ -174,7 +181,10 @@ export async function POST(request: Request) {
}
// DELETE - Remove OmniRoute settings only (keep other settings)
export async function DELETE() {
export async function DELETE(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const writeGuard = ensureCliConfigWriteAllowed();
if (writeGuard) {

View File

@@ -5,12 +5,16 @@
*/
import { NextResponse } from "next/server";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import { getComboModelProvider } from "@/lib/combos/steps";
import { resolveOmniRouteBaseUrl } from "@/shared/utils/resolveOmniRouteBaseUrl";
const OMNIROUTE_BASE_URL = resolveOmniRouteBaseUrl();
export async function GET() {
export async function GET(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
// Fetch current health and combos to determine best provider ordering
const [healthRes, combosRes] = await Promise.allSettled([

View File

@@ -4,6 +4,7 @@ import { NextResponse } from "next/server";
import fs from "fs/promises";
import path from "path";
import os from "os";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import {
ensureCliConfigWriteAllowed,
getCliPrimaryConfigPath,
@@ -64,7 +65,10 @@ const hasOmniRouteConfig = (settings: any) => {
};
// GET - Check Qwen CLI and read current settings
export async function GET() {
export async function GET(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const runtime = await getCliRuntimeStatus("qwen");
@@ -106,6 +110,9 @@ export async function GET() {
// POST - Write OmniRoute config to settings.json + .env
export async function POST(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();
@@ -272,7 +279,10 @@ export async function POST(request: Request) {
}
// DELETE - Remove OmniRoute config from settings.json and .env
export async function DELETE() {
export async function DELETE(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const writeGuard = ensureCliConfigWriteAllowed();
if (writeGuard) {

View File

@@ -1,13 +1,17 @@
"use server";
import { NextResponse } from "next/server";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import {
CLI_TOOL_IDS,
getCliPrimaryConfigPath,
getCliRuntimeStatus,
} from "@/shared/services/cliRuntime";
export async function GET(_request, { params }) {
export async function GET(request, { params }) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const { toolId } = await params;
const normalizedToolId = String(toolId || "")

View File

@@ -2,6 +2,7 @@
import { NextResponse } from "next/server";
import fs from "fs/promises";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import {
getCliRuntimeStatus,
CLI_TOOL_IDS,
@@ -99,7 +100,10 @@ async function checkToolConfigStatus(toolId: string): Promise<string> {
* Returns runtime + config status for all CLI tools in one batch call.
* Used by the CLI Tools page to show status badges in collapsed state.
*/
export async function GET() {
export async function GET(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const statuses = {};

View File

@@ -0,0 +1,50 @@
import { NextResponse } from "next/server";
import { getApiKeyMetadata } from "@/lib/localDb";
import { CORS_HEADERS } from "@/shared/utils/cors";
import { extractApiKey, isValidApiKey } from "@/sse/services/auth";
export interface ApiKeyRequestScope {
apiKey: string | null;
apiKeyId: string | null;
apiKeyMetadata: Awaited<ReturnType<typeof getApiKeyMetadata>>;
rejection: Response | null;
}
export async function getApiKeyRequestScope(request: Request): Promise<ApiKeyRequestScope> {
const apiKey = extractApiKey(request);
if (process.env.REQUIRE_API_KEY === "true") {
if (!apiKey) {
return {
apiKey: null,
apiKeyId: null,
apiKeyMetadata: null,
rejection: NextResponse.json(
{ error: { message: "Missing API key", type: "invalid_request_error" } },
{ status: 401, headers: CORS_HEADERS }
),
};
}
if (!(await isValidApiKey(apiKey))) {
return {
apiKey: null,
apiKeyId: null,
apiKeyMetadata: null,
rejection: NextResponse.json(
{ error: { message: "Invalid API key", type: "invalid_request_error" } },
{ status: 401, headers: CORS_HEADERS }
),
};
}
}
const apiKeyMetadata = await getApiKeyMetadata(apiKey);
return {
apiKey,
apiKeyId: apiKeyMetadata?.id || null,
apiKeyMetadata,
rejection: null,
};
}

View File

@@ -1,71 +1,73 @@
import { CORS_HEADERS } from "@/shared/utils/cors";
import { extractApiKey } from "@/sse/services/auth";
import { getBatch, updateBatch, getApiKeyMetadata } from "@/lib/localDb";
import { getBatch, updateBatch } from "@/lib/localDb";
import { NextResponse } from "next/server";
import { getApiKeyRequestScope } from "@/app/api/v1/_helpers/apiKeyScope";
function formatBatchResponse(batch: any) {
return {
id: batch.id,
object: "batch",
endpoint: batch.endpoint,
errors: batch.errors || null,
input_file_id: batch.inputFileId,
completion_window: batch.completionWindow,
status: batch.status,
output_file_id: batch.outputFileId || null,
error_file_id: batch.errorFileId || null,
created_at: batch.createdAt,
in_progress_at: batch.inProgressAt || null,
expires_at: batch.expiresAt || null,
finalizing_at: batch.finalizingAt || null,
completed_at: batch.completedAt || null,
failed_at: batch.failedAt || null,
expired_at: batch.expiredAt || null,
cancelling_at: batch.cancellingAt || null,
cancelled_at: batch.cancelledAt || null,
request_counts: {
total: batch.requestCountsTotal || 0,
completed: batch.requestCountsCompleted || 0,
failed: batch.requestCountsFailed || 0,
},
metadata: batch.metadata || null,
model: batch.model || null,
usage: batch.usage || null,
};
return {
id: batch.id,
object: "batch",
endpoint: batch.endpoint,
errors: batch.errors || null,
input_file_id: batch.inputFileId,
completion_window: batch.completionWindow,
status: batch.status,
output_file_id: batch.outputFileId || null,
error_file_id: batch.errorFileId || null,
created_at: batch.createdAt,
in_progress_at: batch.inProgressAt || null,
expires_at: batch.expiresAt || null,
finalizing_at: batch.finalizingAt || null,
completed_at: batch.completedAt || null,
failed_at: batch.failedAt || null,
expired_at: batch.expiredAt || null,
cancelling_at: batch.cancellingAt || null,
cancelled_at: batch.cancelledAt || null,
request_counts: {
total: batch.requestCountsTotal || 0,
completed: batch.requestCountsCompleted || 0,
failed: batch.requestCountsFailed || 0,
},
metadata: batch.metadata || null,
model: batch.model || null,
usage: batch.usage || null,
};
}
export async function POST(request: Request, { params }: { params: Promise<{ id: string }> }) {
const apiKey = extractApiKey(request);
const apiKeyMetadata = await getApiKeyMetadata(apiKey);
const apiKeyId = apiKeyMetadata?.id || null;
const scope = await getApiKeyRequestScope(request);
if (scope.rejection) return scope.rejection;
const apiKeyId = scope.apiKeyId;
const { id } = await params;
const batch = getBatch(id);
const { id } = await params;
const batch = getBatch(id);
if (!batch || (batch.apiKeyId !== null && batch.apiKeyId !== apiKeyId)) {
return NextResponse.json(
{ error: { message: "Batch not found", type: "invalid_request_error" } },
{ status: 404, headers: CORS_HEADERS }
);
}
if (!batch || (batch.apiKeyId !== null && batch.apiKeyId !== apiKeyId)) {
return NextResponse.json(
{ error: { message: "Batch not found", type: "invalid_request_error" } },
{ status: 404, headers: CORS_HEADERS }
);
}
if (["completed", "failed", "cancelled", "expired"].includes(batch.status)) {
return NextResponse.json(
{ error: { message: `Batch ${id} is already ${batch.status}`, type: "invalid_request_error" } },
{ status: 400, headers: CORS_HEADERS }
);
}
if (["completed", "failed", "cancelled", "expired"].includes(batch.status)) {
return NextResponse.json(
{
error: { message: `Batch ${id} is already ${batch.status}`, type: "invalid_request_error" },
},
{ status: 400, headers: CORS_HEADERS }
);
}
if (batch.status === "cancelling") {
return NextResponse.json(formatBatchResponse(batch), { headers: CORS_HEADERS });
}
if (batch.status === "cancelling") {
return NextResponse.json(formatBatchResponse(batch), { headers: CORS_HEADERS });
}
updateBatch(id, {
status: "cancelling",
cancellingAt: Math.floor(Date.now() / 1000)
});
updateBatch(id, {
status: "cancelling",
cancellingAt: Math.floor(Date.now() / 1000),
});
const updatedBatch = getBatch(id);
const updatedBatch = getBatch(id);
return NextResponse.json(formatBatchResponse(updatedBatch), { headers: CORS_HEADERS });
return NextResponse.json(formatBatchResponse(updatedBatch), { headers: CORS_HEADERS });
}

View File

@@ -1,53 +1,53 @@
import { CORS_HEADERS } from "@/shared/utils/cors";
import { extractApiKey } from "@/sse/services/auth";
import { getBatch, getApiKeyMetadata } from "@/lib/localDb";
import { getBatch } from "@/lib/localDb";
import { NextResponse } from "next/server";
import { getApiKeyRequestScope } from "@/app/api/v1/_helpers/apiKeyScope";
function formatBatchResponse(batch: any) {
return {
id: batch.id,
object: "batch",
endpoint: batch.endpoint,
errors: batch.errors || null,
input_file_id: batch.inputFileId,
completion_window: batch.completionWindow,
status: batch.status,
output_file_id: batch.outputFileId || null,
error_file_id: batch.errorFileId || null,
created_at: batch.createdAt,
in_progress_at: batch.inProgressAt || null,
expires_at: batch.expiresAt || null,
finalizing_at: batch.finalizingAt || null,
completed_at: batch.completedAt || null,
failed_at: batch.failedAt || null,
expired_at: batch.expiredAt || null,
cancelling_at: batch.cancellingAt || null,
cancelled_at: batch.cancelledAt || null,
request_counts: {
total: batch.requestCountsTotal || 0,
completed: batch.requestCountsCompleted || 0,
failed: batch.requestCountsFailed || 0,
},
metadata: batch.metadata || null,
model: batch.model || null,
usage: batch.usage || null,
};
return {
id: batch.id,
object: "batch",
endpoint: batch.endpoint,
errors: batch.errors || null,
input_file_id: batch.inputFileId,
completion_window: batch.completionWindow,
status: batch.status,
output_file_id: batch.outputFileId || null,
error_file_id: batch.errorFileId || null,
created_at: batch.createdAt,
in_progress_at: batch.inProgressAt || null,
expires_at: batch.expiresAt || null,
finalizing_at: batch.finalizingAt || null,
completed_at: batch.completedAt || null,
failed_at: batch.failedAt || null,
expired_at: batch.expiredAt || null,
cancelling_at: batch.cancellingAt || null,
cancelled_at: batch.cancelledAt || null,
request_counts: {
total: batch.requestCountsTotal || 0,
completed: batch.requestCountsCompleted || 0,
failed: batch.requestCountsFailed || 0,
},
metadata: batch.metadata || null,
model: batch.model || null,
usage: batch.usage || null,
};
}
export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {
const apiKey = extractApiKey(request);
const apiKeyMetadata = await getApiKeyMetadata(apiKey);
const apiKeyId = apiKeyMetadata?.id || null;
const scope = await getApiKeyRequestScope(request);
if (scope.rejection) return scope.rejection;
const apiKeyId = scope.apiKeyId;
const { id } = await params;
const batch = getBatch(id);
const { id } = await params;
const batch = getBatch(id);
if (!batch || (batch.apiKeyId !== null && batch.apiKeyId !== apiKeyId)) {
return NextResponse.json(
{ error: { message: "Batch not found", type: "invalid_request_error" } },
{ status: 404, headers: CORS_HEADERS }
);
}
if (!batch || (batch.apiKeyId !== null && batch.apiKeyId !== apiKeyId)) {
return NextResponse.json(
{ error: { message: "Batch not found", type: "invalid_request_error" } },
{ status: 404, headers: CORS_HEADERS }
);
}
return NextResponse.json(formatBatchResponse(batch), { headers: CORS_HEADERS });
return NextResponse.json(formatBatchResponse(batch), { headers: CORS_HEADERS });
}

View File

@@ -1,44 +1,44 @@
import { CORS_HEADERS } from "@/shared/utils/cors";
import { extractApiKey } from "@/sse/services/auth";
import { createBatch, getApiKeyMetadata, getFile, listBatches } from "@/lib/localDb";
import { createBatch, getFile, listBatches } from "@/lib/localDb";
import { v1BatchCreateSchema } from "@/shared/validation/schemas";
import { NextResponse } from "next/server";
import { getApiKeyRequestScope } from "@/app/api/v1/_helpers/apiKeyScope";
function formatBatchResponse(batch: any) {
return {
id: batch.id,
object: "batch",
endpoint: batch.endpoint,
errors: batch.errors || null,
input_file_id: batch.inputFileId,
completion_window: batch.completionWindow,
status: batch.status,
output_file_id: batch.outputFileId || null,
error_file_id: batch.errorFileId || null,
created_at: batch.createdAt,
in_progress_at: batch.inProgressAt || null,
expires_at: batch.expiresAt || null,
finalizing_at: batch.finalizingAt || null,
completed_at: batch.completedAt || null,
failed_at: batch.failedAt || null,
expired_at: batch.expiredAt || null,
cancelling_at: batch.cancellingAt || null,
cancelled_at: batch.cancelledAt || null,
request_counts: {
total: batch.requestCountsTotal || 0,
completed: batch.requestCountsCompleted || 0,
failed: batch.requestCountsFailed || 0,
},
metadata: batch.metadata || null,
model: batch.model || null,
usage: batch.usage || null,
};
return {
id: batch.id,
object: "batch",
endpoint: batch.endpoint,
errors: batch.errors || null,
input_file_id: batch.inputFileId,
completion_window: batch.completionWindow,
status: batch.status,
output_file_id: batch.outputFileId || null,
error_file_id: batch.errorFileId || null,
created_at: batch.createdAt,
in_progress_at: batch.inProgressAt || null,
expires_at: batch.expiresAt || null,
finalizing_at: batch.finalizingAt || null,
completed_at: batch.completedAt || null,
failed_at: batch.failedAt || null,
expired_at: batch.expiredAt || null,
cancelling_at: batch.cancellingAt || null,
cancelled_at: batch.cancelledAt || null,
request_counts: {
total: batch.requestCountsTotal || 0,
completed: batch.requestCountsCompleted || 0,
failed: batch.requestCountsFailed || 0,
},
metadata: batch.metadata || null,
model: batch.model || null,
usage: batch.usage || null,
};
}
export async function POST(request: Request) {
const apiKey = extractApiKey(request);
const apiKeyMetadata = await getApiKeyMetadata(apiKey);
const apiKeyId = apiKeyMetadata?.id || null;
const scope = await getApiKeyRequestScope(request);
if (scope.rejection) return scope.rejection;
const apiKeyId = scope.apiKeyId;
try {
const body = await request.json();
@@ -46,55 +46,60 @@ export async function POST(request: Request) {
const inputFile = getFile(validated.input_file_id);
if (!inputFile || (inputFile.apiKeyId !== null && inputFile.apiKeyId !== apiKeyId)) {
return NextResponse.json(
{ error: { message: "Input file not found", type: "invalid_request_error" } },
{ status: 400, headers: CORS_HEADERS }
);
return NextResponse.json(
{ error: { message: "Input file not found", type: "invalid_request_error" } },
{ status: 400, headers: CORS_HEADERS }
);
}
const batch = createBatch({
endpoint: validated.endpoint as any,
completionWindow: validated.completion_window,
inputFileId: validated.input_file_id,
metadata: validated.metadata,
apiKeyId,
outputExpiresAfterSeconds: validated.output_expires_after?.seconds || null,
outputExpiresAfterAnchor: validated.output_expires_after?.anchor || null,
endpoint: validated.endpoint as any,
completionWindow: validated.completion_window,
inputFileId: validated.input_file_id,
metadata: validated.metadata,
apiKeyId,
outputExpiresAfterSeconds: validated.output_expires_after?.seconds || null,
outputExpiresAfterAnchor: validated.output_expires_after?.anchor || null,
});
return NextResponse.json(formatBatchResponse(batch), { headers: CORS_HEADERS });
} catch (error) {
console.error("[BATCHES] Create failed:", error);
return NextResponse.json(
{ error: { message: error instanceof Error ? error.message : "Create failed", type: "invalid_request_error" } },
{
error: {
message: error instanceof Error ? error.message : "Create failed",
type: "invalid_request_error",
},
},
{ status: 400, headers: CORS_HEADERS }
);
}
}
export async function GET(request: Request) {
const apiKey = extractApiKey(request);
const apiKeyMetadata = await getApiKeyMetadata(apiKey);
const apiKeyId = apiKeyMetadata?.id || null;
const scope = await getApiKeyRequestScope(request);
if (scope.rejection) return scope.rejection;
const apiKeyId = scope.apiKeyId;
const url = new URL(request.url);
const limit = Number.parseInt(url.searchParams.get("limit") || "20");
const after = url.searchParams.get("after") || undefined;
const url = new URL(request.url);
const limit = Number.parseInt(url.searchParams.get("limit") || "20");
const after = url.searchParams.get("after") || undefined;
const batches = listBatches(apiKeyId || undefined, limit + 1, after);
const hasMore = batches.length > limit;
const data = hasMore ? batches.slice(0, limit) : batches;
const formattedData = data.map(b => formatBatchResponse(b));
return NextResponse.json(
{
object: "list",
data: formattedData,
first_id: formattedData.length > 0 ? formattedData[0].id : null,
last_id: formattedData.length > 0 ? formattedData.at(-1).id : null,
has_more: hasMore,
},
{ headers: CORS_HEADERS }
);
const batches = listBatches(apiKeyId || undefined, limit + 1, after);
const hasMore = batches.length > limit;
const data = hasMore ? batches.slice(0, limit) : batches;
const formattedData = data.map((b) => formatBatchResponse(b));
return NextResponse.json(
{
object: "list",
data: formattedData,
first_id: formattedData.length > 0 ? formattedData[0].id : null,
last_id: formattedData.length > 0 ? formattedData.at(-1).id : null,
has_more: hasMore,
},
{ headers: CORS_HEADERS }
);
}

View File

@@ -1,12 +1,12 @@
import { CORS_HEADERS } from "@/shared/utils/cors";
import { extractApiKey } from "@/sse/services/auth";
import { getFile, getFileContent, getApiKeyMetadata } from "@/lib/localDb";
import { getFile, getFileContent } from "@/lib/localDb";
import { NextResponse } from "next/server";
import { getApiKeyRequestScope } from "@/app/api/v1/_helpers/apiKeyScope";
export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {
const apiKey = extractApiKey(request);
const apiKeyMetadata = await getApiKeyMetadata(apiKey);
const apiKeyId = apiKeyMetadata?.id || null;
const scope = await getApiKeyRequestScope(request);
if (scope.rejection) return scope.rejection;
const apiKeyId = scope.apiKeyId;
const { id } = await params;
const file = getFile(id);
@@ -20,7 +20,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ id:
const content = getFileContent(id);
if (!content) {
return NextResponse.json(
return NextResponse.json(
{ error: { message: "File content not found", type: "invalid_request_error" } },
{ status: 404, headers: CORS_HEADERS }
);

View File

@@ -1,12 +1,12 @@
import { CORS_HEADERS } from "@/shared/utils/cors";
import { extractApiKey } from "@/sse/services/auth";
import { getFile, deleteFile, getApiKeyMetadata, formatFileResponse } from "@/lib/localDb";
import { getFile, deleteFile, formatFileResponse } from "@/lib/localDb";
import { NextResponse } from "next/server";
import { getApiKeyRequestScope } from "@/app/api/v1/_helpers/apiKeyScope";
export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {
const apiKey = extractApiKey(request);
const apiKeyMetadata = await getApiKeyMetadata(apiKey);
const apiKeyId = apiKeyMetadata?.id || null;
const scope = await getApiKeyRequestScope(request);
if (scope.rejection) return scope.rejection;
const apiKeyId = scope.apiKeyId;
const { id } = await params;
const file = getFile(id);
@@ -17,14 +17,14 @@ export async function GET(request: Request, { params }: { params: Promise<{ id:
{ status: 404, headers: CORS_HEADERS }
);
}
return NextResponse.json(formatFileResponse(file), { headers: CORS_HEADERS });
}
export async function DELETE(request: Request, { params }: { params: Promise<{ id: string }> }) {
const apiKey = extractApiKey(request);
const apiKeyMetadata = await getApiKeyMetadata(apiKey);
const apiKeyId = apiKeyMetadata?.id || null;
const scope = await getApiKeyRequestScope(request);
if (scope.rejection) return scope.rejection;
const apiKeyId = scope.apiKeyId;
const { id } = await params;
const file = getFile(id);
@@ -38,9 +38,12 @@ export async function DELETE(request: Request, { params }: { params: Promise<{ i
deleteFile(id);
return NextResponse.json({
id,
object: "file",
deleted: true
}, { headers: CORS_HEADERS });
return NextResponse.json(
{
id,
object: "file",
deleted: true,
},
{ headers: CORS_HEADERS }
);
}

View File

@@ -1,12 +1,12 @@
import { CORS_HEADERS } from "@/shared/utils/cors";
import { extractApiKey } from "@/sse/services/auth";
import { createFile, listFiles, getApiKeyMetadata, formatFileResponse } from "@/lib/localDb";
import { createFile, listFiles, formatFileResponse } from "@/lib/localDb";
import { NextResponse } from "next/server";
import { getApiKeyRequestScope } from "@/app/api/v1/_helpers/apiKeyScope";
export async function POST(request: Request) {
const apiKey = extractApiKey(request);
const apiKeyMetadata = await getApiKeyMetadata(apiKey);
const apiKeyId = apiKeyMetadata?.id || null;
const scope = await getApiKeyRequestScope(request);
if (scope.rejection) return scope.rejection;
const apiKeyId = scope.apiKeyId;
try {
const formData = await request.formData();
@@ -25,7 +25,12 @@ export async function POST(request: Request) {
const MAX_FILE_BYTES = 512 * 1024 * 1024; // 512 MB
if (file.size > MAX_FILE_BYTES) {
return NextResponse.json(
{ error: { message: "File exceeds maximum allowed size of 512 MB", type: "invalid_request_error" } },
{
error: {
message: "File exceeds maximum allowed size of 512 MB",
type: "invalid_request_error",
},
},
{ status: 400, headers: CORS_HEADERS }
);
}
@@ -61,7 +66,7 @@ export async function POST(request: Request) {
apiKeyId,
expiresAt,
});
return NextResponse.json(formatFileResponse(record), { headers: CORS_HEADERS });
} catch (error) {
console.error("[FILES] Upload failed:", error);
@@ -73,9 +78,9 @@ export async function POST(request: Request) {
}
export async function GET(request: Request) {
const apiKey = extractApiKey(request);
const apiKeyMetadata = await getApiKeyMetadata(apiKey);
const apiKeyId = apiKeyMetadata?.id || null;
const scope = await getApiKeyRequestScope(request);
if (scope.rejection) return scope.rejection;
const apiKeyId = scope.apiKeyId;
const { searchParams } = new URL(request.url);
const limit = Math.min(Number.parseInt(searchParams.get("limit") || "20") || 20, 10000);
@@ -89,12 +94,12 @@ export async function GET(request: Request) {
purpose,
limit: limit + 1,
after,
order
order,
});
const hasMore = files.length > limit;
const data = files.slice(0, limit);
return NextResponse.json(
{
object: "list",

View File

@@ -14,6 +14,7 @@
:root {
--desktop-safe-top: 0px;
--desktop-safe-bottom: 0px;
color-scheme: light;
/* Primary - Coral Red (OpenClaw) */
--color-primary: #e54d5e;
@@ -54,6 +55,7 @@
.dark {
/* Dark theme (ClawHub deep) */
color-scheme: dark;
--color-bg: #0b0e14;
--color-bg-alt: #111520;
--color-bg-primary: #0b0e14;

View File

@@ -0,0 +1,5 @@
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
export async function requireCliToolsAuth(request: Request): Promise<Response | null> {
return requireManagementAuth(request);
}

View File

@@ -0,0 +1,49 @@
import type { SupportedBatchEndpoint } from "@/shared/constants/batchEndpoints";
type BatchRouteHandler = (request: Request) => Promise<Response> | Response;
const handlerLoaders: Record<SupportedBatchEndpoint, () => Promise<BatchRouteHandler>> = {
"/v1/responses": async () => (await import("@/app/api/v1/responses/route")).POST,
"/v1/chat/completions": async () => (await import("@/app/api/v1/chat/completions/route")).POST,
"/v1/embeddings": async () => (await import("@/app/api/v1/embeddings/route")).POST,
"/v1/completions": async () => (await import("@/app/api/v1/completions/route")).POST,
"/v1/moderations": async () => (await import("@/app/api/v1/moderations/route")).POST,
"/v1/images/generations": async () =>
(await import("@/app/api/v1/images/generations/route")).POST,
"/v1/videos/generations": async () =>
(await import("@/app/api/v1/videos/generations/route")).POST,
};
const handlerCache = new Map<SupportedBatchEndpoint, BatchRouteHandler>();
async function getHandler(endpoint: SupportedBatchEndpoint): Promise<BatchRouteHandler> {
const cached = handlerCache.get(endpoint);
if (cached) return cached;
const handler = await handlerLoaders[endpoint]();
handlerCache.set(endpoint, handler);
return handler;
}
export async function dispatchBatchApiRequest({
endpoint,
body,
apiKey,
}: {
endpoint: SupportedBatchEndpoint;
body: Record<string, unknown>;
apiKey?: string | null;
}): Promise<Response> {
const headers = new Headers({ "Content-Type": "application/json" });
if (apiKey) {
headers.set("Authorization", `Bearer ${apiKey}`);
}
const handler = await getHandler(endpoint);
const request = new Request(`http://localhost${endpoint}`, {
method: "POST",
headers,
body: JSON.stringify(body),
});
return await handler(request);
}

View File

@@ -4,13 +4,25 @@ import { v4 as uuidv4 } from "uuid";
function parseBatchRow(row: any): BatchRecord {
const camel = rowToCamel(row) as any;
if (camel.metadata && typeof camel.metadata === "string") {
try { camel.metadata = JSON.parse(camel.metadata); } catch { camel.metadata = null; }
try {
camel.metadata = JSON.parse(camel.metadata);
} catch {
camel.metadata = null;
}
}
if (camel.errors && typeof camel.errors === "string") {
try { camel.errors = JSON.parse(camel.errors); } catch { camel.errors = null; }
try {
camel.errors = JSON.parse(camel.errors);
} catch {
camel.errors = null;
}
}
if (camel.usage && typeof camel.usage === "string") {
try { camel.usage = JSON.parse(camel.usage); } catch { camel.usage = null; }
try {
camel.usage = JSON.parse(camel.usage);
} catch {
camel.usage = null;
}
}
return camel as BatchRecord;
}
@@ -19,7 +31,15 @@ export interface BatchRecord {
id: string;
endpoint: string;
completionWindow: string;
status: "validating" | "failed" | "in_progress" | "finalizing" | "completed" | "expired" | "cancelling" | "cancelled";
status:
| "validating"
| "failed"
| "in_progress"
| "finalizing"
| "completed"
| "expired"
| "cancelling"
| "cancelled";
inputFileId: string;
outputFileId?: string | null;
errorFileId?: string | null;
@@ -44,7 +64,17 @@ export interface BatchRecord {
outputExpiresAfterAnchor?: string | null;
}
export function createBatch(batch: Omit<BatchRecord, "id" | "createdAt" | "status" | "requestCountsTotal" | "requestCountsCompleted" | "requestCountsFailed">): BatchRecord {
export function createBatch(
batch: Omit<
BatchRecord,
| "id"
| "createdAt"
| "status"
| "requestCountsTotal"
| "requestCountsCompleted"
| "requestCountsFailed"
>
): BatchRecord {
const db = getDbInstance();
const id = "batch_" + uuidv4().replaceAll("-", "").substring(0, 24);
const createdAt = Math.floor(Date.now() / 1000);
@@ -67,15 +97,13 @@ export function createBatch(batch: Omit<BatchRecord, "id" | "createdAt" | "statu
...record,
metadata: record.metadata ? JSON.stringify(record.metadata) : null,
errors: record.errors ? JSON.stringify(record.errors) : null,
usage: record.usage ? JSON.stringify(record.usage) : null
usage: record.usage ? JSON.stringify(record.usage) : null,
}) as any;
const keys = Object.keys(snakeRecord);
const values = Object.values(snakeRecord);
const placeholders = keys.map(() => "?").join(", ");
db.prepare(
`INSERT INTO batches (${keys.join(", ")}) VALUES (${placeholders})`
).run(...values);
db.prepare(`INSERT INTO batches (${keys.join(", ")}) VALUES (${placeholders})`).run(...values);
return record;
}
@@ -99,52 +127,61 @@ export function updateBatch(id: string, updates: Partial<BatchRecord>): boolean
if (snakeUpdates.usage && typeof snakeUpdates.usage !== "string") {
snakeUpdates.usage = JSON.stringify(snakeUpdates.usage);
}
const keys = Object.keys(snakeUpdates);
if (keys.length === 0) return false;
const setClause = keys.map(k => `${k} = ?`).join(", ");
const setClause = keys.map((k) => `${k} = ?`).join(", ");
const values = Object.values(snakeUpdates);
const result = db.prepare(`UPDATE batches SET ${setClause} WHERE id = ?`).run(...values, id);
return result.changes > 0;
}
export function listBatches(apiKeyId?: string, limit: number = 20, after?: string): BatchRecord[] {
const db = getDbInstance();
const afterBatch = after ? getBatch(after) : null;
let rows: any[];
if (apiKeyId) {
if (after) {
if (afterBatch) {
rows = db
.prepare("SELECT * FROM batches WHERE api_key_id = ? AND id < ? ORDER BY id DESC LIMIT ?")
.all(apiKeyId, after, limit);
.prepare(
"SELECT * FROM batches WHERE api_key_id = ? AND (created_at < ? OR (created_at = ? AND id < ?)) ORDER BY created_at DESC, id DESC LIMIT ?"
)
.all(apiKeyId, afterBatch.createdAt, afterBatch.createdAt, after, limit);
} else {
rows = db
.prepare("SELECT * FROM batches WHERE api_key_id = ? ORDER BY id DESC LIMIT ?")
.prepare(
"SELECT * FROM batches WHERE api_key_id = ? ORDER BY created_at DESC, id DESC LIMIT ?"
)
.all(apiKeyId, limit);
}
} else if (after) {
} else if (afterBatch) {
rows = db
.prepare("SELECT * FROM batches WHERE id < ? ORDER BY id DESC LIMIT ?")
.all(after, limit);
.prepare(
"SELECT * FROM batches WHERE (created_at < ? OR (created_at = ? AND id < ?)) ORDER BY created_at DESC, id DESC LIMIT ?"
)
.all(afterBatch.createdAt, afterBatch.createdAt, after, limit);
} else {
rows = db.prepare("SELECT * FROM batches ORDER BY id DESC LIMIT ?").all(limit);
rows = db.prepare("SELECT * FROM batches ORDER BY created_at DESC, id DESC LIMIT ?").all(limit);
}
return rows.map(row => parseBatchRow(row));
return rows.map((row) => parseBatchRow(row));
}
export function getPendingBatches(): BatchRecord[] {
const db = getDbInstance();
const rows = db.prepare(
"SELECT * FROM batches WHERE status IN ('validating', 'in_progress', 'cancelling')"
).all();
return rows.map(row => parseBatchRow(row));
const rows = db
.prepare("SELECT * FROM batches WHERE status IN ('validating', 'in_progress', 'cancelling')")
.all();
return rows.map((row) => parseBatchRow(row));
}
export function getTerminalBatches(): BatchRecord[] {
const db = getDbInstance();
const rows = db.prepare(
"SELECT * FROM batches WHERE status IN ('completed', 'failed', 'cancelled', 'expired') ORDER BY created_at ASC"
).all();
return rows.map(row => parseBatchRow(row));
const rows = db
.prepare(
"SELECT * FROM batches WHERE status IN ('completed', 'failed', 'cancelled', 'expired') ORDER BY created_at ASC"
)
.all();
return rows.map((row) => parseBatchRow(row));
}

View File

@@ -0,0 +1,11 @@
export const SUPPORTED_BATCH_ENDPOINTS = [
"/v1/responses",
"/v1/chat/completions",
"/v1/embeddings",
"/v1/completions",
"/v1/moderations",
"/v1/images/generations",
"/v1/videos/generations",
] as const;
export type SupportedBatchEndpoint = (typeof SUPPORTED_BATCH_ENDPOINTS)[number];

View File

@@ -15,6 +15,7 @@ const CLI_TOOLS: Record<string, any> = {
healthcheckTimeoutMs: 4000,
paths: {
settings: ".claude/settings.json",
auth: ".claude/.credentials.json",
},
},
codex: {

View File

@@ -1,4 +1,5 @@
import { z } from "zod";
import { SUPPORTED_BATCH_ENDPOINTS } from "@/shared/constants/batchEndpoints";
import { HIDEABLE_SIDEBAR_ITEM_IDS } from "@/shared/constants/sidebarVisibility";
import { isForbiddenUpstreamHeaderName } from "@/shared/constants/upstreamHeaders";
@@ -1799,16 +1800,7 @@ export const versionManagerInstallSchema = versionManagerToolSchema.extend({
export const v1BatchCreateSchema = z.object({
input_file_id: z.string().min(1),
endpoint: z.enum([
"/v1/responses",
"/v1/chat/completions",
"/v1/embeddings",
"/v1/completions",
"/v1/moderations",
"/v1/images/generations",
"/v1/images/edits",
"/v1/videos",
]),
endpoint: z.enum(SUPPORTED_BATCH_ENDPOINTS),
completion_window: z.enum(["24h"]),
metadata: z
.record(z.string().max(64), z.string().max(512))

View File

@@ -699,6 +699,8 @@ async function handleSingleModelChat(
// 4. Execute chat via core after breaker gate checks (with optional TLS tracking)
if (telemetry) telemetry.startPhase("connect");
const { result, tlsFingerprintUsed } = await executeChatWithBreaker({
bypassCircuitBreaker: forceLiveComboTest || hasForcedConnection,
breaker,
body: requestBody,
provider,
model,

View File

@@ -24,7 +24,7 @@ import {
isTlsFingerprintActive,
} from "@omniroute/open-sse/utils/proxyFetch.ts";
import { resolveProxyForConnection } from "@/lib/localDb";
import { getCircuitBreaker } from "../../shared/utils/circuitBreaker";
import { CircuitBreakerOpenError, getCircuitBreaker } from "../../shared/utils/circuitBreaker";
import { logProxyEvent } from "../../lib/proxyLogger";
import { logTranslationEvent } from "../../lib/translatorEvents";
import { getRuntimeProviderProfile } from "@omniroute/open-sse/services/accountFallback.ts";
@@ -102,6 +102,8 @@ export async function checkPipelineGates(
}
export async function executeChatWithBreaker({
bypassCircuitBreaker,
breaker,
body,
provider,
model,
@@ -154,14 +156,36 @@ export async function executeChatWithBreaker({
})
);
if (bypassCircuitBreaker) {
if (!proxyInfo?.proxy && isTlsFingerprintActive()) {
const tracked = await runWithTlsTracking(chatFn);
return { result: tracked.result, tlsFingerprintUsed: tracked.tlsFingerprintUsed };
}
const result = await chatFn();
return { result, tlsFingerprintUsed: false };
}
if (!proxyInfo?.proxy && isTlsFingerprintActive()) {
const tracked = await runWithTlsTracking(chatFn);
const tracked = await breaker.execute(async () => runWithTlsTracking(chatFn));
return { result: tracked.result, tlsFingerprintUsed: tracked.tlsFingerprintUsed };
}
const result = await chatFn();
const result = await breaker.execute(chatFn);
return { result, tlsFingerprintUsed: false };
} catch (cbErr: any) {
if (cbErr instanceof CircuitBreakerOpenError) {
log.warn("CIRCUIT", `${provider} circuit open during retry: ${cbErr.message}`);
return {
result: {
success: false,
response: providerCircuitOpenResponse(provider, Math.ceil(cbErr.retryAfterMs / 1000)),
status: HTTP_STATUS.SERVICE_UNAVAILABLE,
},
tlsFingerprintUsed: false,
};
}
if (cbErr?.code === "PROXY_UNREACHABLE" || /proxy unreachable/i.test(cbErr?.message || "")) {
const detail = cbErr?.message || "Proxy unreachable";
log.warn("PROXY", detail);

View File

@@ -4,14 +4,14 @@ test.describe("API Health Checks", () => {
test("GET /api/monitoring/health returns OK", async ({ request }) => {
const res = await request.get("/api/monitoring/health");
expect(res.ok()).toBeTruthy();
const body = await res.json();
const body = (await res.json()) as any;
expect(body).toHaveProperty("status");
});
test("GET /api/v1/models returns model list", async ({ request }) => {
const res = await request.get("/api/v1/models");
expect(res.ok()).toBeTruthy();
const body = await res.json();
const body = (await res.json()) as any;
expect(body).toHaveProperty("data");
expect(Array.isArray(body.data)).toBe(true);
});
@@ -20,7 +20,7 @@ test.describe("API Health Checks", () => {
const res = await request.get("/api/providers");
// In CI with auth enabled, 401 is acceptable — endpoint is reachable
if (res.ok()) {
const body = await res.json();
const body = (await res.json()) as any;
expect(body).toHaveProperty("connections");
expect(Array.isArray(body.connections)).toBe(true);
} else {

View File

@@ -38,21 +38,21 @@ describe("E2E: MCP Server (16 tools)", () => {
itCase("should respond to health check", async () => {
const res = await apiFetch("/api/monitoring/health");
expect(res.ok).toBe(true);
const data = await res.json();
const data = (await res.json()) as any;
expect(data).toHaveProperty("status");
});
itCase("should list combos", async () => {
const res = await apiFetch("/api/combos");
expect(res.ok).toBe(true);
const data = await res.json();
const data = (await res.json()) as any;
expect(Array.isArray(data?.combos)).toBe(true);
});
itCase("should return quota data", async () => {
const res = await apiFetch("/api/usage/quota");
expect(res.ok).toBe(true);
const data = await res.json();
const data = (await res.json()) as any;
expect(Array.isArray(data?.providers)).toBe(true);
expect(data).toHaveProperty("meta");
});
@@ -74,7 +74,7 @@ describe("E2E: Quota Contract (/api/usage/quota)", () => {
const res = await apiFetch("/api/usage/quota");
expect(res.ok).toBe(true);
const data = await res.json();
const data = (await res.json()) as any;
expect(Array.isArray(data.providers)).toBe(true);
expect(data).toHaveProperty("meta");
expect(typeof data.meta.generatedAt).toBe("string");
@@ -96,13 +96,13 @@ describe("E2E: Quota Contract (/api/usage/quota)", () => {
itCase("should filter quota by provider", async () => {
const allRes = await apiFetch("/api/usage/quota");
expect(allRes.ok).toBe(true);
const allData = await allRes.json();
const allData = (await allRes.json()) as any;
if (!Array.isArray(allData.providers) || allData.providers.length === 0) return;
const provider = allData.providers[0].provider;
const filteredRes = await apiFetch(`/api/usage/quota?provider=${encodeURIComponent(provider)}`);
expect(filteredRes.ok).toBe(true);
const filteredData = await filteredRes.json();
const filteredData = (await filteredRes.json()) as any;
expect(filteredData.meta.filters.provider).toBe(provider);
expect(Array.isArray(filteredData.providers)).toBe(true);
expect(filteredData.providers.every((p: any) => p.provider === provider)).toBe(true);
@@ -111,7 +111,7 @@ describe("E2E: Quota Contract (/api/usage/quota)", () => {
itCase("should filter quota by connectionId", async () => {
const allRes = await apiFetch("/api/usage/quota");
expect(allRes.ok).toBe(true);
const allData = await allRes.json();
const allData = (await allRes.json()) as any;
if (!Array.isArray(allData.providers) || allData.providers.length === 0) return;
const connectionId = allData.providers[0].connectionId;
@@ -119,7 +119,7 @@ describe("E2E: Quota Contract (/api/usage/quota)", () => {
`/api/usage/quota?connectionId=${encodeURIComponent(connectionId)}`
);
expect(filteredRes.ok).toBe(true);
const filteredData = await filteredRes.json();
const filteredData = (await filteredRes.json()) as any;
expect(filteredData.meta.filters.connectionId).toBe(connectionId);
expect(Array.isArray(filteredData.providers)).toBe(true);
expect(filteredData.providers.every((p: any) => p.connectionId === connectionId)).toBe(true);
@@ -131,7 +131,7 @@ describe("E2E: A2A Server (lifecycle)", () => {
itCase("should serve Agent Card", async () => {
const res = await apiFetch("/.well-known/agent.json");
expect(res.ok).toBe(true);
const card = await res.json();
const card = (await res.json()) as any;
expect(card).toHaveProperty("name");
expect(card).toHaveProperty("skills");
expect(card).toHaveProperty("version");
@@ -152,7 +152,7 @@ describe("E2E: A2A Server (lifecycle)", () => {
}),
});
expect(res.ok).toBe(true);
const data = await res.json();
const data = (await res.json()) as any;
expect(data).toHaveProperty("result");
expect(data.result.task).toHaveProperty("id");
expect(data.result.task).toHaveProperty("state");
@@ -168,7 +168,7 @@ describe("E2E: A2A Server (lifecycle)", () => {
params: {},
}),
});
const data = await res.json();
const data = (await res.json()) as any;
expect(data).toHaveProperty("error");
expect(data.error.code).toBe(-32601);
});
@@ -197,7 +197,7 @@ describe("E2E: Auto-Combo (routing + self-healing)", () => {
const res = await apiFetch("/api/combos");
if (!res.ok) console.error("GET /api/combos failed:", await res.text());
expect(res.ok).toBe(true);
const data = await res.json();
const data = (await res.json()) as any;
expect(Array.isArray(data?.combos)).toBe(true);
const autoCombos = data.combos.filter((c: any) => c.strategy === "auto");
expect(autoCombos.length).toBeGreaterThanOrEqual(0);
@@ -209,7 +209,7 @@ describe("E2E: OpenClaw Integration", () => {
itCase("should return dynamic provider.order", async () => {
const res = await apiFetch("/api/cli-tools/openclaw/auto-order");
expect(res.ok).toBe(true);
const data = await res.json();
const data = (await res.json()) as any;
expect(data).toHaveProperty("provider");
expect(data.provider).toHaveProperty("order");
expect(Array.isArray(data.provider.order)).toBe(true);
@@ -306,7 +306,7 @@ describe("E2E: Security", () => {
method: "POST",
body: "not-json",
});
const data = await res.json();
const data = (await res.json()) as any;
if (data.error) {
expect(data.error.code).toBe(-32700); // Parse error
}

View File

@@ -47,7 +47,7 @@ async function getDashboardAuthState(page: Page) {
return await page.evaluate(async () => {
const safeJson = async (response: Response) => {
try {
return await response.json();
return (await response.json()) as any;
} catch {
return null;
}
@@ -111,7 +111,7 @@ export async function gotoDashboardRoute(
await page.locator("body").waitFor({ state: "visible", timeout: timeoutMs });
return;
} catch (error) {
} catch (error: any) {
lastError = error;
await page.waitForTimeout(1000);
}

View File

@@ -129,7 +129,7 @@ describe("Protocol clients E2E", () => {
expect([200, 401]).toContain(auditRes.status);
if (auditRes.status === 200) {
expect(auditRes.ok).toBe(true);
const auditJson = await auditRes.json();
const auditJson = (await auditRes.json()) as any;
const entries = Array.isArray(auditJson?.entries) ? auditJson.entries : [];
expect(entries.some((entry: any) => entry.toolName === "omniroute_get_health")).toBe(true);
}
@@ -142,7 +142,7 @@ describe("Protocol clients E2E", () => {
async () => {
const cardRes = await apiFetch("/.well-known/agent.json");
expect(cardRes.ok).toBe(true);
const card = await cardRes.json();
const card = (await cardRes.json()) as any;
expect(card).toHaveProperty("name");
expect(Array.isArray(card?.skills)).toBe(true);
@@ -203,7 +203,7 @@ describe("Protocol clients E2E", () => {
expect([200, 401]).toContain(tasksRes.status);
if (tasksRes.status === 200) {
expect(tasksRes.ok).toBe(true);
const tasksJson = await tasksRes.json();
const tasksJson = (await tasksRes.json()) as any;
const tasks = Array.isArray(tasksJson?.tasks) ? tasksJson.tasks : [];
expect(tasks.some((task: any) => task.id === sendTaskId)).toBe(true);
}

View File

@@ -73,8 +73,8 @@ test("API keys routes require management auth when login protection is enabled",
})
);
const unauthenticatedBody = await unauthenticated.json();
const invalidTokenBody = await invalidToken.json();
const unauthenticatedBody = (await unauthenticated.json()) as any;
const invalidTokenBody = (await invalidToken.json()) as any;
assert.equal(unauthenticated.status, 401);
assert.equal(unauthenticatedBody.error.message, "Authentication required");
@@ -99,8 +99,8 @@ test("API keys POST also requires management auth when login protection is enabl
})
);
const unauthenticatedBody = await unauthenticated.json();
const invalidTokenBody = await invalidToken.json();
const unauthenticatedBody = (await unauthenticated.json()) as any;
const invalidTokenBody = (await invalidToken.json()) as any;
assert.equal(unauthenticated.status, 401);
assert.equal(unauthenticatedBody.error.message, "Authentication required");
@@ -117,7 +117,7 @@ test("POST /api/keys creates a key, preserves special characters, and persists n
body: { name: "Key / Prod #1", noLog: true },
})
);
const body = await response.json();
const body = (await response.json()) as any;
const stored = await apiKeysDb.getApiKeyById(body.id);
assert.equal(response.status, 201);
@@ -160,7 +160,7 @@ test("POST /api/keys returns a server error for malformed JSON payloads", async
body: "{",
})
);
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 500);
assert.equal(body.error, "Failed to create key");
@@ -180,8 +180,8 @@ test("GET /api/keys lists masked keys with pagination and GET /api/keys/[id] sta
{ params: Promise.resolve({ id: createdB.id }) }
);
const listBody = await listResponse.json();
const getBody = await getResponse.json();
const listBody = (await listResponse.json()) as any;
const getBody = (await getResponse.json()) as any;
assert.equal(listResponse.status, 200);
assert.equal(listBody.total, 3);
@@ -205,7 +205,7 @@ test("GET /api/keys falls back to default pagination for invalid query params",
const response = await listRoute.GET(
await makeManagementSessionRequest("http://localhost/api/keys?limit=0&offset=-25")
);
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(body.total, 3);
@@ -222,7 +222,7 @@ test("GET /api/keys treats non-numeric pagination params as defaults", async ()
const response = await listRoute.GET(
await makeManagementSessionRequest("http://localhost/api/keys?limit=abc&offset=xyz")
);
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(body.total, 3);
@@ -243,7 +243,7 @@ test("GET /api/keys uses default pagination when query params are absent and rep
const response = await listRoute.GET(
await makeManagementSessionRequest("http://localhost/api/keys")
);
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(body.total, 3);
@@ -274,7 +274,7 @@ test("POST /api/keys triggers cloud sync when cloud mode is enabled", async () =
body: { name: "Cloud Synced Key" },
})
);
const body = await response.json();
const body = (await response.json()) as any;
const syncPayload = JSON.parse(calls[0].options.body);
assert.equal(response.status, 201);
@@ -309,7 +309,7 @@ test("GET /api/keys returns 500 when the key store throws unexpectedly", async (
try {
const response = await listRoute.GET(new Request("http://localhost/api/keys"));
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 500);
assert.equal(body.error, "Failed to fetch keys");
@@ -340,7 +340,7 @@ test("POST /api/keys still succeeds when cloud sync fails after creation", async
body: { name: "Cloud Failure Tolerated" },
})
);
const body = await response.json();
const body = (await response.json()) as any;
const stored = await apiKeysDb.getApiKeyById(body.id);
assert.equal(response.status, 201);
@@ -372,9 +372,9 @@ test("GET /api/keys/[id] returns 404 for an unknown key and reveal is gated by t
{ params: Promise.resolve({ id: created.id }) }
);
const missingBody = await missingResponse.json();
const revealDisabledBody = await revealDisabled.json();
const revealEnabledBody = await revealEnabled.json();
const missingBody = (await missingResponse.json()) as any;
const revealDisabledBody = (await revealDisabled.json()) as any;
const revealEnabledBody = (await revealEnabled.json()) as any;
assert.equal(missingResponse.status, 404);
assert.equal(missingBody.error, "Key not found");
@@ -417,9 +417,9 @@ test("PATCH /api/keys/[id] updates permissions and rejects invalid payloads", as
{ params: Promise.resolve({ id: "missing" }) }
);
const patchBody = await patchResponse.json();
const invalidJsonBody = await invalidJsonResponse.json();
const missingKeyBody = await missingKeyResponse.json();
const patchBody = (await patchResponse.json()) as any;
const invalidJsonBody = (await invalidJsonResponse.json()) as any;
const missingKeyBody = (await missingKeyResponse.json()) as any;
const updated = await apiKeysDb.getApiKeyById(created.id);
assert.equal(patchResponse.status, 200);
@@ -453,8 +453,8 @@ test("DELETE /api/keys/[id] removes keys and reports missing resources", async (
{ params: Promise.resolve({ id: "missing" }) }
);
const deleteBody = await deleteResponse.json();
const missingDeleteBody = await missingDeleteResponse.json();
const deleteBody = (await deleteResponse.json()) as any;
const missingDeleteBody = (await missingDeleteResponse.json()) as any;
assert.equal(deleteResponse.status, 200);
assert.equal(deleteBody.message, "Key deleted successfully");

View File

@@ -84,7 +84,7 @@ test("critical routes: v1 management proxies covers auth, lookup, where-used, pa
},
})
);
const created = await createResponse.json();
const created = (await createResponse.json()) as any;
await localDb.assignProxyToScope("provider", "openai", created.id);
@@ -142,17 +142,17 @@ test("critical routes: v1 management proxies covers auth, lookup, where-used, pa
)
);
const unauthenticatedBody = await unauthenticated.json();
const invalidTokenBody = await invalidToken.json();
const getByIdBody = await getById.json();
const whereUsedBody = await whereUsed.json();
const missingGetBody = await missingGet.json();
const invalidJsonPatchBody = await invalidJsonPatch.json();
const invalidPatchBody = await invalidPatch.json();
const validPatchBody = await validPatch.json();
const missingDeleteBody = await missingDelete.json();
const conflictDeleteBody = await conflictDelete.json();
const forcedDeleteBody = await forcedDelete.json();
const unauthenticatedBody = (await unauthenticated.json()) as any;
const invalidTokenBody = (await invalidToken.json()) as any;
const getByIdBody = (await getById.json()) as any;
const whereUsedBody = (await whereUsed.json()) as any;
const missingGetBody = (await missingGet.json()) as any;
const invalidJsonPatchBody = (await invalidJsonPatch.json()) as any;
const invalidPatchBody = (await invalidPatch.json()) as any;
const validPatchBody = (await validPatch.json()) as any;
const missingDeleteBody = (await missingDelete.json()) as any;
const conflictDeleteBody = (await conflictDelete.json()) as any;
const forcedDeleteBody = (await forcedDelete.json()) as any;
assert.equal(unauthenticated.status, 401);
assert.equal(unauthenticatedBody.error.message, "Authentication required");
@@ -210,7 +210,7 @@ test("critical routes: v1 management proxies validates create payloads and clamp
},
})
);
const created = await createResponse.json();
const created = (await createResponse.json()) as any;
createdIds.push(created.id);
assert.equal(createResponse.status, 201);
}
@@ -232,11 +232,11 @@ test("critical routes: v1 management proxies validates create payloads and clamp
})
);
const invalidJsonPostBody = await invalidJsonPost.json();
const invalidPostBody = await invalidPost.json();
const pagedListBody = await pagedList.json();
const missingPatchBody = await missingPatch.json();
const missingDeleteBody = await missingDelete.json();
const invalidJsonPostBody = (await invalidJsonPost.json()) as any;
const invalidPostBody = (await invalidPost.json()) as any;
const pagedListBody = (await pagedList.json()) as any;
const missingPatchBody = (await missingPatch.json()) as any;
const missingDeleteBody = (await missingDelete.json()) as any;
assert.equal(invalidJsonPost.status, 400);
assert.equal(invalidJsonPostBody.error.message, "Invalid JSON body");
@@ -304,13 +304,13 @@ test("critical routes: v1 management proxies requires auth on mutating routes",
);
for (const response of [unauthenticatedPost, unauthenticatedPatch, unauthenticatedDelete]) {
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 401);
assert.equal(body.error.message, "Authentication required");
}
for (const response of [invalidPost, invalidPatch, invalidDelete]) {
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 403);
assert.equal(body.error.message, "Invalid management token");
}
@@ -370,14 +370,14 @@ test("critical routes: settings proxy resolves config, validates payloads, and d
new Request("http://localhost/api/settings/proxy")
);
const invalidJsonBody = await invalidJson.json();
const invalidProvidersBody = await invalidProviders.json();
const setProviderProxyBody = await setProviderProxy.json();
const getProviderProxyBody = await getProviderProxy.json();
const resolveProxyBody = await resolveProxy.json();
const missingLevelDeleteBody = await missingLevelDelete.json();
const deleteProviderProxyBody = await deleteProviderProxy.json();
const getFullConfigBody = await getFullConfig.json();
const invalidJsonBody = (await invalidJson.json()) as any;
const invalidProvidersBody = (await invalidProviders.json()) as any;
const setProviderProxyBody = (await setProviderProxy.json()) as any;
const getProviderProxyBody = (await getProviderProxy.json()) as any;
const resolveProxyBody = (await resolveProxy.json()) as any;
const missingLevelDeleteBody = (await missingLevelDelete.json()) as any;
const deleteProviderProxyBody = (await deleteProviderProxy.json()) as any;
const getFullConfigBody = (await getFullConfig.json()) as any;
assert.equal(invalidJson.status, 400);
assert.equal(invalidJsonBody.error.message, "Invalid JSON body");
@@ -413,7 +413,7 @@ test("critical routes: settings proxy prefers registry assignment for global loo
const response = await settingsProxyRoute.GET(
new Request("http://localhost/api/settings/proxy?level=global")
);
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(body.level, "global");
@@ -470,10 +470,10 @@ test("critical routes: settings proxy covers global fallback and socks5 gating",
})
);
const setLegacyGlobalProxyBody = await setLegacyGlobalProxy.json();
const getLegacyGlobalProxyBody = await getLegacyGlobalProxy.json();
const socksDisabledBody = await socksDisabled.json();
const socksEnabledBody = await socksEnabled.json();
const setLegacyGlobalProxyBody = (await setLegacyGlobalProxy.json()) as any;
const getLegacyGlobalProxyBody = (await getLegacyGlobalProxy.json()) as any;
const socksDisabledBody = (await socksDisabled.json()) as any;
const socksEnabledBody = (await socksEnabled.json()) as any;
assert.equal(setLegacyGlobalProxy.status, 200);
assert.equal(setLegacyGlobalProxyBody.global.host, "legacy.proxy.local");
@@ -490,7 +490,7 @@ test("critical routes: v1 models route exposes CORS and list contracts", async (
const response = await v1ModelsRoute.GET(
new Request("http://localhost/api/v1/models", { method: "GET" })
);
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(options.status, 200);
assert.match(options.headers.get("Access-Control-Allow-Methods") || "", /GET/);

View File

@@ -471,7 +471,7 @@ test("chat pipeline handles OpenAI passthrough with valid API key auth", async (
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(fetchCalls.length, 1);
assert.match(fetchCalls[0].url, /\/chat\/completions$/);
@@ -504,7 +504,7 @@ test("chat pipeline persists Codex responses cache and reasoning tokens to call
})
);
const json = await response.json();
const json = (await response.json()) as any;
const callLog = await waitFor(() => getLatestCallLog());
assert.equal(response.status, 200);
@@ -639,7 +639,7 @@ test("chat pipeline translates OpenAI requests to Claude and returns OpenAI-shap
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(fetchCalls.length, 1);
assert.match(fetchCalls[0].url, /\?beta=true$/);
@@ -673,7 +673,7 @@ test("chat pipeline translates OpenAI requests to Gemini and returns OpenAI-shap
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(fetchCalls.length, 1);
assert.match(fetchCalls[0].url, /generateContent$/);
@@ -710,7 +710,7 @@ test("chat pipeline translates Claude-format requests into OpenAI upstream and b
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(fetchCalls.length, 1);
assert.match(fetchCalls[0].url, /\/chat\/completions$/);
@@ -757,7 +757,7 @@ test("chat pipeline rejects invalid API keys and malformed JSON bodies", async (
},
})
);
const invalidKeyJson = await invalidKeyResponse.json();
const invalidKeyJson = (await invalidKeyResponse.json()) as any;
const invalidJsonResponse = await handleChat(
new Request("http://localhost/v1/chat/completions", {
@@ -766,7 +766,7 @@ test("chat pipeline rejects invalid API keys and malformed JSON bodies", async (
body: "{bad-json",
})
);
const invalidJson = await invalidJsonResponse.json();
const invalidJson = (await invalidJsonResponse.json()) as any;
assert.equal(invalidKeyResponse.status, 401);
assert.match(invalidKeyJson.error.message, /Invalid API key/i);
@@ -786,7 +786,7 @@ test("chat pipeline rejects requests without a bearer key when strict API key mo
},
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 401);
assert.match(json.error.message, /Missing API key/i);
@@ -801,7 +801,7 @@ test("chat pipeline returns 400 when the model field is omitted", async () => {
},
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 400);
assert.match(json.error.message, /Missing model/i);
@@ -857,7 +857,7 @@ test("chat pipeline supports local mode without Authorization on explicit combos
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(fetchCalls.length, 1);
assert.equal(json.choices[0].message.content, "Local combo route");
@@ -901,7 +901,7 @@ test("chat pipeline returns current no-credentials contract when no provider con
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 400);
assert.match(json.error.message, /No credentials for provider: openai/);
});
@@ -925,7 +925,7 @@ test("chat pipeline surfaces upstream 500 responses as structured errors", async
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 500);
assert.match(json.error.message, /\[500\]: provider exploded/);
});
@@ -963,7 +963,7 @@ test("chat pipeline returns 429 with Retry-After when the upstream rate-limits t
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 429);
assert.ok(attempts >= 1, "expected at least one upstream attempt");
assert.ok(Number(response.headers.get("Retry-After")) >= 1);
@@ -1029,7 +1029,7 @@ test("chat pipeline maps upstream timeouts to 504 responses", async () => {
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 504);
assert.match(json.error.message, /\[504\]: upstream timed out/);
});
@@ -1068,7 +1068,7 @@ test("chat pipeline injects memory context before sending the upstream request",
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(fetchCalls.length, 1);
assert.equal(fetchCalls[0].body.messages[0].role, "system");
@@ -1129,7 +1129,7 @@ test("chat pipeline injects skills into tools and intercepts tool calls with ski
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(fetchCalls.length, 1);
assert.ok(Array.isArray(fetchCalls[0].body.tools));
@@ -1176,7 +1176,7 @@ test("chat pipeline falls back to the next account after a provider failure", as
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.deepEqual(seenAuthHeaders, [
"Bearer sk-openai-primary-fallback",
@@ -1224,7 +1224,7 @@ test("chat pipeline falls back across combo models when the first provider fails
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(attempts.length, 2);
assert.match(attempts[0].url, /\/chat\/completions$/);

View File

@@ -64,7 +64,7 @@ test("combo routes requests by exact combo name", async () => {
body: buildOpenAIChatBody("router-priority"),
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(fetchCalls.length, 1);
@@ -165,7 +165,7 @@ test("priority combo falls back to the secondary model when the first one fails"
};
const response = await handleChat(buildRequest({ body: buildOpenAIChatBody("router-fallback") }));
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(attempts.length, 2);
@@ -227,7 +227,7 @@ test("priority combo can repeat the same provider/model with different fixed acc
body: buildOpenAIChatBody("router-fixed-accounts"),
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(authHeaders.length, 2);
@@ -297,7 +297,7 @@ test("model combo mappings route explicit model ids through the configured combo
body: buildOpenAIChatBody("tenant/mapped-model"),
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(fetchCalls.length, 1);
@@ -332,7 +332,7 @@ test("wildcard model combo mappings resolve arbitrary matching models", async ()
body: buildOpenAIChatBody("tenant/any-model-name"),
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(fetchCalls.length, 1);
@@ -347,7 +347,7 @@ test("unmapped custom model requests fail after combo resolution falls through",
body: buildOpenAIChatBody("tenant/unmapped-model"),
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 400);
assert.match(json.error.message, /No credentials for provider: tenant/);
@@ -378,7 +378,7 @@ test("strategy updates take effect for later requests on the same combo name", a
body: buildOpenAIChatBody("router-dynamic", "Route dynamic initial"),
})
);
await combosDb.updateCombo(combo.id, {
await combosDb.updateCombo((combo as any).id, {
strategy: "round-robin",
config: { maxRetries: 0, retryDelayMs: 0 },
});

View File

@@ -37,7 +37,7 @@ function dropFts5Artifacts() {
"DROP TRIGGER IF EXISTS memory_fts_au;" +
"DROP TABLE IF EXISTS memory_fts;"
);
} catch (_) {
} catch (_: any) {
/* ignore if already dropped or DB not yet initialized */
}
}

View File

@@ -231,7 +231,7 @@ describe("Performance: memory API route handler (1000 records)", () => {
assert.equal(response.status, 200, "Response should be 200 OK");
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(body.data.length, 50, "Should return 50 items");
assert.equal(body.total, MEMORY_COUNT, "Total should be 1000");
assert.ok(body.stats, "Response should include stats");

View File

@@ -50,7 +50,7 @@ test("integration: proxy registry full flow works and enforces safe delete", asy
})
);
assert.equal(createRes.status, 201);
const createdProxy = await createRes.json();
const createdProxy = (await createRes.json()) as any;
assert.ok(createdProxy.id);
const assignRes = await proxyAssignmentsRoute.PUT(
@@ -72,7 +72,7 @@ test("integration: proxy registry full flow works and enforces safe delete", asy
)
);
assert.equal(resolveRes.status, 200);
const resolved = await resolveRes.json();
const resolved = (await resolveRes.json()) as any;
assert.equal(resolved.level, "account");
assert.equal(resolved.source, "registry");
assert.equal(resolved.proxy.host, "flow.local");
@@ -89,7 +89,7 @@ test("integration: proxy registry full flow works and enforces safe delete", asy
})
);
assert.equal(bulkRes.status, 200);
const bulkPayload = await bulkRes.json();
const bulkPayload = (await bulkRes.json()) as any;
assert.equal(bulkPayload.updated, 2);
assert.equal(bulkPayload.failed.length, 0);
@@ -114,7 +114,7 @@ test("integration: proxy registry full flow works and enforces safe delete", asy
new Request("http://localhost/api/settings/proxies/health?hours=24")
);
assert.equal(healthRes.status, 200);
const healthPayload = await healthRes.json();
const healthPayload = (await healthRes.json()) as any;
const row = healthPayload.items.find((item) => item.proxyId === createdProxy.id);
assert.ok(row);
assert.equal(row.totalRequests >= 2, true);
@@ -126,7 +126,7 @@ test("integration: proxy registry full flow works and enforces safe delete", asy
})
);
assert.equal(deleteConflictRes.status, 409);
const deleteConflict = await deleteConflictRes.json();
const deleteConflict = (await deleteConflictRes.json()) as any;
assert.equal(deleteConflict.error.type, "conflict");
assert.equal(typeof deleteConflict.requestId, "string");
assert.equal(deleteConflict.requestId.length > 0, true);
@@ -163,6 +163,6 @@ test("integration: proxy registry full flow works and enforces safe delete", asy
})
);
assert.equal(deleteOkRes.status, 200);
const deleteOkPayload = await deleteOkRes.json();
const deleteOkPayload = (await deleteOkRes.json()) as any;
assert.equal(deleteOkPayload.success, true);
});

View File

@@ -237,7 +237,7 @@ async function waitForServer(
});
if (response.ok) return;
lastError = `HTTP ${response.status}`;
} catch (error) {
} catch (error: any) {
lastError = error instanceof Error ? error.message : String(error);
}
await sleep(500);
@@ -383,14 +383,14 @@ async function patchResilience(baseUrl: string, config: Record<string, unknown>)
body: JSON.stringify(config),
signal: AbortSignal.timeout(10_000),
});
const payload = await response.json();
const payload = (await response.json()) as any;
assert.equal(response.status, 200, JSON.stringify(payload));
return payload;
}
async function getJson(url: string) {
const response = await fetch(url, { signal: AbortSignal.timeout(10_000) });
const json = await response.json();
const json = (await response.json()) as any;
return { response, json };
}

View File

@@ -89,7 +89,7 @@ test("skills API lists registered skills", async () => {
});
const response = await skillsRouteModule.GET();
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.ok(Array.isArray(json.skills));
@@ -170,7 +170,7 @@ test("matching tool calls execute the registered skill and return tool results",
},
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(json.choices[0].finish_reason, "tool_calls");
@@ -202,7 +202,7 @@ test("non-matching responses fall through the pipeline normally", async () => {
},
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(json.choices[0].message.content, "Normal pipeline response");
@@ -275,7 +275,7 @@ test("skill execution errors are returned gracefully in tool results", async ()
},
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(json.tool_results[0].tool_call_id, "call_broken");
@@ -350,10 +350,13 @@ test("injectSkills() correctly injects skill context into a request", async () =
assert.ok(Array.isArray(tools), "injectSkills should return an array");
assert.equal(tools.length, 1, "should inject exactly one skill tool");
assert.equal(tools[0].type, "function");
assert.equal(tools[0].function.name, "translateText@1.0.0");
assert.equal(tools[0].function.description, "Translate text to another language");
assert.ok(tools[0].function.parameters, "parameters should be present");
assert.equal((tools[0] as any).type, "function");
assert.equal((tools as any)[0].function.name, "translateText@1.0.0");
(assert as any).equal(
(tools[0] as any).function.description,
"Translate text to another language"
);
assert.ok((tools[0] as any).function.parameters, "parameters should be present");
});
test("injectSkills() merges with existing tools without duplicating", async () => {
@@ -384,7 +387,7 @@ test("injectSkills() merges with existing tools without duplicating", async () =
});
assert.equal(tools.length, 2, "should have injected skill + existing tool");
const names = tools.map((t) => t.function?.name || t.name);
const names = tools.map((t) => (t as any).function?.name || (t as any).name);
assert.ok(names.includes("calcRoute@1.0.0"));
assert.ok(names.includes("preExistingTool"));
});
@@ -599,7 +602,7 @@ test("skills pipeline can be disabled via skillsEnabled flag without crashing",
}
),
(err) => {
assert.ok(err.message.includes("disabled"), "should mention disabled in error");
assert.ok((err as any).message.includes("disabled"), "should mention disabled in error");
return true;
}
);
@@ -830,7 +833,7 @@ test("web_search fallback converts built-in tools for unsupported providers and
},
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(upstreamBodies.length, 1);
@@ -896,7 +899,7 @@ test("web_search fallback preserves Responses API output by appending function_c
},
})
);
const json = await response.json();
const json = (await response.json()) as any;
const functionCall = json.output.find((item) => item.type === "function_call");
const functionCallOutput = json.output.find((item) => item.type === "function_call_output");

View File

@@ -36,8 +36,8 @@ test("contract: /api/v1 and /api/v1/models return consistent model IDs", async (
assert.equal(v1Response.status, 200);
assert.equal(v1ModelsResponse.status, 200);
const v1Body = await v1Response.json();
const v1ModelsBody = await v1ModelsResponse.json();
const v1Body = (await v1Response.json()) as any;
const v1ModelsBody = (await v1ModelsResponse.json()) as any;
assert.equal(v1Body.object, "list");
assert.equal(v1ModelsBody.object, "list");
@@ -55,7 +55,7 @@ test("contract: /api/v1/models returns OpenAI-compatible model shape", async ()
const response = await getV1Models(new Request(`${BASE_URL}/api/v1/models`, { method: "GET" }));
assert.equal(response.status, 200);
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(body.object, "list");
assert.ok(Array.isArray(body.data));
@@ -75,7 +75,7 @@ test("contract: /api/v1/embeddings GET returns embedding model listing shape", a
const response = await getEmbeddings();
assert.equal(response.status, 200);
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(body.object, "list");
assert.ok(Array.isArray(body.data));
@@ -93,7 +93,7 @@ test("contract: /api/v1/images/generations GET returns image model listing shape
const response = await getImageModels();
assert.equal(response.status, 200);
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(body.object, "list");
assert.ok(Array.isArray(body.data));
@@ -118,7 +118,7 @@ test("contract: /api/v1/messages/count_tokens returns 400 on invalid JSON", asyn
);
assert.equal(response.status, 400);
const body = await response.json();
const body = (await response.json()) as any;
assert.ok(body.error, "error payload should exist");
assert.ok(
typeof body.error === "string" || typeof body.error === "object",
@@ -138,7 +138,7 @@ test("contract: /api/v1/messages/count_tokens rejects empty messages payload", a
);
assert.equal(response.status, 400);
const body = await response.json();
const body = (await response.json()) as any;
assert.ok(body.error, "error payload should exist");
assert.ok(
typeof body.error === "string" || typeof body.error === "object",
@@ -168,6 +168,6 @@ test("contract: /api/v1/messages/count_tokens computes token estimate from text
);
assert.equal(response.status, 200);
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(body.input_tokens, 3);
});

View File

@@ -139,7 +139,7 @@ console.log(` Stream: ${body.stream || false}`);
console.log(`\n\n✅ Received ${chunkCount} chunks`);
} else {
const responseData = await response.json();
const responseData = (await response.json()) as any;
console.log("\n📦 Response:");
console.log(JSON.stringify(responseData, null, 2));
}

View File

@@ -69,7 +69,7 @@ test("GET /api/acp/agents requires authentication when login is enabled", async
process.env.INITIAL_PASSWORD = "route-auth-required";
const response = await routeModule.GET(makeRequest("GET"));
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 401);
assert.equal(body.error, "Unauthorized");
@@ -95,7 +95,7 @@ test("POST /api/acp/agents rejects unsafe version commands for authenticated ses
token
)
);
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 400);
assert.match(body.error, /Invalid versionCommand/i);

View File

@@ -139,7 +139,7 @@ test("provider create/update/delete routes emit sanitized credential audit event
);
assert.equal(createResponse.status, 201);
const createBody = await createResponse.json();
const createBody = (await createResponse.json()) as any;
const connectionId = createBody.connection.id;
assert.equal(typeof connectionId, "string");
@@ -179,23 +179,23 @@ test("provider create/update/delete routes emit sanitized credential audit event
assert.equal(createdEvent.resourceType, "provider_credentials");
assert.equal(createdEvent.requestId, "req-provider-create");
assert.equal(createdEvent.target, "openai:Primary OpenAI");
assert.equal("apiKey" in createdEvent.metadata.connection, false);
assert.equal("apiKey" in (createdEvent.metadata as any).connection, false);
const updatedEvent = compliance.getAuditLog({ action: "provider.credentials.updated" })[0];
assert.equal(updatedEvent.requestId, "req-provider-update");
assert.deepEqual(updatedEvent.metadata.changedFields.sort(), [
assert.deepEqual((updatedEvent as any).metadata.changedFields.sort(), [
"defaultModel",
"isActive",
"name",
]);
assert.equal(updatedEvent.metadata.before.name, "Primary OpenAI");
assert.equal(updatedEvent.metadata.after.name, "Primary OpenAI Updated");
assert.equal("apiKey" in updatedEvent.metadata.before, false);
assert.equal("apiKey" in updatedEvent.metadata.after, false);
assert.equal((updatedEvent as any).metadata.before.name, "Primary OpenAI");
(assert as any).equal((updatedEvent.metadata as any).after.name, "Primary OpenAI Updated");
(assert as any).equal("apiKey" in (updatedEvent.metadata as any).before, false);
(assert as any).equal("apiKey" in (updatedEvent.metadata as any).after, false);
const revokedEvent = compliance.getAuditLog({ action: "provider.credentials.revoked" })[0];
assert.equal(revokedEvent.requestId, "req-provider-delete");
assert.equal(revokedEvent.target, "openai:Primary OpenAI Updated");
assert.equal(revokedEvent.status, "success");
assert.equal("apiKey" in revokedEvent.metadata.connection, false);
assert.equal("apiKey" in (revokedEvent.metadata as any).connection, false);
});

View File

@@ -40,7 +40,7 @@ async function resetStorage() {
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
}
break;
} catch (error) {
} catch (error: any) {
if ((error?.code === "EBUSY" || error?.code === "EPERM") && attempt < 9) {
await new Promise((resolve) => setTimeout(resolve, 50 * (attempt + 1)));
} else {
@@ -73,7 +73,7 @@ function makePolicyRequest(apiKey) {
}
async function readErrorMessage(response) {
const body = await response.json();
const body = (await response.json()) as any;
return body.error.message;
}

View File

@@ -44,7 +44,7 @@ test("GET /api/keys stays masked even when reveal is enabled", async () => {
const created = await apiKeysDb.createApiKey("Primary Key", MACHINE_ID);
const response = await listRoute.GET(new Request("http://localhost/api/keys"));
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(body.allowKeyReveal, true);
@@ -59,7 +59,7 @@ test("GET /api/keys falls back to default pagination for invalid query params",
const response = await listRoute.GET(
new Request("http://localhost/api/keys?limit=abc&offset=xyz")
);
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(body.allowKeyReveal, false);
@@ -92,7 +92,7 @@ test("GET /api/keys returns 500 when key loading fails unexpectedly", async () =
try {
const response = await listRoute.GET(new Request("http://localhost/api/keys"));
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 500);
assert.equal(body.error, "Failed to fetch keys");
@@ -114,7 +114,7 @@ test("GET /api/keys/[id]/reveal rejects requests when reveal is disabled", async
const response = await revealRoute.GET(request, {
params: Promise.resolve({ id: created.id }),
});
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 403);
assert.equal(body.error, "API key reveal is disabled");
@@ -128,7 +128,7 @@ test("GET /api/keys/[id]/reveal returns the full key when reveal is enabled", as
const response = await revealRoute.GET(request, {
params: Promise.resolve({ id: created.id }),
});
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(body.key, created.key);
@@ -141,7 +141,7 @@ test("GET /api/keys/[id]/reveal returns 404 for unknown keys even when reveal is
const response = await revealRoute.GET(request, {
params: Promise.resolve({ id: "missing" }),
});
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 404);
assert.equal(body.error, "Key not found");
@@ -154,7 +154,7 @@ test("GET /api/keys/[id]/reveal returns 500 when params resolution fails", async
const response = await revealRoute.GET(request, {
params: Promise.reject(new Error("params exploded")),
});
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 500);
assert.equal(body.error, "Failed to reveal key");

View File

@@ -8,7 +8,7 @@ test("handleAudioSpeech requires model", async () => {
body: { input: "hello" },
credentials: { apiKey: "x" },
});
const payload = await response.json();
const payload = (await response.json()) as any;
assert.equal(response.status, 400);
assert.equal(payload.error.message, "model is required");
@@ -19,7 +19,7 @@ test("handleAudioSpeech requires input text", async () => {
body: { model: "openai/tts-1" },
credentials: { apiKey: "x" },
});
const payload = await response.json();
const payload = (await response.json()) as any;
assert.equal(response.status, 400);
assert.equal(payload.error.message, "input is required");
@@ -121,7 +121,7 @@ test("handleAudioSpeech rejects invalid ElevenLabs voice identifiers", async ()
},
credentials: { apiKey: "xi-key" },
});
const payload = await response.json();
const payload = (await response.json()) as any;
assert.equal(response.status, 400);
assert.equal(payload.error.message, "Invalid voice ID");
@@ -216,7 +216,7 @@ test("handleAudioSpeech requires credentials for authenticated providers", async
},
credentials: null,
});
const payload = await response.json();
const payload = (await response.json()) as any;
assert.equal(response.status, 401);
assert.equal(payload.error.message, "No credentials for speech provider: openai");
@@ -299,7 +299,7 @@ test("handleAudioSpeech validates HuggingFace model identifiers", async () => {
},
credentials: { apiKey: "hf-key" },
});
const payload = await response.json();
const payload = (await response.json()) as any;
assert.equal(response.status, 400);
assert.equal(payload.error.message, "Invalid model ID");
@@ -455,7 +455,7 @@ test("handleAudioSpeech surfaces parsed upstream error messages", async () => {
},
credentials: { apiKey: "openai-key" },
});
const payload = await response.json();
const payload = (await response.json()) as any;
assert.equal(response.status, 429);
assert.equal(payload.error.message, "quota exceeded");
@@ -479,7 +479,7 @@ test("handleAudioSpeech returns a 500 when the provider request throws", async (
},
credentials: { apiKey: "openai-key" },
});
const payload = await response.json();
const payload = (await response.json()) as any;
assert.equal(response.status, 500);
assert.equal(payload.error.message, "Speech request failed: socket hang up");

View File

@@ -17,7 +17,7 @@ test("handleAudioTranscription requires model", async () => {
formData.append("file", buildFile("abc", "audio.wav", "audio/wav"));
const response = await handleAudioTranscription({ formData, credentials: { apiKey: "x" } });
const payload = await response.json();
const payload = (await response.json()) as any;
assert.equal(response.status, 400);
assert.equal(payload.error.message, "model is required");
@@ -28,7 +28,7 @@ test("handleAudioTranscription requires a file upload", async () => {
formData.append("model", "openai/whisper-1");
const response = await handleAudioTranscription({ formData, credentials: { apiKey: "x" } });
const payload = await response.json();
const payload = (await response.json()) as any;
assert.equal(response.status, 400);
assert.equal(payload.error.message, "file is required");
@@ -119,7 +119,7 @@ test("handleAudioTranscription routes Deepgram with binary upload and language p
formData,
credentials: { apiKey: "dg-key" },
});
const payload = await response.json();
const payload = (await response.json()) as any;
const url = new URL(capturedUrl);
assert.equal(url.origin + url.pathname, "https://api.deepgram.com/v1/listen");
@@ -213,7 +213,7 @@ test("handleAudioTranscription rejects invalid HuggingFace model paths", async (
formData,
credentials: { apiKey: "hf-key" },
});
const payload = await response.json();
const payload = (await response.json()) as any;
assert.equal(response.status, 400);
assert.equal(payload.error.message, "Invalid model ID");
@@ -225,7 +225,7 @@ test("handleAudioTranscription requires credentials for authenticated providers"
formData.append("file", buildFile("abc", "clip.wav", "audio/wav"));
const response = await handleAudioTranscription({ formData, credentials: null });
const payload = await response.json();
const payload = (await response.json()) as any;
assert.equal(response.status, 401);
assert.equal(payload.error.message, "No credentials for transcription provider: openai");
@@ -338,7 +338,7 @@ test("handleAudioTranscription returns an error when AssemblyAI reports a termin
formData,
credentials: { apiKey: "assembly-key" },
});
const payload = await response.json();
const payload = (await response.json()) as any;
assert.equal(response.status, 500);
assert.equal(payload.error.message, "corrupt audio payload");
@@ -397,7 +397,7 @@ test("handleAudioTranscription rejects unsupported providers", async () => {
formData,
credentials: { apiKey: "x" },
});
const payload = await response.json();
const payload = (await response.json()) as any;
assert.equal(response.status, 400);
assert.match(
@@ -424,7 +424,7 @@ test("handleAudioTranscription surfaces parsed upstream errors for OpenAI-compat
formData,
credentials: { apiKey: "openai-key" },
});
const payload = await response.json();
const payload = (await response.json()) as any;
assert.equal(response.status, 429);
assert.equal(payload.error.message, "too many requests");
@@ -449,7 +449,7 @@ test("handleAudioTranscription returns a 500 when upstream fetch throws", async
formData,
credentials: { apiKey: "openai-key" },
});
const payload = await response.json();
const payload = (await response.json()) as any;
assert.equal(response.status, 500);
assert.equal(payload.error.message, "Transcription request failed: network timeout");

View File

@@ -45,9 +45,9 @@ test("clearAccountError clears stale provider error metadata after recovery", as
assert.equal(credentials.errorCode, "refresh_failed");
assert.equal(credentials.lastErrorType, "token_refresh_failed");
assert.equal(credentials.lastErrorSource, "oauth");
await auth.clearAccountError(created.id, credentials);
await auth.clearAccountError((created as any).id, credentials);
const updated = await providersDb.getProviderConnectionById(created.id);
const updated = await providersDb.getProviderConnectionById((created as any).id);
assert.equal(updated.testStatus, "active");
assert.equal(updated.lastError, undefined);
assert.equal(updated.lastErrorType, undefined);
@@ -68,7 +68,7 @@ test("clearAccountError is a no-op when the connection is already clean", async
testStatus: "active",
});
await auth.clearAccountError(created.id, {
await auth.clearAccountError((created as any).id, {
connectionId: created.id,
testStatus: "active",
lastError: null,
@@ -78,7 +78,7 @@ test("clearAccountError is a no-op when the connection is already clean", async
lastErrorSource: null,
});
const updated = await providersDb.getProviderConnectionById(created.id);
const updated = await providersDb.getProviderConnectionById((created as any).id);
assert.equal(updated.testStatus, "active");
assert.equal(updated.backoffLevel, 0);
assert.equal(updated.lastError, undefined);
@@ -114,7 +114,7 @@ test("clearRecoveredProviderState ignores empty payloads and clears recoverable
rateLimitedUntil: new Date(Date.now() + 60_000).toISOString(),
});
const updated = await providersDb.getProviderConnectionById(created.id);
const updated = await providersDb.getProviderConnectionById((created as any).id);
assert.equal(updated.testStatus, "active");
assert.equal(updated.lastError, undefined);
assert.equal(updated.lastErrorType, undefined);

View File

@@ -82,7 +82,10 @@ test("auth login route lazily migrates INITIAL_PASSWORD to a persisted hash befo
assert.equal(setCalls.length, 1);
assert.equal(managementPassword.isBcryptHash(settings.password), true);
assert.equal(
await managementPassword.verifyManagementPassword("bootstrap-secret", settings.password),
await managementPassword.verifyManagementPassword(
"bootstrap-secret",
(settings as any).password
),
true
);
});

View File

@@ -96,7 +96,7 @@ test("markAccountUnavailable does not overwrite terminal status", async () => {
});
const result = await auth.markAccountUnavailable(
conn.id,
(conn as any).id,
503,
"temporary upstream error",
"openai",
@@ -106,7 +106,7 @@ test("markAccountUnavailable does not overwrite terminal status", async () => {
assert.equal(result.shouldFallback, true);
assert.equal(result.cooldownMs, 0);
const after = await providersDb.getProviderConnectionById(conn.id);
const after = await providersDb.getProviderConnectionById((conn as any).id);
assert.equal(after.testStatus, "credits_exhausted");
});
@@ -122,13 +122,13 @@ test("markAccountUnavailable marks 401 connections as expired without adding coo
});
const result = await auth.markAccountUnavailable(
conn.id,
(conn as any).id,
401,
"unauthorized",
"openai",
"gpt-4.1"
);
const after = await providersDb.getProviderConnectionById(conn.id);
const after = await providersDb.getProviderConnectionById((conn as any).id);
assert.equal(result.shouldFallback, true);
assert.equal(result.cooldownMs, 0);
@@ -148,13 +148,13 @@ test("markAccountUnavailable marks 402 connections as credits_exhausted without
});
const result = await auth.markAccountUnavailable(
conn.id,
(conn as any).id,
402,
"payment required",
"openai",
"gpt-4.1"
);
const after = await providersDb.getProviderConnectionById(conn.id);
const after = await providersDb.getProviderConnectionById((conn as any).id);
assert.equal(result.shouldFallback, true);
assert.equal(result.cooldownMs, 0);
@@ -173,8 +173,14 @@ test("markAccountUnavailable marks 403 connections as banned without adding cool
testStatus: "active",
});
const result = await auth.markAccountUnavailable(conn.id, 403, "forbidden", "openai", "gpt-4.1");
const after = await providersDb.getProviderConnectionById(conn.id);
const result = await auth.markAccountUnavailable(
(conn as any).id,
403,
"forbidden",
"openai",
"gpt-4.1"
);
const after = await providersDb.getProviderConnectionById((conn as any).id);
assert.equal(result.shouldFallback, true);
assert.equal(result.cooldownMs, 0);
@@ -194,13 +200,13 @@ test("markAccountUnavailable keeps project-route 403 errors non-terminal", async
});
const result = await auth.markAccountUnavailable(
conn.id,
(conn as any).id,
403,
"The service has not been used in project",
"openai",
"gpt-4.1"
);
const after = await providersDb.getProviderConnectionById(conn.id);
const after = await providersDb.getProviderConnectionById((conn as any).id);
assert.equal(result.shouldFallback, true);
assert.equal(result.cooldownMs, 0);
@@ -221,13 +227,13 @@ test("markAccountUnavailable keeps oauth-invalid 401 errors non-terminal", async
});
const result = await auth.markAccountUnavailable(
conn.id,
(conn as any).id,
401,
"Invalid authentication credentials provided",
"openai",
"gpt-4.1"
);
const after = await providersDb.getProviderConnectionById(conn.id);
const after = await providersDb.getProviderConnectionById((conn as any).id);
assert.equal(result.shouldFallback, true);
assert.equal(result.cooldownMs, 0);

View File

@@ -22,8 +22,10 @@ const {
getTerminalBatches,
} = await import("../../src/lib/localDb.ts");
const { getDbInstance } = await import("../../src/lib/db/core.ts");
const { initBatchProcessor, stopBatchProcessor } =
const { initBatchProcessor, stopBatchProcessor, processPendingBatches } =
await import("../../open-sse/services/batchProcessor.ts");
const batchesRoute = await import("../../src/app/api/v1/batches/route.ts");
const filesRoute = await import("../../src/app/api/v1/files/route.ts");
test("Batch API and Processing", async () => {
// 0. Setup environment, mock provider and API key
@@ -255,6 +257,128 @@ test("Batch handles and counts failures correctly", async () => {
}
});
test("Batch dispatches non-chat endpoints through the matching route handler", async () => {
const originalFetch = globalThis.fetch;
globalThis.fetch = async () =>
new Response(
JSON.stringify({
object: "list",
data: [{ object: "embedding", embedding: [0.1, 0.2], index: 0 }],
usage: { prompt_tokens: 2, total_tokens: 2 },
}),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
);
initBatchProcessor();
try {
await createProviderConnection({
provider: "openai",
authType: "apikey",
name: "Mock OpenAI Embeddings",
apiKey: "sk-mock-embeddings-key",
isActive: true,
});
const batchItems = [
JSON.stringify({
custom_id: "embed-request",
method: "POST",
url: "/v1/embeddings",
body: {
model: "openai/text-embedding-3-small",
input: "Hello embeddings",
},
}),
].join("\n");
const file = createFile({
bytes: Buffer.byteLength(batchItems),
filename: "embeddings_batch.jsonl",
purpose: "batch",
content: Buffer.from(batchItems),
apiKeyId: null,
});
const batch = createBatch({
endpoint: "/v1/embeddings",
completionWindow: "24h",
inputFileId: file.id,
apiKeyId: null,
});
let maxAttempts = 20;
let currentBatch = getBatch(batch.id);
while (
maxAttempts > 0 &&
currentBatch?.status !== "completed" &&
currentBatch?.status !== "failed"
) {
await new Promise((resolve) => setTimeout(resolve, 1000));
currentBatch = getBatch(batch.id);
maxAttempts--;
}
assert.strictEqual(currentBatch?.status, "completed", "embedding batch should complete");
assert.strictEqual(currentBatch?.requestCountsCompleted, 1);
assert.ok(currentBatch?.outputFileId, "embedding batch should produce an output file");
const outputContent = getFileContent(currentBatch.outputFileId!);
const result = JSON.parse(outputContent.toString());
assert.strictEqual(result.response.status_code, 200);
assert.ok(Array.isArray(result.response.body.data));
assert.strictEqual(result.response.body.object, "list");
} finally {
stopBatchProcessor();
globalThis.fetch = originalFetch;
}
});
test("Batch rejects input lines whose url does not match the batch endpoint", async () => {
initBatchProcessor();
try {
const batchItems = [
JSON.stringify({
custom_id: "wrong-endpoint",
method: "POST",
url: "/v1/embeddings",
body: {
model: "openai/text-embedding-3-small",
input: "Wrong endpoint",
},
}),
].join("\n");
const file = createFile({
bytes: Buffer.byteLength(batchItems),
filename: "wrong_endpoint_batch.jsonl",
purpose: "batch",
content: Buffer.from(batchItems),
apiKeyId: null,
});
const batch = createBatch({
endpoint: "/v1/chat/completions",
completionWindow: "24h",
inputFileId: file.id,
apiKeyId: null,
});
await processPendingBatches();
const currentBatch = getBatch(batch.id);
assert.strictEqual(currentBatch?.status, "failed");
assert.match(
String(currentBatch?.errors?.[0]?.message || ""),
/does not match batch endpoint/i
);
} finally {
stopBatchProcessor();
}
});
test("Batch forces stream: false for all requests", async () => {
initBatchProcessor();
try {
@@ -424,7 +548,8 @@ test("List batches pagination and response format", async () => {
apiKeyId: apiKey.id,
});
const batchIds: string[] = [];
const batchOrder: Array<{ createdAt: number; id: string }> = [];
const baseCreatedAt = Math.floor(Date.now() / 1000) - 10_000;
for (let i = 0; i < 5; i++) {
const b = createBatch({
endpoint: "/v1/chat/completions",
@@ -433,11 +558,12 @@ test("List batches pagination and response format", async () => {
apiKeyId: apiKey.id,
metadata: { index: i },
});
batchIds.push(b.id);
updateBatch(b.id, { createdAt: baseCreatedAt + i });
batchOrder.push({ createdAt: baseCreatedAt + i, id: b.id });
}
// Sort batchIds in descending order as listBatches returns them by ID DESC
batchIds.sort().reverse();
batchOrder.sort((a, b) => b.createdAt - a.createdAt || b.id.localeCompare(a.id));
const batchIds = batchOrder.map((entry) => entry.id);
// 2. Test listBatches logic (direct DB call)
const { listBatches } = await import("../../src/lib/localDb");
@@ -476,6 +602,92 @@ test("List batches pagination and response format", async () => {
assert.strictEqual(data3[0].id, batchIds[4]);
});
test("Batch cleanup honors output_expires_after for output artifacts", async () => {
const apiKey = await createApiKey("Batch Retention Key", "test-machine");
const now = Math.floor(Date.now() / 1000);
const inputFile = createFile({
bytes: 10,
filename: "retention_input.jsonl",
purpose: "batch",
content: Buffer.from("{}"),
apiKeyId: apiKey.id,
});
const outputFile = createFile({
bytes: 10,
filename: "retention_output.jsonl",
purpose: "batch_output",
content: Buffer.from("{}"),
apiKeyId: apiKey.id,
status: "completed",
});
const errorFile = createFile({
bytes: 10,
filename: "retention_error.jsonl",
purpose: "batch_output",
content: Buffer.from("{}"),
apiKeyId: apiKey.id,
status: "completed",
});
const batch = createBatch({
endpoint: "/v1/chat/completions",
completionWindow: "24h",
inputFileId: inputFile.id,
apiKeyId: apiKey.id,
outputExpiresAfterSeconds: 3600,
outputExpiresAfterAnchor: "created_at",
});
updateBatch(batch.id, {
status: "completed",
createdAt: now - 3700,
completedAt: now - 30,
outputFileId: outputFile.id,
errorFileId: errorFile.id,
});
await processPendingBatches();
assert.ok(getFile(inputFile.id), "input file should still follow completion_window retention");
assert.equal(getFile(outputFile.id), null);
assert.equal(getFile(errorFile.id), null);
});
test("Batch list route rejects missing API key when REQUIRE_API_KEY is enabled", async () => {
const previous = process.env.REQUIRE_API_KEY;
process.env.REQUIRE_API_KEY = "true";
try {
const response = await batchesRoute.GET(new Request("http://localhost/api/v1/batches"));
const json = await response.json();
assert.strictEqual(response.status, 401);
assert.strictEqual(json.error.message, "Missing API key");
} finally {
process.env.REQUIRE_API_KEY = previous ?? "false";
}
});
test("Files list route rejects invalid API key when REQUIRE_API_KEY is enabled", async () => {
const previous = process.env.REQUIRE_API_KEY;
process.env.REQUIRE_API_KEY = "true";
try {
const response = await filesRoute.GET(
new Request("http://localhost/api/v1/files", {
headers: { Authorization: "Bearer invalid-test-key" },
})
);
const json = await response.json();
assert.strictEqual(response.status, 401);
assert.strictEqual(json.error.message, "Invalid API key");
} finally {
process.env.REQUIRE_API_KEY = previous ?? "false";
}
});
test("Batch Cancel API", async () => {
const apiKey = await createApiKey("Cancel Test Key", "test-machine");
@@ -704,62 +916,66 @@ test("Retrieve file content spec compliance", async () => {
});
test("Batch dispatches to embeddings handler for /v1/embeddings URL", async () => {
initBatchProcessor();
try {
const batchItems = [
JSON.stringify({
custom_id: "embed-request-1",
method: "POST",
url: "/v1/embeddings",
body: { model: "mistral/mistral-embed", input: "The food was delicious." }
})
].join("\n");
initBatchProcessor();
try {
const batchItems = [
JSON.stringify({
custom_id: "embed-request-1",
method: "POST",
url: "/v1/embeddings",
body: { model: "mistral/mistral-embed", input: "The food was delicious." },
}),
].join("\n");
const file = createFile({
bytes: Buffer.byteLength(batchItems),
filename: "embed_batch.jsonl",
purpose: "batch",
content: Buffer.from(batchItems),
apiKeyId: null
});
const file = createFile({
bytes: Buffer.byteLength(batchItems),
filename: "embed_batch.jsonl",
purpose: "batch",
content: Buffer.from(batchItems),
apiKeyId: null,
});
const batch = createBatch({
endpoint: "/v1/embeddings",
completionWindow: "24h",
inputFileId: file.id,
apiKeyId: null
});
const batch = createBatch({
endpoint: "/v1/embeddings",
completionWindow: "24h",
inputFileId: file.id,
apiKeyId: null,
});
let maxAttempts = 20;
let currentBatch = getBatch(batch.id);
while (maxAttempts > 0 && currentBatch?.status !== "completed" && currentBatch?.status !== "failed") {
await new Promise(resolve => setTimeout(resolve, 1000));
currentBatch = getBatch(batch.id);
maxAttempts--;
}
assert.ok(
currentBatch?.status === "completed" || currentBatch?.status === "failed",
"Batch should reach a terminal state"
);
assert.strictEqual(currentBatch?.requestCountsTotal, 1);
// Verify the batch item was dispatched to the embeddings handler, not the chat handler.
// The chat handler would return errors about missing "messages", "Missing model", etc.
// The embeddings handler returns errors about missing credentials or invalid embedding models.
const outputFileId = currentBatch?.outputFileId || currentBatch?.errorFileId;
assert.ok(outputFileId, "Should have an output or error file");
const outputContent = getFileContent(outputFileId!);
assert.ok(outputContent, "Output file should have content");
const result = JSON.parse(outputContent.toString());
const errorMsg = result.response?.body?.error?.message || "";
assert.ok(
!errorMsg.includes("messages") && !errorMsg.includes("Missing model"),
`Error should not be a chat-specific error. Got: ${errorMsg}`
);
} finally {
stopBatchProcessor();
let maxAttempts = 20;
let currentBatch = getBatch(batch.id);
while (
maxAttempts > 0 &&
currentBatch?.status !== "completed" &&
currentBatch?.status !== "failed"
) {
await new Promise((resolve) => setTimeout(resolve, 1000));
currentBatch = getBatch(batch.id);
maxAttempts--;
}
assert.ok(
currentBatch?.status === "completed" || currentBatch?.status === "failed",
"Batch should reach a terminal state"
);
assert.strictEqual(currentBatch?.requestCountsTotal, 1);
// Verify the batch item was dispatched to the embeddings handler, not the chat handler.
// The chat handler would return errors about missing "messages", "Missing model", etc.
// The embeddings handler returns errors about missing credentials or invalid embedding models.
const outputFileId = currentBatch?.outputFileId || currentBatch?.errorFileId;
assert.ok(outputFileId, "Should have an output or error file");
const outputContent = getFileContent(outputFileId!);
assert.ok(outputContent, "Output file should have content");
const result = JSON.parse(outputContent.toString());
const errorMsg = result.response?.body?.error?.message || "";
assert.ok(
!errorMsg.includes("messages") && !errorMsg.includes("Missing model"),
`Error should not be a chat-specific error. Got: ${errorMsg}`
);
} finally {
stopBatchProcessor();
}
});
test("getTerminalBatches returns only terminal statuses ordered oldest first", async () => {

View File

@@ -85,7 +85,7 @@ test("movePath rethrows non-EXDEV rename failures", async () => {
throw new Error("remove fallback should not run");
},
}),
(error) => error?.code === "EACCES"
(error) => (error as any).code === "EACCES"
);
});
});

View File

@@ -41,7 +41,7 @@ test("handleBypassRequest returns a canned JSON response for warmup bypasses", a
assert.equal(result.success, true);
assert.equal(result.response.headers.get("content-type"), "application/json");
const payload = await result.response.json();
const payload = (await result.response.json()) as any;
assert.equal(payload.model, "gpt-5-mini");
assert.equal(payload.choices[0].message.role, "assistant");
assert.match(payload.choices[0].message.content, /clear terminal/i);
@@ -79,7 +79,7 @@ test("handleBypassRequest bypasses single-message count probes", async () => {
);
assert.ok(result);
const payload = await result.response.json();
const payload = (await result.response.json()) as any;
assert.equal(payload.usage.total_tokens, 2);
assert.equal(payload.choices[0].finish_reason, "stop");
});

View File

@@ -117,9 +117,9 @@ test("saveCallLog stores only summary metadata in SQLite and writes detailed art
assert.equal(detail?.comboStepId, "step-openai-a");
assert.equal(detail?.comboExecutionKey, "combo-a:0:step-openai-a");
assert.equal(detail?.pipelinePayloads?.clientRawRequest?.body?.raw, true);
assert.equal(detail?.pipelinePayloads?.providerRequest?.body?.translated, true);
assert.equal(detail?.pipelinePayloads?.providerResponse?.body?.upstream, true);
assert.equal(detail?.pipelinePayloads?.clientResponse?.body?.final, true);
assert.equal((detail?.pipelinePayloads?.providerRequest as any).body?.translated, true);
assert.equal((detail?.pipelinePayloads as any).providerResponse?.body?.upstream, true);
assert.equal((detail?.pipelinePayloads as any).clientResponse?.body?.final, true);
assert.match(
detail?.artifactRelPath || "",
/^2026-03-30\/2026-03-30T12-34-56\.789Z_req_artifact_1\.json$/
@@ -129,7 +129,7 @@ test("saveCallLog stores only summary metadata in SQLite and writes detailed art
const columns = db
.prepare("SELECT name FROM pragma_table_info('call_logs') ORDER BY cid")
.all()
.map((row) => row.name);
.map((row) => (row as any).name);
assert.equal(columns.includes("request_body"), false);
assert.equal(columns.includes("response_body"), false);
assert.equal(columns.includes("error"), false);
@@ -142,12 +142,12 @@ test("saveCallLog stores only summary metadata in SQLite and writes detailed art
`
)
.get(logId);
assert.equal(summaryRow.detail_state, "ready");
assert.equal(summaryRow.cache_source, "semantic");
assert.equal(summaryRow.has_request_body, 1);
assert.equal(summaryRow.has_response_body, 1);
assert.equal(summaryRow.has_pipeline_details, 1);
assert.equal(typeof summaryRow.artifact_relpath, "string");
(assert as any).equal((summaryRow as any).detail_state, "ready");
assert.equal((summaryRow as any).cache_source, "semantic");
assert.equal((summaryRow as any).has_request_body, 1);
assert.equal((summaryRow as any).has_response_body, 1);
assert.equal((summaryRow as any).has_pipeline_details, 1);
assert.equal(typeof (summaryRow as any).artifact_relpath, "string");
const artifactPath = path.join(TEST_DATA_DIR, "call_logs", detail.artifactRelPath);
const artifact = JSON.parse(fs.readFileSync(artifactPath, "utf8"));
@@ -192,12 +192,14 @@ test("rotateCallLogs removes expired rows and orphaned artifacts but keeps fresh
.getDbInstance()
.prepare("SELECT artifact_relpath FROM call_logs WHERE id = ?")
.get("fresh-log");
const freshAbsPath = path.join(TEST_DATA_DIR, "call_logs", freshRow.artifact_relpath);
const freshAbsPath = path.join(TEST_DATA_DIR, "call_logs", (freshRow as any).artifact_relpath);
assert.equal(
core
.getDbInstance()
.prepare("SELECT COUNT(*) AS cnt FROM call_logs WHERE id = ?")
.get("expired-log").cnt,
(
core
.getDbInstance()
.prepare("SELECT COUNT(*) AS cnt FROM call_logs WHERE id = ?")
.get("expired-log") as any
).cnt,
0
);
assert.equal(fs.existsSync(oldAbsPath), false);
@@ -207,7 +209,7 @@ test("rotateCallLogs removes expired rows and orphaned artifacts but keeps fresh
const db = core.getDbInstance();
assert.equal(
db.prepare("SELECT COUNT(*) AS cnt FROM call_logs WHERE id = ?").get("fresh-log").cnt,
(db.prepare("SELECT COUNT(*) AS cnt FROM call_logs WHERE id = ?").get("fresh-log") as any).cnt,
1
);
assert.equal(fs.existsSync(freshAbsPath), true);
@@ -365,9 +367,18 @@ test("getCallLogById falls back to legacy inline rows and request_detail_logs",
assert.deepEqual(detail?.responseBody, { recovered: "response" });
assert.deepEqual(detail?.error, { message: "legacy-error" });
assert.equal(detail?.pipelinePayloads?.clientRequest?.body?.from, "detail-client");
assert.equal(detail?.pipelinePayloads?.providerRequest?.body?.from, "detail-provider-request");
assert.equal(detail?.pipelinePayloads?.providerResponse?.body?.from, "detail-provider-response");
assert.equal(detail?.pipelinePayloads?.clientResponse?.body?.from, "detail-client-response");
assert.equal(
(detail?.pipelinePayloads?.providerRequest as any).body?.from,
"detail-provider-request"
);
(assert as any).equal(
(detail?.pipelinePayloads?.providerResponse as any).body?.from,
"detail-provider-response"
);
assert.equal(
(detail?.pipelinePayloads?.clientResponse as any).body?.from,
"detail-client-response"
);
assert.equal(detail?.hasPipelineDetails, true);
});
@@ -394,8 +405,8 @@ test("getCallLogById marks missing artifacts explicitly and clears stale DB poin
const row = db
.prepare("SELECT artifact_relpath, detail_state FROM call_logs WHERE id = ?")
.get("missing-artifact");
assert.equal(row.artifact_relpath, null);
assert.equal(row.detail_state, "missing");
assert.equal((row as any).artifact_relpath, null);
assert.equal((row as any).detail_state, "missing");
});
test("saveCallLog keeps large payloads out of SQLite while preserving explicit detail export", async () => {
@@ -424,12 +435,12 @@ test("saveCallLog keeps large payloads out of SQLite while preserving explicit d
`
)
.get("artifact-only-large-payload");
assert.equal(row.detail_state, "ready");
assert.equal(row.has_request_body, 1);
assert.equal(typeof row.artifact_relpath, "string");
assert.equal(row.error_summary, "upstream unavailable");
assert.equal((row as any).detail_state, "ready");
assert.equal((row as any).has_request_body, 1);
(assert as any).equal(typeof (row as any).artifact_relpath, "string");
assert.equal((row as any).error_summary, "upstream unavailable");
const artifactPath = path.join(TEST_DATA_DIR, "call_logs", row.artifact_relpath);
const artifactPath = path.join(TEST_DATA_DIR, "call_logs", (row as any).artifact_relpath);
const artifact = JSON.parse(fs.readFileSync(artifactPath, "utf8"));
assert.equal(artifact.requestBody.payload.length, requestBody.payload.length);
@@ -438,7 +449,7 @@ test("saveCallLog keeps large payloads out of SQLite while preserving explicit d
const exported = await callLogs.exportCallLogsSince("2026-03-31T00:00:00.000Z");
assert.equal(exported.length, 1);
assert.equal(exported[0].requestBody.payload.length, requestBody.payload.length);
assert.equal((exported[0] as any).requestBody.payload.length, requestBody.payload.length);
});
test("saveCallLog logs and returns when sqlite persistence throws unexpectedly", async () => {

View File

@@ -33,7 +33,7 @@ async function resetTestDataDir() {
const db = core.getDbInstance();
db.prepare("DELETE FROM call_logs").run();
return;
} catch (error) {
} catch (error: any) {
lastError = error;
await new Promise((resolve) => setTimeout(resolve, 25));
}
@@ -176,7 +176,7 @@ test("call log file rotation honors both retention days and file count", () => {
const db = core.getDbInstance();
assert.equal(
db.prepare("SELECT COUNT(*) AS cnt FROM call_logs WHERE id = ?").get("old-log").cnt,
(db.prepare("SELECT COUNT(*) AS cnt FROM call_logs WHERE id = ?").get("old-log") as any).cnt,
0
);
assert.equal(fs.existsSync(path.join(CALL_LOGS_DIR, oldRelPath)), false);
@@ -184,8 +184,8 @@ test("call log file rotation honors both retention days and file count", () => {
const keepARow = db
.prepare("SELECT detail_state, artifact_relpath FROM call_logs WHERE id = ?")
.get("keep-a");
assert.equal(keepARow.detail_state, "missing");
assert.equal(keepARow.artifact_relpath, null);
assert.equal((keepARow as any).detail_state, "missing");
(assert as any).equal((keepARow as any).artifact_relpath, null);
assert.equal(fs.existsSync(path.join(CALL_LOGS_DIR, keepARelPath)), false);
assert.equal(fs.existsSync(path.join(CALL_LOGS_DIR, keepBRelPath)), true);

View File

@@ -15,7 +15,7 @@ async function removeTestDataDir() {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
return;
} catch (error) {
} catch (error: any) {
lastError = error;
await new Promise((resolve) => setTimeout(resolve, 25));
}

View File

@@ -55,14 +55,14 @@ test("v1 models exposes CC-compatible fallback models under the provider node pr
);
assert.equal(response.status, 200);
const body = await response.json();
const body = (await response.json()) as any;
const ids = new Set(body.data.map((item) => item.id));
assert.ok(ids.has("cm/claude-opus-4-7"));
assert.ok(ids.has("cm/claude-opus-4-6"));
assert.ok(ids.has("cm/claude-sonnet-4-6"));
assert.equal(
[...ids].some((id) => id.startsWith("anthropic-compatible-cc-cm/")),
[...ids].some((id) => (id as any).startsWith("anthropic-compatible-cc-cm/")),
false
);
});

View File

@@ -117,15 +117,15 @@ test("buildClaudeCodeCompatibleRequest keeps prior role history while dropping t
{ role: "user", text: "u2" },
]
);
assert.equal(payload.messages[0].content.at(-1).cache_control, undefined);
assert.equal(payload.messages[1].content.at(-1).cache_control, undefined);
assert.equal(payload.messages[2].content.at(-1).cache_control, undefined);
assert.equal((payload.messages[0].content.at(-1) as any).cache_control, undefined);
assert.equal((payload.messages[1] as any).content.at(-1).cache_control, undefined);
assert.equal((payload.messages as any)[2].content.at(-1).cache_control, undefined);
assert.equal(payload.system.length, 2);
assert.match(payload.system[0].text, /Claude Agent SDK/);
assert.equal(payload.system[0].cache_control, undefined);
assert.equal(payload.system[1].cache_control, undefined);
assert.match((payload as any).system[0].text, /Claude Agent SDK/);
(assert as any).equal((payload.system[0] as any).cache_control, undefined);
assert.equal((payload.system[1] as any).cache_control, undefined);
assert.equal(payload.system[1].text, "sys");
assert.equal(payload.system[1].cache_control, undefined);
assert.equal((payload.system[1] as any).cache_control, undefined);
assert.equal(payload.tools.length, 1);
assert.deepEqual(payload.tools[0], {
name: "lookup_weather",
@@ -139,8 +139,8 @@ test("buildClaudeCodeCompatibleRequest keeps prior role history while dropping t
},
});
assert.deepEqual(payload.tool_choice, { type: "any" });
assert.equal(payload.context_management, undefined);
assert.equal(JSON.parse(payload.metadata.user_id).session_id, "session-1");
assert.equal(payload.context_management, undefined as any);
assert.equal(JSON.parse((payload as any).metadata.user_id).session_id, "session-1");
});
test("buildClaudeCodeCompatibleRequest preserves xhigh for Claude models that support it", () => {
@@ -212,13 +212,15 @@ test("buildClaudeCodeCompatibleRequest preserves Claude cache markers when reque
preserveCacheControl: true,
});
assert.deepEqual(payload.system[0].cache_control, { type: "ephemeral", ttl: "5m" });
assert.deepEqual(payload.messages[0].content[0].cache_control, { type: "ephemeral" });
assert.deepEqual(payload.messages[1].content[0].cache_control, {
assert.deepEqual((payload.system[0] as any).cache_control, { type: "ephemeral", ttl: "5m" });
(assert as any).deepEqual((payload.messages[0].content[0] as any).cache_control, {
type: "ephemeral",
});
assert.deepEqual((payload.messages[1].content[0] as any).cache_control, {
type: "ephemeral",
ttl: "10m",
});
assert.equal(payload.messages[2].content[0].cache_control, undefined);
assert.equal((payload.messages[2].content[0] as any).cache_control, undefined);
assert.deepEqual(payload.tools[0].cache_control, { type: "ephemeral", ttl: "30m" });
});
@@ -263,11 +265,11 @@ test("buildClaudeCodeCompatibleRequest does not supplement missing Claude cache
preserveCacheControl: true,
});
assert.equal(payload.system[0].cache_control, undefined);
assert.equal(payload.messages[0].content[0].cache_control, undefined);
assert.equal(payload.messages[1].content[0].cache_control, undefined);
assert.equal(payload.messages[2].content[0].cache_control, undefined);
assert.equal(payload.system.at(-1).cache_control, undefined);
assert.equal((payload.system[0] as any).cache_control, undefined);
assert.equal((payload.messages[0].content[0] as any).cache_control, undefined);
assert.equal((payload.messages[1].content[0] as any).cache_control, undefined);
assert.equal((payload.messages[2].content[0] as any).cache_control, undefined);
assert.equal((payload.system.at(-1) as any).cache_control, undefined);
assert.equal(payload.tools[0].cache_control, undefined);
});
@@ -293,8 +295,8 @@ test("buildClaudeCodeCompatibleRequest keeps built-in system blocks untagged whe
preserveCacheControl: true,
});
assert.deepEqual(payload.system[0].cache_control, { type: "ephemeral" });
assert.deepEqual(payload.system[1].cache_control, { type: "ephemeral", ttl: "1h" });
assert.deepEqual((payload.system[0] as any).cache_control, { type: "ephemeral" });
assert.deepEqual((payload.system[1] as any).cache_control, { type: "ephemeral", ttl: "1h" });
});
test("buildClaudeCodeCompatibleRequest does not add cache markers in non-preserve mode", () => {
@@ -321,12 +323,12 @@ test("buildClaudeCodeCompatibleRequest does not add cache markers in non-preserv
preserveCacheControl: false,
});
assert.equal(payload.system.length, 2);
assert.equal(payload.system[0].cache_control, undefined);
assert.equal(payload.system[1].cache_control, undefined);
assert.equal(payload.messages[0].content[0].cache_control, undefined);
assert.equal(payload.messages[1].content[0].cache_control, undefined);
assert.equal(payload.messages[2].content[0].cache_control, undefined);
(assert as any).equal(payload.system.length, 2);
assert.equal((payload as any).system[0].cache_control, undefined);
assert.equal((payload as any).system[1].cache_control, undefined);
assert.equal((payload as any).messages[0].content[0].cache_control, undefined);
assert.equal((payload as any).messages[1].content[0].cache_control, undefined);
assert.equal((payload.messages[2].content[0] as any).cache_control, undefined);
});
test("buildClaudeCodeCompatibleRequest falls back to a user turn when the source only has assistant/model text", () => {
@@ -557,7 +559,7 @@ test("handleChatCore forces SSE upstream for CC compatible providers while retur
assert.equal(calls[0].body.stream, true);
assert.equal(JSON.stringify(calls[0].body).includes('"cache_control"'), false);
const payload = await result.response.json();
const payload = (await result.response.json()) as any;
assert.equal(payload.choices[0].message.content, "Hello from CC");
assert.equal(payload.choices[0].finish_reason, "stop");
assert.equal(payload.usage.prompt_tokens, 2007);
@@ -725,7 +727,7 @@ test("provider-nodes create route creates CC node with dedicated prefix when ena
);
assert.equal(response.status, 201);
const data = await response.json();
const data = (await response.json()) as any;
assert.match(data.node.id, /^anthropic-compatible-cc-/);
assert.equal(data.node.baseUrl, "https://proxy.example.com");
assert.equal(data.node.chatPath, CLAUDE_CODE_COMPATIBLE_DEFAULT_CHAT_PATH);
@@ -780,7 +782,7 @@ test("provider-nodes validate route rejects invalid JSON and schema errors", asy
);
assert.equal(invalidBodyResponse.status, 400);
const invalidBodyPayload = await invalidBodyResponse.json();
const invalidBodyPayload = (await invalidBodyResponse.json()) as any;
assert.equal(invalidBodyPayload.error.message, "Invalid request");
assert.equal(invalidBodyPayload.error.details.length >= 1, true);
});
@@ -1053,7 +1055,7 @@ test("provider-nodes list route exposes CC flag state from server env", async ()
const response = await providerNodesRoute.GET();
assert.equal(response.status, 200);
const data = await response.json();
const data = (await response.json()) as any;
assert.equal(data.ccCompatibleProviderEnabled, true);
});

View File

@@ -122,7 +122,7 @@ test("combo live test bypasses connection cooldown and breaker state to perform
const liveResponse = await chatRoute.POST(
makeRequest({ "X-Internal-Test": "combo-health-check" })
);
const liveBody = await liveResponse.json();
const liveBody = (await liveResponse.json()) as any;
assert.equal(liveResponse.status, 200);
assert.equal(fetchCalls.length, 1);
@@ -130,7 +130,7 @@ test("combo live test bypasses connection cooldown and breaker state to perform
assert.equal(fetchCalls[0].init.headers.Authorization, "Bearer sk-live-test");
assert.equal(liveBody.choices[0].message.content, "OK");
const updated = await providersDb.getProviderConnectionById(created.id);
const updated = await providersDb.getProviderConnectionById((created as any).id);
assert.equal(updated.testStatus, "active");
});
@@ -174,7 +174,7 @@ test("combo live test bypasses semantic cache and forces a fresh upstream reques
try {
const cachedResponse = await chatRoute.POST(makeRequest());
const cachedBody = await cachedResponse.json();
const cachedBody = (await cachedResponse.json()) as any;
assert.equal(cachedResponse.status, 200);
assert.equal(fetchCalls.length, 0);
@@ -187,7 +187,7 @@ test("combo live test bypasses semantic cache and forces a fresh upstream reques
"X-Request-Id": "combo-test-cache-bypass",
})
);
const liveBody = await liveResponse.json();
const liveBody = (await liveResponse.json()) as any;
assert.equal(liveResponse.status, 200);
assert.equal(fetchCalls.length, 1);

View File

@@ -159,7 +159,7 @@ test("handleChat generates and injects context-relay handoffs across Codex accou
},
})
);
const firstJson = await firstResponse.json();
const firstJson = (await firstResponse.json()) as any;
assert.equal(firstResponse.status, 200);
assert.equal(firstJson.choices[0].message.content, "relay-success");
@@ -171,7 +171,7 @@ test("handleChat generates and injects context-relay handoffs across Codex accou
assert.equal(summaryBodies.length, 1);
assert.match(summaryBodies[0].serializedBody, /You are a context summarizer/);
await providersDb.updateProviderConnection(primary.id, {
await providersDb.updateProviderConnection((primary as any).id, {
rateLimitedUntil: new Date(Date.now() + 60_000).toISOString(),
});
@@ -185,7 +185,7 @@ test("handleChat generates and injects context-relay handoffs across Codex accou
},
})
);
const secondJson = await secondResponse.json();
const secondJson = (await secondResponse.json()) as any;
assert.equal(secondResponse.status, 200);
assert.equal(secondJson.choices[0].message.content, "relay-success");

View File

@@ -75,7 +75,7 @@ test("handleChat waits for a short cooldown and retries once within the configur
})
);
const elapsedMs = Date.now() - startedAt;
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(fetchCalls, 1);
@@ -126,7 +126,7 @@ test("handleChat recovers from a real 429 once the connection cooldown expires",
})
);
const elapsedMs = Date.now() - startedAt;
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(fetchCalls, 4);
@@ -161,7 +161,7 @@ test("handleChat does not wait when the cooldown exceeds maxRetryIntervalSec", a
},
})
);
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(fetchCalls, 0);
assert.equal(response.status, 503);
@@ -181,9 +181,15 @@ test("handleChat returns model_cooldown when every credential for the requested
maxRetryIntervalSec: 0,
});
await auth.markAccountUnavailable(first.id, 429, "too many requests", "gemini", "gemini-2.5-pro");
await auth.markAccountUnavailable(
second.id,
(first as any).id,
429,
"too many requests",
"gemini",
"gemini-2.5-pro"
);
await auth.markAccountUnavailable(
(second as any).id,
429,
"too many requests",
"gemini",
@@ -205,7 +211,7 @@ test("handleChat returns model_cooldown when every credential for the requested
},
})
);
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(fetchCalls, 0);
assert.equal(response.status, 429);
@@ -247,7 +253,7 @@ test("handleChat aborts the pending cooldown wait when the client disconnects",
controller.signal
)
);
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(fetchCalls, 0);
assert.equal(response.status, 499);

View File

@@ -58,7 +58,7 @@ test("resolveModelOrError rejects ambiguous aliases without a provider prefix",
assert.ok(result.error);
assert.equal(result.error.status, 400);
const json = await result.error.json();
const json = (await result.error.json()) as any;
assert.match(json.error.message, /Ambiguous model/i);
});
@@ -71,7 +71,7 @@ test("resolveModelOrError rejects ambiguous slashful canonical ids instead of mi
assert.ok(result.error);
assert.equal(result.error.status, 400);
const json = await result.error.json();
const json = (await result.error.json()) as any;
assert.match(json.error.message, /Ambiguous model/i);
assert.match(json.error.message, /openai\/gpt-oss-120b/i);
});
@@ -85,7 +85,7 @@ test("resolveModelOrError rejects malformed model strings", async () => {
assert.ok(result.error);
assert.equal(result.error.status, 400);
const json = await result.error.json();
const json = (await result.error.json()) as any;
assert.match(json.error.message, /Invalid model format/i);
});
@@ -101,7 +101,7 @@ test("checkPipelineGates blocks providers with an open circuit breaker", async (
resetTimeoutMs: 5_000,
},
});
const json = await response.json();
const json = (await response.json()) as any;
const retryAfter = Number(response.headers.get("Retry-After"));
assert.equal(response.status, 503);
@@ -143,8 +143,8 @@ test("handleNoCredentials reports missing provider credentials and exhausted acc
500
);
const missingJson = await missing.json();
const exhaustedJson = await exhausted.json();
const missingJson = (await missing.json()) as any;
const exhaustedJson = (await exhausted.json()) as any;
assert.equal(missing.status, 400);
assert.match(missingJson.error.message, /No credentials for provider: openai/);
@@ -168,7 +168,7 @@ test("handleNoCredentials returns Retry-After when every account is rate limited
null,
null
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 429);
assert.ok(Number(response.headers.get("Retry-After")) >= 1);
@@ -193,7 +193,7 @@ test("handleNoCredentials returns structured model_cooldown when every credentia
null,
null
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 429);
assert.equal(Number(response.headers.get("Retry-After")) >= 1, true);
@@ -207,7 +207,7 @@ test("handleNoCredentials returns structured model_cooldown when every credentia
test("safeResolveProxy returns the direct route when no proxy config is present", async () => {
const connection = await seedConnection("openai", { apiKey: "sk-openai-direct" });
const resolved = await safeResolveProxy(connection.id);
const resolved = await safeResolveProxy((connection as any).id);
assert.deepEqual(resolved, {
proxy: null,
@@ -230,7 +230,10 @@ test("executeChatWithBreaker converts proxy fast-fail errors", async () => {
apiKey: "sk-openai-helper",
providerSpecificData: {},
};
const breaker = getCircuitBreaker("openai");
const proxyResult = await executeChatWithBreaker({
bypassCircuitBreaker: false,
breaker,
body: { model: "openai/gpt-4o-mini" },
provider: "openai",
model: "gpt-4o-mini",

View File

@@ -54,7 +54,7 @@ test("handleChat applies body-derived retry-after to the runtime limiter", async
},
})
);
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 429);
assert.match(body.error.message, /retry after 20s/i);
@@ -86,7 +86,7 @@ test("handleChat tolerates non-JSON rate-limit bodies without breaking fallback
},
})
);
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 429);
assert.match(body.error.message, /rate limit exceeded but body is not json/i);

View File

@@ -84,7 +84,7 @@ test("handleChat returns 400 for malformed JSON payloads", async () => {
body: "{bad-json",
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 400);
assert.match(json.error.message, /Invalid JSON body/i);
@@ -106,7 +106,7 @@ test("handleChat rejects suspicious prompt-injection payloads before routing", a
},
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 400);
assert.match(json.error.message, /suspicious content detected/i);
@@ -132,7 +132,7 @@ test("handleChat redacts PII before sending the upstream request", async () => {
},
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(fetchCalls.length, 1);
@@ -199,7 +199,7 @@ test("handleChat rejects requests without a model", async () => {
},
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 400);
assert.match(json.error.message, /Missing model/i);
@@ -232,7 +232,7 @@ test("handleChat applies task-aware routing when a semantic override is enabled"
},
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.deepEqual(seenAuthHeaders, ["Bearer sk-deepseek-task-route"]);
@@ -279,7 +279,7 @@ test("handleChat routes exact combo names and can recover via global fallback",
},
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(attempts, 2);
@@ -320,7 +320,7 @@ test("handleChat keeps the combo error when the global fallback throws", async (
},
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 503);
assert.equal(attempts, 2);
@@ -337,7 +337,7 @@ test("handleChat returns 400 when no provider credentials exist", async () => {
},
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 400);
assert.match(json.error.message, /No credentials for provider: openai/);
@@ -358,7 +358,7 @@ test("handleChat returns 503 for cooled-down connections and 503 for open circui
},
})
);
const cooldownJson = await cooldownResponse.json();
const cooldownJson = (await cooldownResponse.json()) as any;
assert.equal(cooldownResponse.status, 503);
assert.ok(Number(cooldownResponse.headers.get("Retry-After")) >= 1);
assert.match(cooldownJson.error.message, /\[openai\/gpt-4o-mini\]/i);
@@ -377,7 +377,7 @@ test("handleChat returns 503 for cooled-down connections and 503 for open circui
},
})
);
const breakerJson = await breakerBlocked.json();
const breakerJson = (await breakerBlocked.json()) as any;
assert.equal(breakerBlocked.status, 503);
assert.equal(breakerBlocked.headers.get("X-OmniRoute-Provider-Breaker"), "open");
@@ -403,7 +403,7 @@ test("handleChat maps upstream timeouts to HTTP 504", async () => {
},
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 504);
assert.match(json.error.message, /\[504\]: upstream timed out/);
@@ -440,7 +440,7 @@ test("handleChat uses the emergency fallback model on budget exhaustion", async
},
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(seenBodies.length, 2);
@@ -483,7 +483,7 @@ test("handleChat returns the primary budget error when emergency fallback also f
},
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 402);
assert.deepEqual(seenModels, ["gpt-4o-mini", "openai/gpt-oss-120b", "openai/gpt-oss-120b"]);
@@ -506,7 +506,7 @@ test("handleChat rejects models that are not allowed by the caller API key polic
},
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 403);
assert.match(json.error.message, /not allowed|model restriction|forbidden/i);

View File

@@ -115,7 +115,7 @@ test("Test 3: handleChat returns cached response directly for Semantic Cache hit
});
const res2 = await handleChat(req2);
const json2 = await res2.json();
const json2 = (await res2.json()) as any;
assert.equal(fetchCount, 1, "Should have hit the semantic cache without calling fetch again");
assert.equal(json2.choices[0].message.content, "Cache Generation 1");
@@ -207,7 +207,7 @@ test("handleChat returns cached response directly for Idempotency hits", async (
})
);
const json2 = await response2.json();
const json2 = (await response2.json()) as any;
assert.equal(response2.status, 200);
assert.equal(response2.headers.get("X-OmniRoute-Idempotent"), "true");
assert.equal(json2.choices[0].message.content, "Original response");
@@ -243,7 +243,7 @@ test("Test 6: handleChat correctly sets isResponsesEndpoint for /v1/responses",
})
);
const json = await response.json();
const json = (await response.json()) as any;
assert.equal(response.status, 200);
const responseText = json.output_text || json.output?.[0]?.content?.[0]?.text;
assert.equal(responseText, "Responses OK");
@@ -269,7 +269,7 @@ test("handleChat returns Semantic Cache hit", async () => {
// Second request: should hit semantic cache
const response2 = await handleChat(buildRequest({ body: reqBody }));
const json2 = await response2.json();
const json2 = (await response2.json()) as any;
assert.equal(response2.status, 200);
assert.equal(response2.headers.get("X-OmniRoute-Cache"), "HIT");
assert.equal(json2.choices[0].message.content, "Semantic API response");

View File

@@ -41,7 +41,7 @@ test("chatCore integration: compressContext called proactively when context exce
`Final tokens ${result.stats.final} should fit within limit ${contextLimit}`
);
assert.equal(
result.body.messages[result.body.messages.length - 1].content,
result.body.messages[(result.body.messages as any).length - 1].content,
"Final question?",
"Latest user turn should be preserved after compression"
);
@@ -133,7 +133,7 @@ test("chatCore integration: compression handles tool messages", async () => {
assert.ok(result.compressed, "Context should be compressed");
const toolMessage = result.body.messages.find((m: any) => m.role === "tool");
const toolMessage = (result.body as any).messages.find((m: any) => m.role === "tool");
assert.ok(toolMessage, "Tool message should exist");
assert.ok(toolMessage.content.length < longToolOutput.length, "Tool message should be truncated");
assert.ok(

View File

@@ -404,7 +404,7 @@ test("chatCore honors providerSpecificData.apiType for legacy openai-compatible
responseFormat: "openai-responses",
});
const payload = await result.response.json();
const payload = (await result.response.json()) as any;
assert.equal(result.success, true);
assert.match(call.url, /\/responses$/);
assert.ok(call.body.input);
@@ -770,7 +770,7 @@ test("chatCore restores prefixed Claude passthrough tool names in upstream respo
},
});
const payload = await result.response.json();
const payload = (await result.response.json()) as any;
assert.equal(result.success, true);
assert.equal(payload.content[0].name, "Bash");
});
@@ -850,7 +850,7 @@ test("chatCore surfaces typed translation errors with the declared error type",
assert.equal(result.success, false);
assert.equal(result.status, 422);
const payload = await result.response.json();
const payload = (await result.response.json()) as any;
assert.equal(payload.error.type, "unsupported_feature");
assert.equal(payload.error.code, "unsupported_feature");
});
@@ -931,7 +931,7 @@ test("chatCore refreshes GitHub credentials after 401 and retries with the refre
},
});
const payload = await result.response.json();
const payload = (await result.response.json()) as any;
const providerCalls = calls.filter((entry) =>
entry.url.startsWith("https://api.githubcopilot.com/")
);
@@ -1134,7 +1134,7 @@ test("chatCore serves a cached idempotent response without hitting the provider
assert.equal(second.result.success, true);
assert.equal(second.result.response.headers.get("X-OmniRoute-Idempotent"), "true");
const payload = await second.result.response.json();
const payload = (await second.result.response.json()) as any;
assert.equal(payload.choices[0].message.content, "ok");
});
@@ -1175,7 +1175,7 @@ test("chatCore returns a semantic cache HIT for repeated deterministic requests"
assert.equal(second.result.response.headers.get("X-OmniRoute-Cache"), "HIT");
assert.equal(upstreamHits, 1);
const payload = await second.result.response.json();
const payload = (await second.result.response.json()) as any;
assert.equal(payload.choices[0].message.content, "cached-once");
await waitForAsyncSideEffects();
@@ -1230,7 +1230,7 @@ test("chatCore skips semantic cache when disabled in settings", async () => {
assert.equal(first.result.response.headers.get("X-OmniRoute-Cache"), "MISS");
assert.equal(second.result.response.headers.get("X-OmniRoute-Cache"), "MISS");
const payload = await second.result.response.json();
const payload = (await second.result.response.json()) as any;
assert.equal(payload.choices[0].message.content, "fresh-2");
});
@@ -1301,7 +1301,7 @@ test("chatCore normalizes tool finish reasons and estimates usage when upstream
},
});
const payload = await result.response.json();
const payload = (await result.response.json()) as any;
assert.equal(result.success, true);
assert.equal(payload.choices[0].finish_reason, "tool_calls");
assert.ok(payload.usage.total_tokens > 0);
@@ -1319,7 +1319,7 @@ test("chatCore bypasses Claude CLI warmup probes before touching the provider",
},
});
const payload = await result.response.json();
const payload = (await result.response.json()) as any;
assert.equal(result.success, true);
assert.equal(calls.length, 0);
assert.match(payload.choices[0].message.content, /CLI Command Execution/);
@@ -1380,7 +1380,7 @@ test("chatCore retries Qwen quota 429 responses before succeeding", async () =>
},
});
const payload = await result.response.json();
const payload = (await result.response.json()) as any;
assert.equal(result.success, true);
assert.equal(calls.length, 2);
assert.equal(payload.choices[0].message.content, "qwen recovered");
@@ -1489,13 +1489,16 @@ test("chatCore persists Codex quota headers and scope cooldown on 429 responses"
},
});
const updated = await providersDb.getProviderConnectionById(connection.id);
const updated = await providersDb.getProviderConnectionById((connection as any).id);
assert.equal(result.success, false);
assert.equal(result.status, 429);
assert.equal(updated.providerSpecificData.codexQuotaState.limit5h, 100);
assert.equal(updated.providerSpecificData.codexQuotaState.scope, "codex");
assert.equal(typeof updated.providerSpecificData.codexScopeRateLimitedUntil.codex, "string");
assert.equal(updated.providerSpecificData.codexExhaustedWindow, "5h");
assert.equal((updated as any).providerSpecificData.codexQuotaState.limit5h, 100);
assert.equal((updated as any).providerSpecificData.codexQuotaState.scope, "codex");
assert.equal(
typeof (updated as any).providerSpecificData.codexScopeRateLimitedUntil.codex,
"string"
);
(assert as any).equal((updated.providerSpecificData as any).codexExhaustedWindow, "5h");
});
test("chatCore falls back to the next family model when the requested model is unavailable", async () => {
@@ -1518,7 +1521,7 @@ test("chatCore falls back to the next family model when the requested model is u
},
});
const payload = await result.response.json();
const payload = (await result.response.json()) as any;
assert.equal(result.success, true);
assert.equal(calls.length, 2);
assert.equal(calls[1].body.model, "gpt-5.1-mini");
@@ -1553,7 +1556,7 @@ test("chatCore falls back to a larger-context sibling when the request overflows
},
});
const payload = await result.response.json();
const payload = (await result.response.json()) as any;
assert.equal(result.success, true);
assert.equal(calls.length, 2);
assert.equal(calls[1].body.model, "gpt-4o");
@@ -1574,7 +1577,7 @@ test("chatCore parses upstream SSE payloads for non-streaming requests", async (
},
});
const payload = await result.response.json();
const payload = (await result.response.json()) as any;
assert.equal(result.success, true);
assert.equal(payload.choices[0].message.content, "sse json");
});
@@ -1657,7 +1660,7 @@ test("chatCore falls back after an empty-content success response", async () =>
},
});
const payload = await result.response.json();
const payload = (await result.response.json()) as any;
assert.equal(result.success, true);
assert.equal(calls.length, 2);
assert.equal(calls[1].body.model, "gpt-5.1-mini");
@@ -1813,7 +1816,7 @@ test("chatCore serves emergency fallback responses for budget errors on non-stre
},
});
const payload = await result.response.json();
const payload = (await result.response.json()) as any;
assert.equal(result.success, true);
assert.equal(calls.length, 2);
@@ -1938,7 +1941,7 @@ test("chatCore caches streaming response and serves cache HIT on repeat", async
assert.equal(second.calls.length, 0, "second request should not reach upstream");
assert.equal(second.result.response.headers.get("X-OmniRoute-Cache"), "HIT");
const payload = await second.result.response.json();
const payload = (await second.result.response.json()) as any;
assert.ok(payload.choices, "cached response should have choices");
assert.equal(payload.choices[0].message.content, "streamed-once");
});
@@ -2070,6 +2073,6 @@ test("chatCore returns cache HIT as JSON even when client requests SSE", async (
"cache HIT should return JSON regardless of stream flag"
);
const payload = await second.result.response.json();
const payload = (await second.result.response.json()) as any;
assert.equal(payload.choices[0].message.content, "cached-json");
});

View File

@@ -99,7 +99,7 @@ test("buildClaudeCodeCompatibleValidationPayload produces the expected smoke-tes
});
assert.equal(payload.tools.length, 0);
assert.equal(payload.system.length, 1);
assert.match(payload.system[0].text, /Claude Agent SDK/);
assert.ok(JSON.parse(payload.metadata.user_id).session_id);
assert.match((payload as any).system[0].text, /Claude Agent SDK/);
assert.ok((JSON as any).parse(payload.metadata.user_id).session_id);
assert.ok(CLAUDE_CODE_COMPATIBLE_DEFAULT_MAX_TOKENS > payload.max_tokens);
});

View File

@@ -115,7 +115,7 @@ test("buildClaudeCodeCompatibleRequest covers normalized OpenAI-style messages,
},
]);
assert.equal(payload.system.length, 3);
assert.match(payload.system[0].text, /Claude Agent SDK/);
assert.match((payload as any).system[0].text, /Claude Agent SDK/);
assert.equal(payload.system[1].text, "system note");
assert.equal(payload.system[2].text, "developer note");
assert.equal(payload.tools.length, 1);
@@ -194,17 +194,17 @@ test("buildClaudeCodeCompatibleRequest covers Claude-native bodies and cache-con
});
assert.equal(stripped.stream, true);
assert.equal(JSON.parse(stripped.metadata.user_id).session_id, "explicit-session");
assert.equal((JSON as any).parse(stripped.metadata.user_id).session_id, "explicit-session");
assert.equal(stripped.messages.at(-1).role, "user");
assert.equal(stripped.system[0].cache_control, undefined);
assert.equal(stripped.messages[0].content[0].cache_control, undefined);
assert.equal((stripped as any).system[0].cache_control, undefined);
assert.equal((stripped as any).messages[0].content[0].cache_control, undefined);
assert.equal(stripped.tools[0].cache_control, undefined);
assert.deepEqual(stripped.thinking, { type: "enabled", budget_tokens: 12 });
assert.deepEqual(stripped.output_config, { effort: "high", format: "compact" });
assert.equal(stripped.metadata.foo, "bar");
assert.deepEqual(preserved.system[0].cache_control, { type: "ephemeral" });
assert.equal(preserved.messages[0].content[0].cache_control.type, "ephemeral");
assert.equal(preserved.tools[0].cache_control.type, "ephemeral");
(assert as any).deepEqual((preserved.system[0] as any).cache_control, { type: "ephemeral" });
(assert as any).equal((preserved.messages[0].content[0] as any).cache_control.type, "ephemeral");
assert.equal((preserved.tools[0].cache_control as any).type, "ephemeral");
});
test("buildClaudeCodeCompatibleRequest omits tool choice when there are no tools", () => {
@@ -222,8 +222,8 @@ test("buildClaudeCodeCompatibleRequest omits tool choice when there are no tools
assert.equal(payload.tools.length, 0);
assert.equal("tool_choice" in payload, false);
assert.equal(payload.output_config.effort, "high");
assert.equal(payload.system.length, 1);
assert.match(payload.system[0].text, /Claude Agent SDK/);
(assert as any).equal(payload.system.length, 1);
assert.match((payload as any).system[0].text, /Claude Agent SDK/);
});
test("buildClaudeCodeCompatibleRequest covers string system input, non-array Claude fields and tool choice variants", () => {
@@ -264,8 +264,8 @@ test("buildClaudeCodeCompatibleRequest covers string system input, non-array Cla
});
assert.deepEqual(anyChoice.tool_choice, { type: "any" });
assert.equal(anyChoice.tools.length, 2);
assert.equal(anyChoice.tools[0].input_schema.properties.q.type, "string");
assert.equal((anyChoice as any).tools.length, 2);
assert.equal((anyChoice.tools[0].input_schema as any).properties.q.type, "string");
assert.equal(anyChoice.tools[1].description, "");
assert.equal(stringSystem.messages.length, 0);
assert.equal(stringSystem.tools.length, 0);

View File

@@ -50,7 +50,7 @@ test.afterEach(() => {
restoreEnv();
for (const dir of tempDirs) {
fs.rmSync(dir, { recursive: true, force: true });
fs.rmSync(dir as any, { recursive: true, force: true });
}
tempDirs.clear();
});

View File

@@ -0,0 +1,76 @@
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const THIS_FILE = fileURLToPath(import.meta.url);
const TESTS_DIR = path.dirname(THIS_FILE);
const REPO_ROOT = path.resolve(TESTS_DIR, "../..");
const CLI_TOOLS_DIR = path.join(REPO_ROOT, "src", "app", "api", "cli-tools");
function listCliToolRouteFiles(dir: string): string[] {
const entries = fs.readdirSync(dir, { withFileTypes: true });
const files: string[] = [];
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...listCliToolRouteFiles(fullPath));
continue;
}
if (entry.isFile() && entry.name === "route.ts") {
files.push(fullPath);
}
}
return files;
}
test("all cli-tools route handlers require the shared management auth guard", () => {
const routeFiles = listCliToolRouteFiles(CLI_TOOLS_DIR)
.map((fullPath) => path.relative(REPO_ROOT, fullPath).replace(/\\/g, "/"))
.sort();
assert.ok(routeFiles.length > 0, "expected at least one cli-tools route");
for (const relPath of routeFiles) {
const fullPath = path.join(REPO_ROOT, relPath);
const content = fs.readFileSync(fullPath, "utf8");
const handlerCount = (content.match(/export async function (GET|POST|PUT|DELETE)\(/g) || [])
.length;
const authCount = (
content.match(/const authError = await requireCliToolsAuth\(request\);/g) || []
).length;
const returnCount = (content.match(/if \(authError\) return authError;/g) || []).length;
assert.ok(handlerCount > 0, `${relPath} should export at least one route handler`);
assert.ok(
content.includes('from "@/lib/api/requireCliToolsAuth"'),
`${relPath} should import requireCliToolsAuth`
);
assert.equal(
authCount,
handlerCount,
`${relPath} should guard every exported handler before host access`
);
assert.equal(
returnCount,
handlerCount,
`${relPath} should return the auth error from every exported handler`
);
}
});
test("cli-tools auth helper delegates to management auth", () => {
const helperPath = path.join(REPO_ROOT, "src/lib/api/requireCliToolsAuth.ts");
const content = fs.readFileSync(helperPath, "utf8");
assert.ok(
content.includes('from "@/lib/api/requireManagementAuth"'),
"requireCliToolsAuth should reuse the shared management auth helper"
);
assert.ok(
content.includes("return requireManagementAuth(request);"),
"requireCliToolsAuth should delegate directly to requireManagementAuth"
);
});

View File

@@ -210,8 +210,8 @@ test("cloudSync syncs data upstream and refreshes only locally stale provider to
const cloudSync = await loadCloudSync("sync-success");
const result = await cloudSync.syncToCloud("machine-1", "created-key-1");
const staleAfter = await providersDb.getProviderConnectionById(stale.id);
const freshAfter = await providersDb.getProviderConnectionById(fresh.id);
const staleAfter = await providersDb.getProviderConnectionById((stale as any).id);
const freshAfter = await providersDb.getProviderConnectionById((fresh as any).id);
assert.equal(Array.isArray(postedBody.providers), true);
assert.equal(Array.isArray(postedBody.apiKeys), true);

View File

@@ -47,7 +47,7 @@ async function readJsonFileWithRetry(filePath, attempts = 100) {
try {
return JSON.parse(trimmed);
} catch (error) {
} catch (error: any) {
const snapshots = trimmed
.split(/\n(?=\{)/)
.map((entry) => entry.trim())
@@ -69,7 +69,7 @@ async function readJsonFileWithRetry(filePath, attempts = 100) {
for (let attempt = 0; attempt < attempts; attempt += 1) {
try {
return parseJsonSnapshot(await fs.readFile(filePath, "utf8"));
} catch (error) {
} catch (error: any) {
lastError = error;
await new Promise((resolve) => setTimeout(resolve, 10));
}
@@ -101,7 +101,7 @@ test.afterEach(async () => {
restoreEnv();
for (const dir of tempDirs) {
await fs.rm(dir, { recursive: true, force: true });
await fs.rm(dir as any, { recursive: true, force: true });
}
tempDirs.clear();
});

View File

@@ -69,20 +69,20 @@ test("migration backfills Codex request defaults, preserves existing providerSpe
assert.equal(firstRun.migrated, true);
assert.deepEqual(firstRun.updatedConnectionIds.sort(), [first.id, second.id].sort());
assert.deepEqual(byId.get(first.id).providerSpecificData.requestDefaults, {
assert.deepEqual((byId.get(first.id).providerSpecificData as any).requestDefaults, {
reasoningEffort: "medium",
serviceTier: "priority",
});
assert.deepEqual(byId.get(second.id).providerSpecificData.requestDefaults, {
assert.deepEqual((byId.get(second.id) as any).providerSpecificData.requestDefaults, {
reasoningEffort: "high",
serviceTier: "priority",
});
assert.deepEqual(byId.get(untouched.id).providerSpecificData.requestDefaults, {
assert.deepEqual((byId.get(untouched.id) as any).providerSpecificData.requestDefaults, {
reasoningEffort: "low",
serviceTier: "priority",
});
assert.equal(byId.get(first.id).providerSpecificData.tag, "team-a");
assert.deepEqual(byId.get(first.id).providerSpecificData.codexLimitPolicy, {
assert.equal((byId.get((first as any).id).providerSpecificData as any).tag, "team-a");
assert.deepEqual((byId as any).get(first.id).providerSpecificData.codexLimitPolicy, {
use5h: false,
useWeekly: true,
});
@@ -110,26 +110,26 @@ test("provider connection persistence normalizes request defaults without droppi
},
});
assert.deepEqual(created.providerSpecificData.requestDefaults, {
(assert as any).deepEqual((created.providerSpecificData as any).requestDefaults, {
reasoningEffort: "high",
serviceTier: "priority",
customFlag: "keep-me",
});
assert.equal(created.providerSpecificData.openaiStoreEnabled, true);
assert.equal(created.providerSpecificData.workspaceId, "ws-normalize");
assert.equal(created.providerSpecificData.tag, "team-z");
assert.equal((created.providerSpecificData as any).openaiStoreEnabled, true);
assert.equal((created.providerSpecificData as any).workspaceId, "ws-normalize");
assert.equal((created.providerSpecificData as any).tag, "team-z");
const updated = await providersDb.updateProviderConnection(created.id, {
const updated = await providersDb.updateProviderConnection((created as any).id, {
providerSpecificData: {
...created.providerSpecificData,
requestDefaults: { reasoningEffort: "medium" },
},
});
assert.deepEqual(updated.providerSpecificData.requestDefaults, {
assert.deepEqual((updated.providerSpecificData as any).requestDefaults, {
reasoningEffort: "medium",
});
assert.equal(updated.providerSpecificData.openaiStoreEnabled, true);
assert.equal(updated.providerSpecificData.workspaceId, "ws-normalize");
assert.equal(updated.providerSpecificData.tag, "team-z");
assert.equal((updated.providerSpecificData as any).openaiStoreEnabled, true);
assert.equal((updated.providerSpecificData as any).workspaceId, "ws-normalize");
assert.equal((updated.providerSpecificData as any).tag, "team-z");
});

View File

@@ -227,7 +227,7 @@ test("chatCore converts Responses-style SSE fallback into JSON when stream=false
responseFactory: () => buildResponsesSse("Brasilia"),
});
const payload = await result.response.json();
const payload = (await result.response.json()) as any;
assert.equal(result.success, true);
assert.equal(call.headers.Accept || call.headers.accept, "application/json");
@@ -250,7 +250,7 @@ test("chatCore converts Responses-style NDJSON fallback into JSON when stream=fa
responseFactory: () => buildResponsesNdjson("Brasilia"),
});
const payload = await result.response.json();
const payload = (await result.response.json()) as any;
assert.equal(result.success, true);
assert.equal(call.headers.Accept || call.headers.accept, "application/json");
@@ -300,7 +300,7 @@ test("handleComboChat validates non-stream quality using the original client str
allCombos: null,
});
const payload = await result.json();
const payload = (await result.json()) as any;
assert.equal(result.ok, true);
assert.deepEqual(seenModels, ["codex/gpt-5.4", "openai/gpt-4o-mini"]);

View File

@@ -125,7 +125,7 @@ test("combo builder options route aggregates providers, connections, models and
});
const response = await route.GET();
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(body.schemaVersion, 2);
@@ -212,7 +212,7 @@ test("combo builder options route exposes compatible provider nodes with node me
await modelsDb.addCustomModel("openai-compatible-demo", "gpt-custom", "GPT Custom");
const response = await route.GET();
const body = await response.json();
const body = (await response.json()) as any;
const provider = body.providers.find((entry) => entry.providerId === "openai-compatible-demo");
assert.equal(response.status, 200);

View File

@@ -113,7 +113,7 @@ test("combo health route exposes step-level target health for structured combos"
const response = await route.GET(
new Request(`http://localhost/api/usage/combo-health?range=24h&comboId=${combo.id}`)
);
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(body.combos.length, 1);
@@ -234,7 +234,7 @@ test("combo health route prefers historical call log target metrics over volatil
const response = await route.GET(
new Request(`http://localhost/api/usage/combo-health?range=24h&comboId=${combo.id}`)
);
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(body.combos.length, 1);

View File

@@ -91,7 +91,7 @@ test("combo failover skips the cooled provider target on the next request", asyn
},
})
);
const firstBody = await firstResponse.json();
const firstBody = (await firstResponse.json()) as any;
const secondResponse = await handleChat(
buildRequest({
@@ -102,7 +102,7 @@ test("combo failover skips the cooled provider target on the next request", asyn
},
})
);
const secondBody = await secondResponse.json();
const secondBody = (await secondResponse.json()) as any;
assert.equal(firstResponse.status, 200);
assert.equal(secondResponse.status, 200);

View File

@@ -96,7 +96,7 @@ test("POST /api/combos rejects composite tiers that point to unknown steps", asy
},
})
);
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 400);
assert.deepEqual(body, {
@@ -114,12 +114,12 @@ test("POST /api/combos rejects composite tiers that point to unknown steps", asy
test("POST /api/combos persists valid composite tiers", async () => {
const response = await createRoute.POST(makeCreateRequest(createTieredComboInput()));
const body = await response.json();
const body = (await response.json()) as any;
const stored = await combosDb.getComboByName("tiered-codex");
assert.equal(response.status, 201);
assert.equal(body.config.compositeTiers.defaultTier, "primary");
assert.equal(stored.config.compositeTiers.tiers.primary.stepId, "step-primary");
assert.equal((stored.config as any).compositeTiers.tiers.primary.stepId, "step-primary");
assert.equal(stored.models[0].id, "step-primary");
});
@@ -137,7 +137,7 @@ test("POST /api/combos preserves legacy string combo refs during normalization",
models: ["child-ref"],
})
);
const body = await response.json();
const body = (await response.json()) as any;
const stored = await combosDb.getComboByName("parent-ref");
assert.equal(response.status, 201);
@@ -164,7 +164,7 @@ test("PUT /api/combos rejects updates that orphan an existing composite tier ste
}),
{ params: Promise.resolve({ id: combo.id }) }
);
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 400);
assert.deepEqual(body, {
@@ -198,8 +198,8 @@ test("PUT /api/combos preserves legacy string combo refs during normalization",
}),
{ params: Promise.resolve({ id: combo.id }) }
);
const body = await response.json();
const stored = await combosDb.getComboById(combo.id);
const body = (await response.json()) as any;
const stored = await combosDb.getComboById((combo as any).id);
assert.equal(response.status, 200);
assert.equal(body.models[0].kind, "combo-ref");

View File

@@ -113,7 +113,7 @@ async function cleanupTestDataDir() {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
return;
} catch (error) {
} catch (error: any) {
lastError = error;
await new Promise((resolve) => setTimeout(resolve, 25));
}
@@ -622,7 +622,7 @@ test("handleComboChat preserves the first failure status but surfaces the last e
relayOptions: null,
});
const payload = await result.json();
const payload = (await result.json()) as any;
assert.equal(result.status, 500);
assert.equal(payload.error.message, "fail:model-b");
@@ -982,7 +982,7 @@ test("handleComboChat returns the earliest retry-after when all priority targets
relayOptions: null,
});
const payload = await result.json();
const payload = (await result.json()) as any;
assert.equal(result.status, 429);
assert.match(payload.error.message, /limited:model-b/);
@@ -1013,7 +1013,7 @@ test("handleComboChat returns 404 model_not_found when a combo has no executable
relayOptions: null,
});
const payload = await result.json();
const payload = (await result.json()) as any;
assert.equal(result.status, 404);
assert.equal(payload.error.code, "model_not_found");
@@ -1046,7 +1046,7 @@ test("handleComboChat round-robin returns 404 when no models are configured", as
relayOptions: null,
});
const payload = await result.json();
const payload = (await result.json()) as any;
assert.equal(result.status, 404);
assert.equal(payload.error.code, "model_not_found");
@@ -1137,7 +1137,7 @@ test("handleComboChat round-robin surfaces retry-after metadata after exhausting
relayOptions: null,
});
const payload = await result.json();
const payload = (await result.json()) as any;
assert.equal(result.status, 429);
assert.match(payload.error.message, /rr-limited:model-b/);
@@ -1222,7 +1222,7 @@ test("handleComboChat round-robin falls through provider-scoped 400s and returns
relayOptions: null,
});
const payload = await result.json();
const payload = (await result.json()) as any;
assert.equal(result.status, 400);
assert.equal(payload.error.message, "rr-final-fail");
assert.deepEqual(calls, ["model-a", "model-b"]);
@@ -1388,7 +1388,7 @@ test("handleComboChat returns a 503 when every model is unavailable before execu
relayOptions: null,
});
const payload = await result.json();
const payload = (await result.json()) as any;
assert.equal(result.status, 503);
assert.equal(payload.error.code, "ALL_ACCOUNTS_INACTIVE");
});
@@ -1694,7 +1694,7 @@ test("handleComboChat context cache protection pins the model and tags tool-call
relayOptions: null,
});
const payload = await result.json();
const payload = (await result.json()) as any;
assert.equal(result.ok, true);
assert.deepEqual(calls, ["claude/claude-sonnet-4-6"]);
assert.match(
@@ -1809,7 +1809,7 @@ test("handleComboChat round-robin resolves nested combos and returns inactive wh
],
});
const payload = await result.json();
const payload = (await result.json()) as any;
assert.equal(result.status, 503);
assert.equal(payload.error.code, "ALL_ACCOUNTS_INACTIVE");
});
@@ -1947,7 +1947,7 @@ test("handleComboChat falls back to next model when first model returns all-acco
relayOptions: null,
});
const payload = await result.json();
const payload = (await result.json()) as any;
// First model returns 503 with "unavailable" → combo should try model-b next
// If the fix is not applied, combo would abort here and return 503 immediately
assert.equal(result.ok, true);
@@ -1988,7 +1988,7 @@ test("handleComboChat round-robin falls back when all-accounts-rate-limited 503
relayOptions: null,
});
const payload = await result.json();
const payload = (await result.json()) as any;
assert.equal(result.ok, true);
assert.deepEqual(calls, ["model-a", "model-b"]);
assert.equal(payload.choices[0].message.content, "ok");
@@ -2022,7 +2022,7 @@ test("handleComboChat aborts combo when 503 response does NOT contain the unavai
relayOptions: null,
});
const payload = await result.json();
const payload = (await result.json()) as any;
// Without the fix, combo would abort (still 503). With the fix, it's still 503 because
// the signal check filters out non-JSON or non-"unavailable" responses.
assert.equal(result.status, 503);

View File

@@ -48,7 +48,7 @@ test("handleComboChat with 'usage' strategy hits sortModelsByUsage", async () =>
try {
const res = await handleComboChat(id, req, stubConnection());
assert.equal(res.status >= 200, true);
} catch (e) {
} catch (e: any) {
// Expect error as fetch is not globally mocked for this quick edge branch test, that's fine
}
});
@@ -70,7 +70,7 @@ test("handleComboChat with 'context' strategy hits sortModelsByContextSize", asy
try {
const res = await handleComboChat(id, req, stubConnection());
} catch (e) {}
} catch (e: any) {}
});
test("handleComboChat hits extractPromptForIntent edge cases", async () => {
@@ -99,8 +99,8 @@ test("handleComboChat hits extractPromptForIntent edge cases", async () => {
try {
await handleComboChat(id, reqNull, stubConnection());
} catch (e) {}
} catch (e: any) {}
try {
await handleComboChat(id, reqEmpty, stubConnection());
} catch (e) {}
} catch (e: any) {}
});

View File

@@ -74,12 +74,12 @@ test("combo test route validates request payloads and combo existence", async ()
body: JSON.stringify({ comboName: "" }),
})
);
const invalidBody = await invalidBodyResponse.json();
const invalidBody = (await invalidBodyResponse.json()) as any;
assert.equal(invalidBodyResponse.status, 400);
assert.equal(invalidBody.error.message, "Invalid request");
const missingResponse = await route.POST(makeRequest("missing-combo"));
const missingBody = await missingResponse.json();
const missingBody = (await missingResponse.json()) as any;
assert.equal(missingResponse.status, 404);
assert.equal(missingBody.error, "Combo not found");
});
@@ -120,7 +120,7 @@ test("combo test route marks a model healthy only when it returns assistant text
} finally {
Math.random = originalRandom;
}
const body = await response.json();
const body = (await response.json()) as any;
const forwardedBody = JSON.parse(fetchCalls[0].init.body);
assert.equal(response.status, 200);
@@ -163,7 +163,7 @@ test("combo test route treats empty successful responses as failures", async ()
);
const response = await route.POST(makeRequest());
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(body.resolvedBy, null);
@@ -203,7 +203,7 @@ test("combo test route accepts reasoning-only completions as healthy smoke-test
);
const response = await route.POST(makeRequest());
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(body.resolvedBy, "openrouter/openai/gpt-5.4");
@@ -228,7 +228,7 @@ test("combo test route surfaces provider errors instead of downgrading them to r
);
const response = await route.POST(makeRequest());
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(body.resolvedBy, null);
@@ -284,7 +284,7 @@ test("combo test route launches model probes concurrently while preserving combo
);
const response = await responsePromise;
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(body.resolvedBy, "provider/first");
@@ -343,7 +343,7 @@ test("combo test route preserves structured step metadata for repeated model/acc
};
const response = await route.POST(makeRequest());
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(fetchCalls.length, 2);
@@ -366,7 +366,7 @@ test("combo test route rejects empty combos and respects forwarded base URLs", a
await createTestCombo([]);
const emptyResponse = await route.POST(makeRequest());
const emptyBody = await emptyResponse.json();
const emptyBody = (await emptyResponse.json()) as any;
assert.equal(emptyResponse.status, 400);
assert.equal(emptyBody.error, "Combo has no models");
@@ -422,7 +422,7 @@ test("combo test route handles upstream timeouts and non-JSON error bodies", asy
};
const response = await route.POST(makeRequest());
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(body.resolvedBy, null);

View File

@@ -60,7 +60,7 @@ test("compliance audit route keeps array payloads and exposes total count with s
assert.equal(response.status, 200);
assert.equal(response.headers.get("x-total-count"), "1");
assert.equal(response.headers.get("x-page-limit"), "10");
const payload = await response.json();
const payload = (await response.json()) as any;
assert.equal(Array.isArray(payload), true);
assert.equal(payload.length, 1);
assert.equal(payload[0].action, "provider.validation.ssrf_blocked");

View File

@@ -233,13 +233,14 @@ test("cleanupExpiredLogs removes stale rows across all log tables and records an
);
const result = compliance.cleanupExpiredLogs();
const usageCount = db.prepare("SELECT COUNT(*) as count FROM usage_history").get().count;
const callCount = db.prepare("SELECT COUNT(*) as count FROM call_logs").get().count;
const proxyCount = db.prepare("SELECT COUNT(*) as count FROM proxy_logs").get().count;
const requestDetailCount = db
.prepare("SELECT COUNT(*) as count FROM request_detail_logs")
.get().count;
const mcpAuditCount = db.prepare("SELECT COUNT(*) as count FROM mcp_tool_audit").get().count;
const usageCount = (db.prepare("SELECT COUNT(*) as count FROM usage_history").get() as any).count;
const callCount = (db.prepare("SELECT COUNT(*) as count FROM call_logs").get() as any).count;
const proxyCount = (db.prepare("SELECT COUNT(*) as count FROM proxy_logs").get() as any).count;
const requestDetailCount = (
db.prepare("SELECT COUNT(*) as count FROM request_detail_logs") as any
).get().count;
const mcpAuditCount = (db.prepare("SELECT COUNT(*) as count FROM mcp_tool_audit").get() as any)
.count;
const auditEntries = compliance.getAuditLog();
const auditActions = auditEntries.map((entry) => entry.action);

View File

@@ -44,7 +44,7 @@ test("console log API normalizes numeric pino levels correctly", async () => {
const response = await route.GET(
new Request("http://localhost/api/logs/console?level=info&limit=10")
);
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.deepEqual(
@@ -89,7 +89,7 @@ test("console log API filters by component, time window, and result limit", asyn
const response = await route.GET(
new Request("http://localhost/api/logs/console?level=warn&component=router&limit=1")
);
const body = await response.json();
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(body.length, 1);
@@ -111,7 +111,7 @@ test("console log API returns an empty list for a missing file and surfaces read
try {
const brokenResponse = await route.GET(new Request("http://localhost/api/logs/console"));
assert.equal(brokenResponse.status, 500);
const payload = await brokenResponse.json();
const payload = (await brokenResponse.json()) as any;
assert.equal(typeof payload.error, "string");
assert.equal(payload.error.length > 0, true);
} finally {

View File

@@ -82,7 +82,7 @@ test("compressContext: Layer 1 — trims long tool messages", () => {
// Use target limit that allows the truncated tool message (~1000 tokens) to survive
const result = compressContext(body, { maxTokens: 2000, reserveTokens: 100 });
assert.ok(result.compressed);
const toolMsg = result.body.messages.find((m: any) => m.role === "tool");
const toolMsg = (result.body.messages as any).find((m: any) => m.role === "tool");
assert.ok(toolMsg.content.length < longContent.length);
assert.ok(toolMsg.content.includes("[truncated]"));
});
@@ -111,7 +111,7 @@ test("compressContext: Layer 2 — compresses thinking in old messages", () => {
};
const result = compressContext(body, { maxTokens: 2000, reserveTokens: 500 });
// First assistant should have thinking removed
const firstAssistant = result.body.messages.find((m: any) => m.role === "assistant");
const firstAssistant = (result.body as any).messages.find((m: any) => m.role === "assistant");
if (Array.isArray(firstAssistant.content)) {
const hasThinking = firstAssistant.content.some((b: any) => b.type === "thinking");
assert.equal(hasThinking, false);
@@ -129,7 +129,7 @@ test("compressContext: Layer 3 — drops old messages to fit", () => {
const body = { model: "test", messages };
const result = compressContext(body, { maxTokens: 3000, reserveTokens: 500 });
assert.ok(result.compressed);
assert.ok(result.body.messages.length < messages.length);
assert.ok((result as any).body.messages.length < messages.length);
assert.equal(result.body.messages[0].role, "system");
});
@@ -243,7 +243,7 @@ test("Layer 3: preserves intact tool_use/tool_result pairs after compression", (
];
const body = { model: "test", messages };
const result = compressContext(body, { maxTokens: 50000, reserveTokens: 10000 });
const toolMsg = result.body.messages.find(
const toolMsg = (result.body.messages as any).find(
(m: any) => m.role === "tool" && m.tool_call_id === "call_1"
);
assert.ok(toolMsg, "tool_result for call_1 should survive compression");

View File

@@ -28,7 +28,7 @@ describe("Context pinning — tool call responses (#721)", () => {
assert.equal(result.length, 3, "Should have 3 messages (original 2 + synthetic)");
assert.equal(result[2].role, "assistant");
assert.ok(
result[2].content.includes("<omniModel>ollamacloud/glm-5</omniModel>"),
(result[2].content as any).includes("<omniModel>ollamacloud/glm-5</omniModel>"),
"Synthetic message should contain the pin tag"
);
});
@@ -50,7 +50,7 @@ describe("Context pinning — tool call responses (#721)", () => {
// Array content → should append synthetic message
assert.equal(result.length, 3);
assert.equal(result[2].role, "assistant");
assert.ok(result[2].content.includes("<omniModel>nvidia/llama-3.4-70b</omniModel>"));
assert.ok((result[2] as any).content.includes("<omniModel>nvidia/llama-3.4-70b</omniModel>"));
});
test("extractPinnedModel finds tag in synthetic message after tool_calls", () => {
@@ -79,8 +79,8 @@ describe("Context pinning — tool call responses (#721)", () => {
const result = injectModelTag(messages, "openai/gpt-4o");
assert.equal(result.length, 2, "Should not add a new message");
assert.ok(result[1].content.includes("<omniModel>openai/gpt-4o</omniModel>"));
assert.ok(result[1].content.startsWith("Hi there!"));
assert.ok((result as any)[1].content.includes("<omniModel>openai/gpt-4o</omniModel>"));
(assert as any).ok((result[1].content as any).startsWith("Hi there!"));
});
test("roundtrip: inject → extract works for tool-call messages", () => {

View File

@@ -21,7 +21,7 @@ async function resetStorage() {
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
}
break;
} catch (error) {
} catch (error: any) {
if ((error?.code === "EBUSY" || error?.code === "EPERM") && attempt < 9) {
await new Promise((resolve) => setTimeout(resolve, 50 * (attempt + 1)));
} else {

View File

@@ -126,7 +126,7 @@ test("restoreDbBackup restores SQLite contents and returns entity counts", async
assert.equal(restored.nodeCount, 0);
assert.equal(restored.comboCount, 0);
assert.equal(restored.apiKeyCount, 0);
assert.equal(row.cnt, 1);
assert.equal((row as any).cnt, 1);
});
test("cleanupDbBackups removes overflow families and orphaned sidecars", async () => {

View File

@@ -19,7 +19,7 @@ async function resetStorage() {
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
}
break;
} catch (error) {
} catch (error: any) {
if ((error?.code === "EBUSY" || error?.code === "EPERM") && attempt < 9) {
await new Promise((resolve) => setTimeout(resolve, 50 * (attempt + 1)));
} else {
@@ -58,7 +58,7 @@ test("createCombo stores default strategy and supports lookup by id and name", a
weight: 0,
},
]);
assert.deepEqual(await combosDb.getComboById(combo.id), combo);
assert.deepEqual(await combosDb.getComboById((combo as any).id), combo);
assert.deepEqual(await combosDb.getComboByName("Priority Combo"), combo);
});
@@ -91,7 +91,7 @@ test("updateCombo merges fields while preserving immutable data", async () => {
config: { retries: 1 },
});
const updated = await combosDb.updateCombo(combo.id, {
const updated = await combosDb.updateCombo((combo as any).id, {
strategy: "round-robin",
config: { retries: 3, timeoutMs: 2000 },
isHidden: true,
@@ -103,7 +103,7 @@ test("updateCombo merges fields while preserving immutable data", async () => {
assert.deepEqual(updated.config, { retries: 3, timeoutMs: 2000 });
assert.equal(updated.strategy, "round-robin");
assert.equal(updated.isHidden, true);
assert.deepEqual(await combosDb.getComboById(combo.id), updated);
assert.deepEqual(await combosDb.getComboById((combo as any).id), updated);
});
test("reorderCombos persists manual combo ordering in sqlite", async () => {
@@ -130,9 +130,9 @@ test("reorderCombos persists manual combo ordering in sqlite", async () => {
reordered.map((combo) => combo.sortOrder),
[1, 2, 3]
);
assert.equal((await combosDb.getComboById(charlie.id))?.sortOrder, 1);
assert.equal((await combosDb.getComboById(alpha.id))?.sortOrder, 2);
assert.equal((await combosDb.getComboById(bravo.id))?.sortOrder, 3);
assert.equal((await combosDb.getComboById((charlie as any).id))?.sortOrder, 1);
assert.equal((await combosDb.getComboById((alpha as any).id))?.sortOrder, 2);
assert.equal((await combosDb.getComboById((bravo as any).id))?.sortOrder, 3);
});
test("deleteCombo reports missing ids and removes existing rows", async () => {
@@ -142,8 +142,8 @@ test("deleteCombo reports missing ids and removes existing rows", async () => {
});
assert.equal(await combosDb.deleteCombo("missing-combo"), false);
assert.equal(await combosDb.deleteCombo(combo.id), true);
assert.equal(await combosDb.getComboById(combo.id), null);
assert.equal(await combosDb.deleteCombo((combo as any).id), true);
assert.equal(await combosDb.getComboById((combo as any).id), null);
});
test("getCombos upgrades legacy persisted entries to version 2 and resolves combo refs", async () => {

Some files were not shown because too many files have changed in this diff Show More