diff --git a/CHANGELOG.md b/CHANGELOG.md index b30c0ee479..27d7faadda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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` 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) --- diff --git a/electron/package.json b/electron/package.json index f4da5e1509..58f200b0f2 100644 --- a/electron/package.json +++ b/electron/package.json @@ -45,7 +45,8 @@ "files": [ "main.js", "preload.js", - "package.json" + "package.json", + "node_modules/**/*" ], "extraResources": [ { diff --git a/open-sse/executors/codex.ts b/open-sse/executors/codex.ts index 294c4e6ba5..dc9abdcd55 100644 --- a/open-sse/executors/codex.ts +++ b/open-sse/executors/codex.ts @@ -339,12 +339,14 @@ function normalizeCodexTools(body: Record): 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; - 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; + const name = typeof subTool.name === "string" ? subTool.name.trim() : ""; + if (name) validToolNames.add(name); + } } } return true; diff --git a/open-sse/services/batchProcessor.ts b/open-sse/services/batchProcessor.ts index 7452b917c6..57ed33ee08 100644 --- a/open-sse/services/batchProcessor.ts +++ b/open-sse/services/batchProcessor.ts @@ -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; + 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, 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; + 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, + 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 -): Promise { - 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; } diff --git a/open-sse/services/combo.ts b/open-sse/services/combo.ts index ca08f3a17d..33e845b421 100644 --- a/open-sse/services/combo.ts +++ b/open-sse/services/combo.ts @@ -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++; diff --git a/src/app/api/cli-tools/antigravity-mitm/alias/route.ts b/src/app/api/cli-tools/antigravity-mitm/alias/route.ts index 67e6babefd..9233a8cd62 100644 --- a/src/app/api/cli-tools/antigravity-mitm/alias/route.ts +++ b/src/app/api/cli-tools/antigravity-mitm/alias/route.ts @@ -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(); diff --git a/src/app/api/cli-tools/antigravity-mitm/route.ts b/src/app/api/cli-tools/antigravity-mitm/route.ts index 48a523ff8f..6d4e0b442f 100644 --- a/src/app/api/cli-tools/antigravity-mitm/route.ts +++ b/src/app/api/cli-tools/antigravity-mitm/route.ts @@ -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(); diff --git a/src/app/api/cli-tools/backups/route.ts b/src/app/api/cli-tools/backups/route.ts index beb3a7d6b5..39a55a9be7 100644 --- a/src/app/api/cli-tools/backups/route.ts +++ b/src/app/api/cli-tools/backups/route.ts @@ -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(); diff --git a/src/app/api/cli-tools/claude-settings/route.ts b/src/app/api/cli-tools/claude-settings/route.ts index 63205c9e2a..45d37c45ae 100644 --- a/src/app/api/cli-tools/claude-settings/route.ts +++ b/src/app/api/cli-tools/claude-settings/route.ts @@ -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) { diff --git a/src/app/api/cli-tools/cline-settings/route.ts b/src/app/api/cli-tools/cline-settings/route.ts index dbe832800b..090dcaf920 100644 --- a/src/app/api/cli-tools/cline-settings/route.ts +++ b/src/app/api/cli-tools/cline-settings/route.ts @@ -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) { diff --git a/src/app/api/cli-tools/codex-profiles/route.ts b/src/app/api/cli-tools/codex-profiles/route.ts index feb790a444..65126fd282 100644 --- a/src/app/api/cli-tools/codex-profiles/route.ts +++ b/src/app/api/cli-tools/codex-profiles/route.ts @@ -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(); diff --git a/src/app/api/cli-tools/codex-settings/route.ts b/src/app/api/cli-tools/codex-settings/route.ts index 5cf1151a1d..f997aa5fb1 100644 --- a/src/app/api/cli-tools/codex-settings/route.ts +++ b/src/app/api/cli-tools/codex-settings/route.ts @@ -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) { diff --git a/src/app/api/cli-tools/droid-settings/route.ts b/src/app/api/cli-tools/droid-settings/route.ts index 46a7d64212..c7e15f42a0 100644 --- a/src/app/api/cli-tools/droid-settings/route.ts +++ b/src/app/api/cli-tools/droid-settings/route.ts @@ -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) { diff --git a/src/app/api/cli-tools/guide-settings/[toolId]/route.ts b/src/app/api/cli-tools/guide-settings/[toolId]/route.ts index 1dd970bb60..d97d6344da 100644 --- a/src/app/api/cli-tools/guide-settings/[toolId]/route.ts +++ b/src/app/api/cli-tools/guide-settings/[toolId]/route.ts @@ -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(); diff --git a/src/app/api/cli-tools/kilo-settings/route.ts b/src/app/api/cli-tools/kilo-settings/route.ts index d14f4b17a5..aac82bebd7 100644 --- a/src/app/api/cli-tools/kilo-settings/route.ts +++ b/src/app/api/cli-tools/kilo-settings/route.ts @@ -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) { diff --git a/src/app/api/cli-tools/openclaw-settings/route.ts b/src/app/api/cli-tools/openclaw-settings/route.ts index e007951bb6..3b6e37d0d4 100644 --- a/src/app/api/cli-tools/openclaw-settings/route.ts +++ b/src/app/api/cli-tools/openclaw-settings/route.ts @@ -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) { diff --git a/src/app/api/cli-tools/openclaw/auto-order/route.ts b/src/app/api/cli-tools/openclaw/auto-order/route.ts index 2cbd2cc7f9..9c9b9ae43b 100644 --- a/src/app/api/cli-tools/openclaw/auto-order/route.ts +++ b/src/app/api/cli-tools/openclaw/auto-order/route.ts @@ -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([ diff --git a/src/app/api/cli-tools/qwen-settings/route.ts b/src/app/api/cli-tools/qwen-settings/route.ts index 1e0797559d..11ca68b298 100644 --- a/src/app/api/cli-tools/qwen-settings/route.ts +++ b/src/app/api/cli-tools/qwen-settings/route.ts @@ -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) { diff --git a/src/app/api/cli-tools/runtime/[toolId]/route.ts b/src/app/api/cli-tools/runtime/[toolId]/route.ts index 27c5eb7d28..b410bd75a1 100644 --- a/src/app/api/cli-tools/runtime/[toolId]/route.ts +++ b/src/app/api/cli-tools/runtime/[toolId]/route.ts @@ -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 || "") diff --git a/src/app/api/cli-tools/status/route.ts b/src/app/api/cli-tools/status/route.ts index cc7c5aff0e..7bf7ef83e6 100644 --- a/src/app/api/cli-tools/status/route.ts +++ b/src/app/api/cli-tools/status/route.ts @@ -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 { * 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 = {}; diff --git a/src/app/api/v1/_helpers/apiKeyScope.ts b/src/app/api/v1/_helpers/apiKeyScope.ts new file mode 100644 index 0000000000..07c7afd8ac --- /dev/null +++ b/src/app/api/v1/_helpers/apiKeyScope.ts @@ -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>; + rejection: Response | null; +} + +export async function getApiKeyRequestScope(request: Request): Promise { + 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, + }; +} diff --git a/src/app/api/v1/batches/[id]/cancel/route.ts b/src/app/api/v1/batches/[id]/cancel/route.ts index a83e406e1d..bfc22823d4 100644 --- a/src/app/api/v1/batches/[id]/cancel/route.ts +++ b/src/app/api/v1/batches/[id]/cancel/route.ts @@ -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 }); } diff --git a/src/app/api/v1/batches/[id]/route.ts b/src/app/api/v1/batches/[id]/route.ts index db1cc119fb..c661fb3a21 100644 --- a/src/app/api/v1/batches/[id]/route.ts +++ b/src/app/api/v1/batches/[id]/route.ts @@ -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 }); } diff --git a/src/app/api/v1/batches/route.ts b/src/app/api/v1/batches/route.ts index 14734a7d69..f732efb76d 100644 --- a/src/app/api/v1/batches/route.ts +++ b/src/app/api/v1/batches/route.ts @@ -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 } + ); } diff --git a/src/app/api/v1/files/[id]/content/route.ts b/src/app/api/v1/files/[id]/content/route.ts index b7a150bb75..4cad037f60 100644 --- a/src/app/api/v1/files/[id]/content/route.ts +++ b/src/app/api/v1/files/[id]/content/route.ts @@ -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 } ); diff --git a/src/app/api/v1/files/[id]/route.ts b/src/app/api/v1/files/[id]/route.ts index 00baab6617..3b31db553f 100644 --- a/src/app/api/v1/files/[id]/route.ts +++ b/src/app/api/v1/files/[id]/route.ts @@ -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 } + ); } diff --git a/src/app/api/v1/files/route.ts b/src/app/api/v1/files/route.ts index 8fe4f36565..cceb347f39 100644 --- a/src/app/api/v1/files/route.ts +++ b/src/app/api/v1/files/route.ts @@ -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", diff --git a/src/app/globals.css b/src/app/globals.css index ab42b2b10a..0c06029166 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -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; diff --git a/src/lib/api/requireCliToolsAuth.ts b/src/lib/api/requireCliToolsAuth.ts new file mode 100644 index 0000000000..b5180346da --- /dev/null +++ b/src/lib/api/requireCliToolsAuth.ts @@ -0,0 +1,5 @@ +import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; + +export async function requireCliToolsAuth(request: Request): Promise { + return requireManagementAuth(request); +} diff --git a/src/lib/batches/dispatch.ts b/src/lib/batches/dispatch.ts new file mode 100644 index 0000000000..68bc5e51eb --- /dev/null +++ b/src/lib/batches/dispatch.ts @@ -0,0 +1,49 @@ +import type { SupportedBatchEndpoint } from "@/shared/constants/batchEndpoints"; + +type BatchRouteHandler = (request: Request) => Promise | Response; + +const handlerLoaders: Record Promise> = { + "/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(); + +async function getHandler(endpoint: SupportedBatchEndpoint): Promise { + 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; + apiKey?: string | null; +}): Promise { + 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); +} diff --git a/src/lib/db/batches.ts b/src/lib/db/batches.ts index 039ae0136e..f96dcd99f3 100644 --- a/src/lib/db/batches.ts +++ b/src/lib/db/batches.ts @@ -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 { +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 "?").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): 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)); } diff --git a/src/shared/constants/batchEndpoints.ts b/src/shared/constants/batchEndpoints.ts new file mode 100644 index 0000000000..1a9a92e023 --- /dev/null +++ b/src/shared/constants/batchEndpoints.ts @@ -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]; diff --git a/src/shared/services/cliRuntime.ts b/src/shared/services/cliRuntime.ts index 5a8e08effb..ea73f454ab 100644 --- a/src/shared/services/cliRuntime.ts +++ b/src/shared/services/cliRuntime.ts @@ -15,6 +15,7 @@ const CLI_TOOLS: Record = { healthcheckTimeoutMs: 4000, paths: { settings: ".claude/settings.json", + auth: ".claude/.credentials.json", }, }, codex: { diff --git a/src/shared/validation/schemas.ts b/src/shared/validation/schemas.ts index 5c9d84c205..a7f7c52b08 100644 --- a/src/shared/validation/schemas.ts +++ b/src/shared/validation/schemas.ts @@ -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)) diff --git a/src/sse/handlers/chat.ts b/src/sse/handlers/chat.ts index b66d1ea534..955ba39527 100644 --- a/src/sse/handlers/chat.ts +++ b/src/sse/handlers/chat.ts @@ -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, diff --git a/src/sse/handlers/chatHelpers.ts b/src/sse/handlers/chatHelpers.ts index 8099889ed3..ae92f1c833 100644 --- a/src/sse/handlers/chatHelpers.ts +++ b/src/sse/handlers/chatHelpers.ts @@ -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); diff --git a/tests/e2e/api.spec.ts b/tests/e2e/api.spec.ts index e358fe6209..08738d6b8b 100644 --- a/tests/e2e/api.spec.ts +++ b/tests/e2e/api.spec.ts @@ -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 { diff --git a/tests/e2e/ecosystem.test.ts b/tests/e2e/ecosystem.test.ts index 8f7c8e09ea..dfb8fbfbd5 100644 --- a/tests/e2e/ecosystem.test.ts +++ b/tests/e2e/ecosystem.test.ts @@ -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 } diff --git a/tests/e2e/helpers/dashboardAuth.ts b/tests/e2e/helpers/dashboardAuth.ts index 280d499f61..74974aece8 100644 --- a/tests/e2e/helpers/dashboardAuth.ts +++ b/tests/e2e/helpers/dashboardAuth.ts @@ -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); } diff --git a/tests/e2e/protocol-clients.test.ts b/tests/e2e/protocol-clients.test.ts index 7d31b80db1..0193e8bce5 100644 --- a/tests/e2e/protocol-clients.test.ts +++ b/tests/e2e/protocol-clients.test.ts @@ -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); } diff --git a/tests/integration/api-keys.test.ts b/tests/integration/api-keys.test.ts index 706f7dc1c1..1de3c0a1e0 100644 --- a/tests/integration/api-keys.test.ts +++ b/tests/integration/api-keys.test.ts @@ -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"); diff --git a/tests/integration/api-routes-critical.test.ts b/tests/integration/api-routes-critical.test.ts index a1f3a184e9..4ab8291f7a 100644 --- a/tests/integration/api-routes-critical.test.ts +++ b/tests/integration/api-routes-critical.test.ts @@ -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/); diff --git a/tests/integration/chat-pipeline.test.ts b/tests/integration/chat-pipeline.test.ts index ce6f4641b3..1742fbe62f 100644 --- a/tests/integration/chat-pipeline.test.ts +++ b/tests/integration/chat-pipeline.test.ts @@ -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$/); diff --git a/tests/integration/combo-routing-e2e.test.ts b/tests/integration/combo-routing-e2e.test.ts index 66427cb2af..20ea3b88fe 100644 --- a/tests/integration/combo-routing-e2e.test.ts +++ b/tests/integration/combo-routing-e2e.test.ts @@ -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 }, }); diff --git a/tests/integration/memory-pipeline.test.ts b/tests/integration/memory-pipeline.test.ts index dc0f0b73d7..998b17c0ec 100644 --- a/tests/integration/memory-pipeline.test.ts +++ b/tests/integration/memory-pipeline.test.ts @@ -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 */ } } diff --git a/tests/integration/performance-regression.test.ts b/tests/integration/performance-regression.test.ts index 4c9c5a7313..6c93076cb0 100644 --- a/tests/integration/performance-regression.test.ts +++ b/tests/integration/performance-regression.test.ts @@ -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"); diff --git a/tests/integration/proxy-registry-flow.test.ts b/tests/integration/proxy-registry-flow.test.ts index 168ebacb42..1a6c71ba35 100644 --- a/tests/integration/proxy-registry-flow.test.ts +++ b/tests/integration/proxy-registry-flow.test.ts @@ -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); }); diff --git a/tests/integration/resilience-http-e2e.test.ts b/tests/integration/resilience-http-e2e.test.ts index f6ea094ab2..8942d7c32f 100644 --- a/tests/integration/resilience-http-e2e.test.ts +++ b/tests/integration/resilience-http-e2e.test.ts @@ -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) 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 }; } diff --git a/tests/integration/skills-pipeline.test.ts b/tests/integration/skills-pipeline.test.ts index 136aa30cd7..f953d62096 100644 --- a/tests/integration/skills-pipeline.test.ts +++ b/tests/integration/skills-pipeline.test.ts @@ -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"); diff --git a/tests/integration/v1-contracts-behavior.test.ts b/tests/integration/v1-contracts-behavior.test.ts index 39dea2523b..54204e7455 100644 --- a/tests/integration/v1-contracts-behavior.test.ts +++ b/tests/integration/v1-contracts-behavior.test.ts @@ -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); }); diff --git a/tests/translator/testFromFile.ts b/tests/translator/testFromFile.ts index cb22f8dfa6..f1053bcdcd 100755 --- a/tests/translator/testFromFile.ts +++ b/tests/translator/testFromFile.ts @@ -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)); } diff --git a/tests/unit/acp-agents-route.test.ts b/tests/unit/acp-agents-route.test.ts index 58f9881644..699cb607bd 100644 --- a/tests/unit/acp-agents-route.test.ts +++ b/tests/unit/acp-agents-route.test.ts @@ -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); diff --git a/tests/unit/admin-audit-events.test.ts b/tests/unit/admin-audit-events.test.ts index 073f6d6bf8..79520eb5be 100644 --- a/tests/unit/admin-audit-events.test.ts +++ b/tests/unit/admin-audit-events.test.ts @@ -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); }); diff --git a/tests/unit/api-key-policy.test.ts b/tests/unit/api-key-policy.test.ts index ce62eeda00..9225abd417 100644 --- a/tests/unit/api-key-policy.test.ts +++ b/tests/unit/api-key-policy.test.ts @@ -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; } diff --git a/tests/unit/api-key-reveal-route.test.ts b/tests/unit/api-key-reveal-route.test.ts index 0095995dda..437c49a215 100644 --- a/tests/unit/api-key-reveal-route.test.ts +++ b/tests/unit/api-key-reveal-route.test.ts @@ -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"); diff --git a/tests/unit/audio-speech-handler.test.ts b/tests/unit/audio-speech-handler.test.ts index 3d6aced845..8dc6fcd809 100644 --- a/tests/unit/audio-speech-handler.test.ts +++ b/tests/unit/audio-speech-handler.test.ts @@ -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"); diff --git a/tests/unit/audio-transcription-handler.test.ts b/tests/unit/audio-transcription-handler.test.ts index 441c974805..5ce2cbd59b 100644 --- a/tests/unit/audio-transcription-handler.test.ts +++ b/tests/unit/audio-transcription-handler.test.ts @@ -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"); diff --git a/tests/unit/auth-clear-account-error.test.ts b/tests/unit/auth-clear-account-error.test.ts index 129d16ddbb..1131ae6370 100644 --- a/tests/unit/auth-clear-account-error.test.ts +++ b/tests/unit/auth-clear-account-error.test.ts @@ -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); diff --git a/tests/unit/auth-login-route.test.ts b/tests/unit/auth-login-route.test.ts index b7aecb1f75..f6402a2047 100644 --- a/tests/unit/auth-login-route.test.ts +++ b/tests/unit/auth-login-route.test.ts @@ -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 ); }); diff --git a/tests/unit/auth-terminal-status.test.ts b/tests/unit/auth-terminal-status.test.ts index e3cad8eea0..66069b8256 100644 --- a/tests/unit/auth-terminal-status.test.ts +++ b/tests/unit/auth-terminal-status.test.ts @@ -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); diff --git a/tests/unit/batch_api.test.ts b/tests/unit/batch_api.test.ts index bff2c3075e..808dacc5cb 100644 --- a/tests/unit/batch_api.test.ts +++ b/tests/unit/batch_api.test.ts @@ -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 () => { diff --git a/tests/unit/build-next-isolated.test.ts b/tests/unit/build-next-isolated.test.ts index ef372eb8c6..64f22ede3e 100644 --- a/tests/unit/build-next-isolated.test.ts +++ b/tests/unit/build-next-isolated.test.ts @@ -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" ); }); }); diff --git a/tests/unit/bypass-handler.test.ts b/tests/unit/bypass-handler.test.ts index 89697d8ef9..28eeeb7394 100644 --- a/tests/unit/bypass-handler.test.ts +++ b/tests/unit/bypass-handler.test.ts @@ -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"); }); diff --git a/tests/unit/call-log-cap.test.ts b/tests/unit/call-log-cap.test.ts index 3de6810d46..7c093a2a56 100644 --- a/tests/unit/call-log-cap.test.ts +++ b/tests/unit/call-log-cap.test.ts @@ -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 () => { diff --git a/tests/unit/call-log-file-rotation.test.ts b/tests/unit/call-log-file-rotation.test.ts index 8530f595cb..d6f2e3ade1 100644 --- a/tests/unit/call-log-file-rotation.test.ts +++ b/tests/unit/call-log-file-rotation.test.ts @@ -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); diff --git a/tests/unit/call-log-startup.test.ts b/tests/unit/call-log-startup.test.ts index b22d19c10b..9d556f7a87 100644 --- a/tests/unit/call-log-startup.test.ts +++ b/tests/unit/call-log-startup.test.ts @@ -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)); } diff --git a/tests/unit/cc-compatible-model-catalog.test.ts b/tests/unit/cc-compatible-model-catalog.test.ts index d227e17d62..8453abed62 100644 --- a/tests/unit/cc-compatible-model-catalog.test.ts +++ b/tests/unit/cc-compatible-model-catalog.test.ts @@ -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 ); }); diff --git a/tests/unit/cc-compatible-provider.test.ts b/tests/unit/cc-compatible-provider.test.ts index c9be1396f5..af9b47d59a 100644 --- a/tests/unit/cc-compatible-provider.test.ts +++ b/tests/unit/cc-compatible-provider.test.ts @@ -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); }); diff --git a/tests/unit/chat-combo-live-test.test.ts b/tests/unit/chat-combo-live-test.test.ts index 31c99f7517..12f416d7c4 100644 --- a/tests/unit/chat-combo-live-test.test.ts +++ b/tests/unit/chat-combo-live-test.test.ts @@ -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); diff --git a/tests/unit/chat-context-relay.test.ts b/tests/unit/chat-context-relay.test.ts index 2e932be269..168acf6a9e 100644 --- a/tests/unit/chat-context-relay.test.ts +++ b/tests/unit/chat-context-relay.test.ts @@ -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"); diff --git a/tests/unit/chat-cooldown-aware-retry.test.ts b/tests/unit/chat-cooldown-aware-retry.test.ts index e49f5c63d4..cab0138158 100644 --- a/tests/unit/chat-cooldown-aware-retry.test.ts +++ b/tests/unit/chat-cooldown-aware-retry.test.ts @@ -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); diff --git a/tests/unit/chat-helpers.test.ts b/tests/unit/chat-helpers.test.ts index 102ccfd84c..168ed13ca5 100644 --- a/tests/unit/chat-helpers.test.ts +++ b/tests/unit/chat-helpers.test.ts @@ -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", diff --git a/tests/unit/chat-rate-limit-body-lock.test.ts b/tests/unit/chat-rate-limit-body-lock.test.ts index 20876e5516..00585835d4 100644 --- a/tests/unit/chat-rate-limit-body-lock.test.ts +++ b/tests/unit/chat-rate-limit-body-lock.test.ts @@ -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); diff --git a/tests/unit/chat-route-coverage.test.ts b/tests/unit/chat-route-coverage.test.ts index f38085ff94..0ad2389e05 100644 --- a/tests/unit/chat-route-coverage.test.ts +++ b/tests/unit/chat-route-coverage.test.ts @@ -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); diff --git a/tests/unit/chat-route-edge-cases.test.ts b/tests/unit/chat-route-edge-cases.test.ts index a9bf6ca72f..073ec72b3f 100644 --- a/tests/unit/chat-route-edge-cases.test.ts +++ b/tests/unit/chat-route-edge-cases.test.ts @@ -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"); diff --git a/tests/unit/chatcore-compression-integration.test.ts b/tests/unit/chatcore-compression-integration.test.ts index a34ee21408..8cc3a8532c 100644 --- a/tests/unit/chatcore-compression-integration.test.ts +++ b/tests/unit/chatcore-compression-integration.test.ts @@ -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( diff --git a/tests/unit/chatcore-translation-paths.test.ts b/tests/unit/chatcore-translation-paths.test.ts index dc173d0fc9..0977d807e6 100644 --- a/tests/unit/chatcore-translation-paths.test.ts +++ b/tests/unit/chatcore-translation-paths.test.ts @@ -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"); }); diff --git a/tests/unit/claude-code-compatible-helpers.test.ts b/tests/unit/claude-code-compatible-helpers.test.ts index 724ad1a859..18c45e5781 100644 --- a/tests/unit/claude-code-compatible-helpers.test.ts +++ b/tests/unit/claude-code-compatible-helpers.test.ts @@ -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); }); diff --git a/tests/unit/claude-code-compatible-request.test.ts b/tests/unit/claude-code-compatible-request.test.ts index 19c94c3065..1279d384b5 100644 --- a/tests/unit/claude-code-compatible-request.test.ts +++ b/tests/unit/claude-code-compatible-request.test.ts @@ -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); diff --git a/tests/unit/cli-runtime-extended.test.ts b/tests/unit/cli-runtime-extended.test.ts index ac31f4e714..c4aadfe3dc 100644 --- a/tests/unit/cli-runtime-extended.test.ts +++ b/tests/unit/cli-runtime-extended.test.ts @@ -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(); }); diff --git a/tests/unit/cli-tools-auth-hardening.test.ts b/tests/unit/cli-tools-auth-hardening.test.ts new file mode 100644 index 0000000000..28891dcb69 --- /dev/null +++ b/tests/unit/cli-tools-auth-hardening.test.ts @@ -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" + ); +}); diff --git a/tests/unit/cloud-sync.test.ts b/tests/unit/cloud-sync.test.ts index 31d4ae1fcc..60129f3848 100644 --- a/tests/unit/cloud-sync.test.ts +++ b/tests/unit/cloud-sync.test.ts @@ -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); diff --git a/tests/unit/cloudflaredTunnel-extended.test.ts b/tests/unit/cloudflaredTunnel-extended.test.ts index 805a5a2492..f34f7516ab 100644 --- a/tests/unit/cloudflaredTunnel-extended.test.ts +++ b/tests/unit/cloudflaredTunnel-extended.test.ts @@ -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(); }); diff --git a/tests/unit/codex-connection-defaults.test.ts b/tests/unit/codex-connection-defaults.test.ts index b9bd0ab5ce..eca1033276 100644 --- a/tests/unit/codex-connection-defaults.test.ts +++ b/tests/unit/codex-connection-defaults.test.ts @@ -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"); }); diff --git a/tests/unit/codex-stream-false.test.ts b/tests/unit/codex-stream-false.test.ts index fd8f78f140..50d2988d1b 100644 --- a/tests/unit/codex-stream-false.test.ts +++ b/tests/unit/codex-stream-false.test.ts @@ -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"]); diff --git a/tests/unit/combo-builder-options-route.test.ts b/tests/unit/combo-builder-options-route.test.ts index b6c4536a04..de2137c447 100644 --- a/tests/unit/combo-builder-options-route.test.ts +++ b/tests/unit/combo-builder-options-route.test.ts @@ -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); diff --git a/tests/unit/combo-health-route.test.ts b/tests/unit/combo-health-route.test.ts index f76a96e7cd..eb73ea3976 100644 --- a/tests/unit/combo-health-route.test.ts +++ b/tests/unit/combo-health-route.test.ts @@ -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); diff --git a/tests/unit/combo-provider-cooldown.test.ts b/tests/unit/combo-provider-cooldown.test.ts index 451c5b723b..650b0682a5 100644 --- a/tests/unit/combo-provider-cooldown.test.ts +++ b/tests/unit/combo-provider-cooldown.test.ts @@ -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); diff --git a/tests/unit/combo-routes-composite-tiers.test.ts b/tests/unit/combo-routes-composite-tiers.test.ts index b0c3f1be9a..5694aedbee 100644 --- a/tests/unit/combo-routes-composite-tiers.test.ts +++ b/tests/unit/combo-routes-composite-tiers.test.ts @@ -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"); diff --git a/tests/unit/combo-routing-engine.test.ts b/tests/unit/combo-routing-engine.test.ts index c8dd07e2ac..0cb75bde1a 100644 --- a/tests/unit/combo-routing-engine.test.ts +++ b/tests/unit/combo-routing-engine.test.ts @@ -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); diff --git a/tests/unit/combo-strategies.test.ts b/tests/unit/combo-strategies.test.ts index 3dd09549bb..3a4acc84d0 100644 --- a/tests/unit/combo-strategies.test.ts +++ b/tests/unit/combo-strategies.test.ts @@ -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) {} }); diff --git a/tests/unit/combo-test-route.test.ts b/tests/unit/combo-test-route.test.ts index d1a0c0e31e..ecb1cc6875 100644 --- a/tests/unit/combo-test-route.test.ts +++ b/tests/unit/combo-test-route.test.ts @@ -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); diff --git a/tests/unit/compliance-audit-route.test.ts b/tests/unit/compliance-audit-route.test.ts index 74fe2abd2d..51e4fc9c09 100644 --- a/tests/unit/compliance-audit-route.test.ts +++ b/tests/unit/compliance-audit-route.test.ts @@ -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"); diff --git a/tests/unit/compliance-index.test.ts b/tests/unit/compliance-index.test.ts index f9394636ae..8469d862f5 100644 --- a/tests/unit/compliance-index.test.ts +++ b/tests/unit/compliance-index.test.ts @@ -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); diff --git a/tests/unit/console-log-levels.test.ts b/tests/unit/console-log-levels.test.ts index 014f04814b..531a26c329 100644 --- a/tests/unit/console-log-levels.test.ts +++ b/tests/unit/console-log-levels.test.ts @@ -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 { diff --git a/tests/unit/context-manager.test.ts b/tests/unit/context-manager.test.ts index 25e966a2c6..7cfa47fab8 100644 --- a/tests/unit/context-manager.test.ts +++ b/tests/unit/context-manager.test.ts @@ -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"); diff --git a/tests/unit/context-pinning-tool-calls.test.ts b/tests/unit/context-pinning-tool-calls.test.ts index 045e6d2f24..9d804446ef 100644 --- a/tests/unit/context-pinning-tool-calls.test.ts +++ b/tests/unit/context-pinning-tool-calls.test.ts @@ -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("ollamacloud/glm-5"), + (result[2].content as any).includes("ollamacloud/glm-5"), "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("nvidia/llama-3.4-70b")); + assert.ok((result[2] as any).content.includes("nvidia/llama-3.4-70b")); }); 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("openai/gpt-4o")); - assert.ok(result[1].content.startsWith("Hi there!")); + assert.ok((result as any)[1].content.includes("openai/gpt-4o")); + (assert as any).ok((result[1].content as any).startsWith("Hi there!")); }); test("roundtrip: inject โ†’ extract works for tool-call messages", () => { diff --git a/tests/unit/db-apikeys-crud.test.ts b/tests/unit/db-apikeys-crud.test.ts index 7b615978fe..a175b2fa86 100644 --- a/tests/unit/db-apikeys-crud.test.ts +++ b/tests/unit/db-apikeys-crud.test.ts @@ -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 { diff --git a/tests/unit/db-backup-extended.test.ts b/tests/unit/db-backup-extended.test.ts index c1e342be80..127eb97999 100644 --- a/tests/unit/db-backup-extended.test.ts +++ b/tests/unit/db-backup-extended.test.ts @@ -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 () => { diff --git a/tests/unit/db-combos-crud.test.ts b/tests/unit/db-combos-crud.test.ts index 0102d6d1ac..69b754d693 100644 --- a/tests/unit/db-combos-crud.test.ts +++ b/tests/unit/db-combos-crud.test.ts @@ -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 () => { diff --git a/tests/unit/db-core-migration.test.ts b/tests/unit/db-core-migration.test.ts index a09871c024..8069214877 100644 --- a/tests/unit/db-core-migration.test.ts +++ b/tests/unit/db-core-migration.test.ts @@ -95,18 +95,18 @@ test("Test 2: migrateFromJson migrates data to SQLite successfully", () => { const pc = db.prepare("SELECT * FROM provider_connections WHERE id = 'test-conn'").get(); assert.ok(pc); - assert.equal(pc.provider, "openai"); - assert.equal(pc.is_active, 0); + assert.equal((pc as any).provider, "openai"); + (assert as any).equal((pc as any).is_active, 0); const key = db.prepare("SELECT * FROM api_keys WHERE id = 'test-key'").get(); assert.ok(key); - assert.equal(key.name, "Key 1"); + (assert as any).equal((key as any).name, "Key 1"); const kv = db .prepare("SELECT * FROM key_value WHERE namespace = 'settings' AND key = 'globalFallbackModel'") .get(); assert.ok(kv); - assert.equal(JSON.parse(kv.value), "openai/gpt-4o"); + (assert as any).equal(JSON.parse((kv as any).value), "openai/gpt-4o"); core.resetDbInstance(); }); diff --git a/tests/unit/db-detailed-logs.test.ts b/tests/unit/db-detailed-logs.test.ts index fc53cf4adf..acdcc088b3 100644 --- a/tests/unit/db-detailed-logs.test.ts +++ b/tests/unit/db-detailed-logs.test.ts @@ -30,7 +30,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 { @@ -107,9 +107,9 @@ test("saveRequestDetailLog persists protected payloads and compacted stream summ token: "[REDACTED]", }); assert.deepEqual(row.translated_request, { message: "hello" }); - assert.equal(row.provider_response.id, "resp_123"); - assert.equal(row.provider_response.output_text, "Hello world"); - assert.deepEqual(row.provider_response._omniroute_stream, { + assert.equal((row.provider_response as any).id, "resp_123"); + assert.equal((row as any).provider_response.output_text, "Hello world"); + assert.deepEqual((row as any).provider_response._omniroute_stream, { format: "sse-json", stage: "provider-response", eventCount: 1, diff --git a/tests/unit/db-health-check.test.ts b/tests/unit/db-health-check.test.ts index f0228e5b70..52f0f0a7a6 100644 --- a/tests/unit/db-health-check.test.ts +++ b/tests/unit/db-health-check.test.ts @@ -72,9 +72,12 @@ test("runDbHealthCheck reports issues without mutating when autoRepair is disabl assert.equal(result.isHealthy, false); assert.equal(result.repairedCount, 0); assert.equal(result.issues.length, 6); - assert.equal(db.prepare("SELECT COUNT(*) AS count FROM quota_snapshots").get().count, 2); - assert.equal(db.prepare("SELECT COUNT(*) AS count FROM domain_budgets").get().count, 1); - assert.equal(db.prepare("SELECT COUNT(*) AS count FROM domain_fallback_chains").get().count, 1); + assert.equal((db.prepare("SELECT COUNT(*) AS count FROM quota_snapshots").get() as any).count, 2); + assert.equal((db.prepare("SELECT COUNT(*) AS count FROM domain_budgets").get() as any).count, 1); + assert.equal( + (db.prepare("SELECT COUNT(*) AS count FROM domain_fallback_chains").get() as any).count, + 1 + ); }); test("runDbHealthCheck tolerates databases without a combos table", async () => { @@ -102,14 +105,26 @@ test("runDbHealthCheck auto-repairs orphan rows and invalid JSON payloads", asyn assert.equal(result.isHealthy, false); assert.equal(result.backupCreated, true); assert.equal(result.repairedCount, 7); - assert.equal(db.prepare("SELECT COUNT(*) AS count FROM quota_snapshots").get().count, 0); - assert.equal(db.prepare("SELECT COUNT(*) AS count FROM domain_budgets").get().count, 0); - assert.equal(db.prepare("SELECT COUNT(*) AS count FROM domain_cost_history").get().count, 0); - assert.equal(db.prepare("SELECT COUNT(*) AS count FROM domain_fallback_chains").get().count, 0); - assert.equal(db.prepare("SELECT COUNT(*) AS count FROM domain_lockout_state").get().count, 0); + assert.equal((db.prepare("SELECT COUNT(*) AS count FROM quota_snapshots").get() as any).count, 0); + assert.equal((db.prepare("SELECT COUNT(*) AS count FROM domain_budgets").get() as any).count, 0); assert.equal( - db.prepare("SELECT options FROM domain_circuit_breakers WHERE name = ?").get("broken-breaker") - .options, + (db.prepare("SELECT COUNT(*) AS count FROM domain_cost_history").get() as any).count, + 0 + ); + assert.equal( + (db.prepare("SELECT COUNT(*) AS count FROM domain_fallback_chains").get() as any).count, + 0 + ); + assert.equal( + (db.prepare("SELECT COUNT(*) AS count FROM domain_lockout_state").get() as any).count, + 0 + ); + assert.equal( + ( + db + .prepare("SELECT options FROM domain_circuit_breakers WHERE name = ?") + .get("broken-breaker") as any + ).options, null ); }); @@ -192,10 +207,10 @@ test("runDbHealthCheck repairs broken combo payloads, combo refs and stale conne createBackupBeforeRepair: () => false, }); const invalidCombo = JSON.parse( - db.prepare("SELECT data FROM combos WHERE id = ?").get("combo-invalid").data + (db.prepare("SELECT data FROM combos WHERE id = ?").get("combo-invalid") as any).data ); const repairedCombo = JSON.parse( - db.prepare("SELECT data FROM combos WHERE id = ?").get("combo-broken").data + (db.prepare("SELECT data FROM combos WHERE id = ?").get("combo-broken") as any).data ); assert.equal( @@ -273,14 +288,26 @@ test("getDbInstance can auto-repair persisted broken rows when startup repair is } } - assert.equal(db.prepare("SELECT COUNT(*) AS count FROM quota_snapshots").get().count, 0); - assert.equal(db.prepare("SELECT COUNT(*) AS count FROM domain_budgets").get().count, 0); - assert.equal(db.prepare("SELECT COUNT(*) AS count FROM domain_cost_history").get().count, 0); - assert.equal(db.prepare("SELECT COUNT(*) AS count FROM domain_fallback_chains").get().count, 0); - assert.equal(db.prepare("SELECT COUNT(*) AS count FROM domain_lockout_state").get().count, 0); + assert.equal((db.prepare("SELECT COUNT(*) AS count FROM quota_snapshots").get() as any).count, 0); + assert.equal((db.prepare("SELECT COUNT(*) AS count FROM domain_budgets").get() as any).count, 0); assert.equal( - db.prepare("SELECT options FROM domain_circuit_breakers WHERE name = ?").get("broken-breaker") - .options, + (db.prepare("SELECT COUNT(*) AS count FROM domain_cost_history").get() as any).count, + 0 + ); + assert.equal( + (db.prepare("SELECT COUNT(*) AS count FROM domain_fallback_chains").get() as any).count, + 0 + ); + assert.equal( + (db.prepare("SELECT COUNT(*) AS count FROM domain_lockout_state").get() as any).count, + 0 + ); + assert.equal( + ( + db + .prepare("SELECT options FROM domain_circuit_breakers WHERE name = ?") + .get("broken-breaker") as any + ).options, null ); }); @@ -292,11 +319,20 @@ test("getDbInstance skips automatic startup repair during tests unless forced", db = core.getDbInstance(); - assert.equal(db.prepare("SELECT COUNT(*) AS count FROM quota_snapshots").get().count, 2); - assert.equal(db.prepare("SELECT COUNT(*) AS count FROM domain_budgets").get().count, 1); - assert.equal(db.prepare("SELECT COUNT(*) AS count FROM domain_cost_history").get().count, 1); - assert.equal(db.prepare("SELECT COUNT(*) AS count FROM domain_fallback_chains").get().count, 1); - assert.equal(db.prepare("SELECT COUNT(*) AS count FROM domain_lockout_state").get().count, 1); + assert.equal((db.prepare("SELECT COUNT(*) AS count FROM quota_snapshots").get() as any).count, 2); + assert.equal((db.prepare("SELECT COUNT(*) AS count FROM domain_budgets").get() as any).count, 1); + assert.equal( + (db.prepare("SELECT COUNT(*) AS count FROM domain_cost_history").get() as any).count, + 1 + ); + assert.equal( + (db.prepare("SELECT COUNT(*) AS count FROM domain_fallback_chains").get() as any).count, + 1 + ); + assert.equal( + (db.prepare("SELECT COUNT(*) AS count FROM domain_lockout_state").get() as any).count, + 1 + ); }); test("runDbHealthCheck repairs a drifted db_meta schema version", async () => { @@ -313,7 +349,7 @@ test("runDbHealthCheck repairs a drifted db_meta schema version", async () => { true ); assert.equal( - db.prepare("SELECT value FROM db_meta WHERE key = 'schema_version'").get().value, + (db.prepare("SELECT value FROM db_meta WHERE key = 'schema_version'").get() as any).value, "1" ); }); @@ -333,14 +369,19 @@ test("deleteApiKey removes domain budget and cost history rows for that key", as assert.equal(await apiKeysDb.deleteApiKey(created.id), true); assert.equal( - db.prepare("SELECT COUNT(*) AS count FROM domain_budgets WHERE api_key_id = ?").get(created.id) - .count, + ( + db + .prepare("SELECT COUNT(*) AS count FROM domain_budgets WHERE api_key_id = ?") + .get(created.id) as any + ).count, 0 ); assert.equal( - db - .prepare("SELECT COUNT(*) AS count FROM domain_cost_history WHERE api_key_id = ?") - .get(created.id).count, + ( + db + .prepare("SELECT COUNT(*) AS count FROM domain_cost_history WHERE api_key_id = ?") + .get((created as any).id) as any + ).count, 0 ); }); @@ -371,19 +412,21 @@ test("deleteProviderConnection and bulk delete remove related quota snapshots", VALUES (?, ?, ?, ?, ?, ?)` ).run("openai", second.id, "monthly", 40, 0, new Date().toISOString()); - assert.equal(await providersDb.deleteProviderConnection(first.id), true); + assert.equal(await providersDb.deleteProviderConnection((first as any).id), true); assert.equal( - db - .prepare("SELECT COUNT(*) AS count FROM quota_snapshots WHERE connection_id = ?") - .get(first.id).count, + ( + db.prepare("SELECT COUNT(*) AS count FROM quota_snapshots WHERE connection_id = ?") as any + ).get(first.id).count, 0 ); await providersDb.deleteProviderConnectionsByProvider("openai"); assert.equal( - db - .prepare("SELECT COUNT(*) AS count FROM quota_snapshots WHERE connection_id = ?") - .get(second.id).count, + ( + db + .prepare("SELECT COUNT(*) AS count FROM quota_snapshots WHERE connection_id = ?") + .get(second.id) as any + ).count, 0 ); }); diff --git a/tests/unit/db-health-route.test.ts b/tests/unit/db-health-route.test.ts index 91eb01b9d5..fa8c136e20 100644 --- a/tests/unit/db-health-route.test.ts +++ b/tests/unit/db-health-route.test.ts @@ -53,7 +53,7 @@ test("GET /api/v1/db/health requires authentication", async () => { try { 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.message, "Authentication required"); @@ -72,13 +72,13 @@ test("GET /api/v1/db/health diagnoses without mutating database rows", async () insertBrokenRows(db); const response = await routeModule.GET(makeRequest("GET", authKey.key)); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.equal(body.isHealthy, false); assert.equal(body.repairedCount, 0); - assert.equal(db.prepare("SELECT COUNT(*) AS count FROM quota_snapshots").get().count, 1); - assert.equal(db.prepare("SELECT COUNT(*) AS count FROM domain_budgets").get().count, 1); + assert.equal((db.prepare("SELECT COUNT(*) AS count FROM quota_snapshots").get() as any).count, 1); + assert.equal((db.prepare("SELECT COUNT(*) AS count FROM domain_budgets").get() as any).count, 1); }); test("POST /api/v1/db/health repairs broken rows for authenticated callers", async () => { @@ -87,11 +87,11 @@ test("POST /api/v1/db/health repairs broken rows for authenticated callers", asy insertBrokenRows(db); const response = await routeModule.POST(makeRequest("POST", authKey.key)); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.equal(body.isHealthy, false); assert.equal(body.repairedCount, 2); - assert.equal(db.prepare("SELECT COUNT(*) AS count FROM quota_snapshots").get().count, 0); - assert.equal(db.prepare("SELECT COUNT(*) AS count FROM domain_budgets").get().count, 0); + assert.equal((db.prepare("SELECT COUNT(*) AS count FROM quota_snapshots").get() as any).count, 0); + assert.equal((db.prepare("SELECT COUNT(*) AS count FROM domain_budgets").get() as any).count, 0); }); diff --git a/tests/unit/db-models-crud.test.ts b/tests/unit/db-models-crud.test.ts index 198a0ec2a8..7f8f63f76c 100644 --- a/tests/unit/db-models-crud.test.ts +++ b/tests/unit/db-models-crud.test.ts @@ -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 { @@ -97,7 +97,7 @@ test("custom models can be added once and queried by provider", async () => { assert.equal(duplicate.id, created.id); assert.equal(providerModels.length, 1); assert.deepEqual(providerModels[0], created); - assert.equal(allModels.openrouter.length, 1); + assert.equal((allModels.openrouter as any).length, 1); }); test("replaceCustomModels preserves compat fields and respects the empty-list guard", async () => { diff --git a/tests/unit/db-providers-crud.test.ts b/tests/unit/db-providers-crud.test.ts index b2ed7ea931..811e66ddea 100644 --- a/tests/unit/db-providers-crud.test.ts +++ b/tests/unit/db-providers-crud.test.ts @@ -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 { @@ -136,7 +136,7 @@ test("codex workspace uniqueness uses workspaceId alongside email", async () => assert.equal(workspaceAUpdate.id, workspaceA.id); assert.notEqual(workspaceB.id, workspaceA.id); assert.equal(rows.length, 2); - assert.deepEqual(rows.map((row) => row.providerSpecificData.workspaceId).sort(), [ + assert.deepEqual(rows.map((row) => (row.providerSpecificData as any).workspaceId).sort(), [ "ws-a", "ws-b", ]); @@ -162,7 +162,7 @@ test("updateProviderConnection reorders priorities and returns decrypted payload apiKey: "third-key", }); - const updated = await providersDb.updateProviderConnection(third.id, { + const updated = await providersDb.updateProviderConnection((third as any).id, { priority: 0, providerSpecificData: { region: "us-east-1" }, rateLimitProtection: true, @@ -170,7 +170,7 @@ test("updateProviderConnection reorders priorities and returns decrypted payload const ordered = await providersDb.getProviderConnections({ provider: "openai" }); - assert.equal(updated.providerSpecificData.region, "us-east-1"); + assert.equal((updated as any).providerSpecificData.region, "us-east-1"); assert.equal(updated.rateLimitProtection, true); assert.deepEqual( ordered.map((connection) => ({ @@ -205,7 +205,7 @@ test("deleteProviderConnection reorders remaining rows and bulk delete reports c apiKey: "three", }); - assert.equal(await providersDb.deleteProviderConnection(second.id), true); + assert.equal(await providersDb.deleteProviderConnection((second as any).id), true); const reordered = await providersDb.getProviderConnections({ provider: "anthropic" }); const deletedCount = await providersDb.deleteProviderConnectionsByProvider("anthropic"); @@ -238,12 +238,12 @@ test("provider node CRUD supports filter, update and delete", async () => { }); const filtered = await providersDb.getProviderNodes({ type: "custom" }); - const updated = await providersDb.updateProviderNode(customNode.id, { + const updated = await providersDb.updateProviderNode((customNode as any).id, { name: "Custom Gateway v2", chatPath: "/v1/chat/completions", modelsPath: "/v1/models", }); - const deleted = await providersDb.deleteProviderNode(openAiNode.id); + const deleted = await providersDb.deleteProviderNode((openAiNode as any).id); assert.deepEqual( filtered.map((node) => node.id), @@ -251,9 +251,9 @@ test("provider node CRUD supports filter, update and delete", async () => { ); assert.equal(updated.name, "Custom Gateway v2"); assert.equal(updated.chatPath, "/v1/chat/completions"); - assert.deepEqual(await providersDb.getProviderNodeById(customNode.id), updated); + assert.deepEqual(await providersDb.getProviderNodeById((customNode as any).id), updated); assert.equal(deleted.id, openAiNode.id); - assert.equal(await providersDb.getProviderNodeById(openAiNode.id), null); + assert.equal(await providersDb.getProviderNodeById((openAiNode as any).id), null); }); test("rate-limit helpers persist cooldown state in the database", async () => { @@ -265,9 +265,9 @@ test("rate-limit helpers persist cooldown state in the database", async () => { }); const future = Date.now() + 90_000; - providersDb.setConnectionRateLimitUntil(connection.id, future); + providersDb.setConnectionRateLimitUntil((connection as any).id, future); - assert.equal(providersDb.isConnectionRateLimited(connection.id), true); + assert.equal(providersDb.isConnectionRateLimited((connection as any).id), true); assert.deepEqual( providersDb .getRateLimitedConnections("openai") @@ -275,9 +275,9 @@ test("rate-limit helpers persist cooldown state in the database", async () => { [{ id: connection.id, rateLimitedUntil: future }] ); - providersDb.setConnectionRateLimitUntil(connection.id, null); + providersDb.setConnectionRateLimitUntil((connection as any).id, null); - assert.equal(providersDb.isConnectionRateLimited(connection.id), false); + assert.equal(providersDb.isConnectionRateLimited((connection as any).id), false); assert.deepEqual(providersDb.getRateLimitedConnections("openai"), []); }); diff --git a/tests/unit/db-proxies-crud.test.ts b/tests/unit/db-proxies-crud.test.ts index 1f9b1ee5c2..7275096871 100644 --- a/tests/unit/db-proxies-crud.test.ts +++ b/tests/unit/db-proxies-crud.test.ts @@ -20,7 +20,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 { @@ -106,14 +106,18 @@ test("proxy assignments resolve by account, provider and global scope", async () await proxiesDb.assignProxyToScope("provider", "openai", providerProxy.id); const providerResolved = await proxiesDb.resolveProxyForProvider("openai"); - const beforeAccount = await proxiesDb.resolveProxyForConnectionFromRegistry(connection.id); + const beforeAccount = await proxiesDb.resolveProxyForConnectionFromRegistry( + (connection as any).id + ); - await proxiesDb.assignProxyToScope("key", connection.id, accountProxy.id); + await proxiesDb.assignProxyToScope("key", (connection as any).id, accountProxy.id); const assignmentsForAccountProxy = await proxiesDb.getProxyAssignments({ proxyId: accountProxy.id, }); - const accountResolved = await proxiesDb.resolveProxyForConnectionFromRegistry(connection.id); + const accountResolved = await proxiesDb.resolveProxyForConnectionFromRegistry( + (connection as any).id + ); const usage = await proxiesDb.getProxyWhereUsed(accountProxy.id); assert.equal(providerResolved.host, "provider.local"); @@ -220,7 +224,7 @@ test("assignProxyToScope normalizes key scope, supports removal, and blocks dele port: 8080, }); - const assignment = await proxiesDb.assignProxyToScope("key", connection.id, proxy.id); + const assignment = await proxiesDb.assignProxyToScope("key", (connection as any).id, proxy.id); assert.equal(assignment.scope, "account"); assert.equal((await proxiesDb.getProxyAssignments({ scope: "key" })).length, 1); @@ -230,7 +234,7 @@ test("assignProxyToScope normalizes key scope, supports removal, and blocks dele /Remove assignments first or use force=true/ ); - const removed = await proxiesDb.assignProxyToScope("key", connection.id, null); + const removed = await proxiesDb.assignProxyToScope("key", (connection as any).id, null); assert.equal(removed, null); assert.equal((await proxiesDb.getProxyAssignments({ scope: "account" })).length, 0); diff --git a/tests/unit/db-read-cache.test.ts b/tests/unit/db-read-cache.test.ts index 83074f6217..4a8a7879f3 100644 --- a/tests/unit/db-read-cache.test.ts +++ b/tests/unit/db-read-cache.test.ts @@ -26,7 +26,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 { diff --git a/tests/unit/db-secrets.test.ts b/tests/unit/db-secrets.test.ts index e44c42bd4a..0d085c2176 100644 --- a/tests/unit/db-secrets.test.ts +++ b/tests/unit/db-secrets.test.ts @@ -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 { diff --git a/tests/unit/db-settings-crud.test.ts b/tests/unit/db-settings-crud.test.ts index 9f1fdebb0b..41813d5adb 100644 --- a/tests/unit/db-settings-crud.test.ts +++ b/tests/unit/db-settings-crud.test.ts @@ -23,7 +23,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 { @@ -437,7 +437,7 @@ test("proxy helpers resolve key, provider, global, and direct paths while tolera }); db.prepare("UPDATE combos SET data = ? WHERE id = ?").run("{not-json", combo.id); - const providerResolved = await settingsDb.resolveProxyForConnection(connection.id); + const providerResolved = await settingsDb.resolveProxyForConnection((connection as any).id); assert.equal(providerResolved.level, "provider"); assert.equal(providerResolved.proxy.host, "provider.local"); @@ -449,26 +449,26 @@ test("proxy helpers resolve key, provider, global, and direct paths while tolera await settingsDb.deleteProxyForLevel("provider", "openai"); - const globalResolved = await settingsDb.resolveProxyForConnection(connection.id); + const globalResolved = await settingsDb.resolveProxyForConnection((connection as any).id); assert.equal(globalResolved.level, "global"); assert.equal(globalResolved.proxy.host, "global.local"); - await settingsDb.setProxyForLevel("key", connection.id, { + await settingsDb.setProxyForLevel("key", (connection as any).id, { type: "http", host: "key.local", port: 3128, }); - const keyResolved = await settingsDb.resolveProxyForConnection(connection.id); + const keyResolved = await settingsDb.resolveProxyForConnection((connection as any).id); assert.equal(keyResolved.level, "key"); assert.equal(keyResolved.proxy.host, "key.local"); - await settingsDb.deleteProxyForLevel("key", connection.id); + await settingsDb.deleteProxyForLevel("key", (connection as any).id); await settingsDb.deleteProxyForLevel("global", null); - const directResolved = await settingsDb.resolveProxyForConnection(connection.id); + const directResolved = await settingsDb.resolveProxyForConnection((connection as any).id); assert.equal(directResolved.level, "direct"); assert.equal(directResolved.proxy, null); @@ -494,14 +494,14 @@ test("proxy resolution skips combos without serialized data and falls back to pr models: ["claude/claude-3-5-sonnet"], strategy: "priority", }); - await settingsDb.setProxyForLevel("combo", combo.id, { + await settingsDb.setProxyForLevel("combo" as any, (combo as any).id, { type: "http", host: "combo-null.local", port: 8080, }); db.prepare("UPDATE combos SET data = ? WHERE id = ?").run(0, combo.id); - const resolved = await settingsDb.resolveProxyForConnection(connection.id); + const resolved = await settingsDb.resolveProxyForConnection((connection as any).id); assert.equal(resolved.level, "provider"); assert.equal(resolved.proxy.host, "provider-claude.local"); @@ -520,13 +520,13 @@ test("proxy resolution matches combo proxies through aliased model entries", asy models: [{ model: "cc/claude-3-5-sonnet" }], strategy: "priority", }); - await settingsDb.setProxyForLevel("combo", combo.id, { + await settingsDb.setProxyForLevel("combo", (combo as any).id, { type: "https", host: "combo-alias.local", port: 443, }); - const resolved = await settingsDb.resolveProxyForConnection(connection.id); + const resolved = await settingsDb.resolveProxyForConnection((connection as any).id); assert.equal(resolved.level, "combo"); assert.equal(resolved.levelId, combo.id); diff --git a/tests/unit/display-and-error-utils.test.ts b/tests/unit/display-and-error-utils.test.ts index 5725ab6a6c..55b89c1de1 100644 --- a/tests/unit/display-and-error-utils.test.ts +++ b/tests/unit/display-and-error-utils.test.ts @@ -76,7 +76,7 @@ test("createErrorResponse: infers error types from status and preserves details" message: "Conflict detected", details: { field: "name" }, }); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 409); assert.equal(body.error.message, "Conflict detected"); @@ -91,7 +91,7 @@ test("createErrorResponse: uses explicit type when provided", async () => { message: "teapot", type: "not_found", }); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(body.error.type, "not_found"); }); @@ -103,7 +103,7 @@ test("createErrorResponseFromUnknown: normalizes typed errors", async () => { type: "server_error", details: { retryable: true }, }); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 503); assert.equal(body.error.message, "db exploded"); @@ -113,7 +113,7 @@ test("createErrorResponseFromUnknown: normalizes typed errors", async () => { test("createErrorResponseFromUnknown: falls back for non-object errors", async () => { const response = createErrorResponseFromUnknown("boom", "fallback message"); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 500); assert.equal(body.error.message, "fallback message"); diff --git a/tests/unit/domain-branch-hardening.test.ts b/tests/unit/domain-branch-hardening.test.ts index 8f6071dbee..bf2319d440 100644 --- a/tests/unit/domain-branch-hardening.test.ts +++ b/tests/unit/domain-branch-hardening.test.ts @@ -37,7 +37,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 { diff --git a/tests/unit/domain-cost-rules.test.ts b/tests/unit/domain-cost-rules.test.ts index 63216800cf..874ba257ee 100644 --- a/tests/unit/domain-cost-rules.test.ts +++ b/tests/unit/domain-cost-rules.test.ts @@ -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 { diff --git a/tests/unit/domain-fallback-policy.test.ts b/tests/unit/domain-fallback-policy.test.ts index 0f1cd28e93..28730aebc6 100644 --- a/tests/unit/domain-fallback-policy.test.ts +++ b/tests/unit/domain-fallback-policy.test.ts @@ -20,7 +20,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 { diff --git a/tests/unit/domain-lockout-policy.test.ts b/tests/unit/domain-lockout-policy.test.ts index 7f62a6fb88..758742dc87 100644 --- a/tests/unit/domain-lockout-policy.test.ts +++ b/tests/unit/domain-lockout-policy.test.ts @@ -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 { diff --git a/tests/unit/empty-tool-name-loop.test.ts b/tests/unit/empty-tool-name-loop.test.ts index 533d718b1b..f5a84a85f3 100644 --- a/tests/unit/empty-tool-name-loop.test.ts +++ b/tests/unit/empty-tool-name-loop.test.ts @@ -18,7 +18,7 @@ test("openaiResponsesToOpenAIRequest: skips function_call items with empty name" }; const result = openaiResponsesToOpenAIRequest("gpt-4", body, true, {}); - const messages = result.messages; + const messages = (result as any).messages; // Should have only the user message, no assistant message with empty tool call const assistantMsgs = messages.filter((m) => m.role === "assistant"); @@ -35,7 +35,7 @@ test("openaiResponsesToOpenAIRequest: skips function_call items with whitespace- }; const result = openaiResponsesToOpenAIRequest("gpt-4", body, true, {}); - const messages = result.messages; + const messages = (result as any).messages; const assistantMsgs = messages.filter((m) => m.role === "assistant"); assert.equal(assistantMsgs.length, 0, "whitespace-only name function_call should be skipped"); @@ -56,7 +56,7 @@ test("openaiResponsesToOpenAIRequest: keeps function_call items with valid name" }; const result = openaiResponsesToOpenAIRequest("gpt-4", body, true, {}); - const messages = result.messages; + const messages = (result as any).messages; const assistantMsgs = messages.filter((m) => m.role === "assistant"); assert.equal(assistantMsgs.length, 1, "valid function_call should produce assistant message"); @@ -81,7 +81,7 @@ test("openaiResponsesToOpenAIRequest: mixed valid and empty names keeps only val }; const result = openaiResponsesToOpenAIRequest("gpt-4", body, true, {}); - const messages = result.messages; + const messages = (result as any).messages; const assistantMsgs = messages.filter((m) => m.role === "assistant"); assert.equal(assistantMsgs.length, 1); @@ -106,7 +106,7 @@ test("openaiToOpenAIResponsesRequest: skips tool_calls with empty function name" }; const result = openaiToOpenAIResponsesRequest("gpt-4", body, true, {}); - const fnCalls = result.input.filter((i) => i.type === "function_call"); + const fnCalls = (result as any).input.filter((i) => i.type === "function_call"); assert.equal(fnCalls.length, 0, "empty-name tool_call should be skipped"); }); @@ -125,7 +125,7 @@ test("openaiToOpenAIResponsesRequest: skips tool_calls with whitespace-only func }; const result = openaiToOpenAIResponsesRequest("gpt-4", body, true, {}); - const fnCalls = result.input.filter((i) => i.type === "function_call"); + const fnCalls = (result as any).input.filter((i) => i.type === "function_call"); assert.equal(fnCalls.length, 0, "whitespace-only name tool_call should be skipped"); }); @@ -148,7 +148,7 @@ test("openaiToOpenAIResponsesRequest: keeps tool_calls with valid function name" }; const result = openaiToOpenAIResponsesRequest("gpt-4", body, true, {}); - const fnCalls = result.input.filter((i) => i.type === "function_call"); + const fnCalls = (result as any).input.filter((i) => i.type === "function_call"); assert.equal(fnCalls.length, 1); assert.equal(fnCalls[0].name, "get_weather"); }); @@ -174,7 +174,7 @@ test("openaiToOpenAIResponsesRequest: mixed valid and empty names keeps only val }; const result = openaiToOpenAIResponsesRequest("gpt-4", body, true, {}); - const fnCalls = result.input.filter((i) => i.type === "function_call"); + const fnCalls = (result as any).input.filter((i) => i.type === "function_call"); assert.equal(fnCalls.length, 1, "only valid tool call should remain"); assert.equal(fnCalls[0].name, "get_weather"); }); diff --git a/tests/unit/executor-antigravity.test.ts b/tests/unit/executor-antigravity.test.ts index eeeffcbedd..0e9eed230b 100644 --- a/tests/unit/executor-antigravity.test.ts +++ b/tests/unit/executor-antigravity.test.ts @@ -153,7 +153,7 @@ test("AntigravityExecutor.transformRequest returns a structured error response w true, {} ); - const payload = await result.json(); + const payload = (await result.json()) as any; assert.equal(result.status, 422); assert.equal(payload.error.code, "missing_project_id"); @@ -231,7 +231,7 @@ test("AntigravityExecutor.collectStreamToResponse turns SSE Gemini chunks into a { Authorization: "Bearer ag-token" }, { request: {} } ); - const payload = await result.response.json(); + const payload = (await result.response.json()) as any; assert.equal(result.response.status, 200); assert.equal(payload.object, "chat.completion"); @@ -316,7 +316,7 @@ test("AntigravityExecutor.execute auto-retries short 429 responses and collects credentials: { accessToken: "token", projectId: "project-1" }, log: { debug() {}, warn() {} }, }); - const payload = await result.response.json(); + const payload = (await result.response.json()) as any; assert.equal(calls.length, 2); assert.equal(result.response.status, 200); @@ -358,7 +358,7 @@ test("AntigravityExecutor.execute embeds retryAfterMs when the upstream asks for credentials: { accessToken: "token", projectId: "project-1" }, log: { debug() {}, warn() {} }, }); - const payload = await result.response.json(); + const payload = (await result.response.json()) as any; assert.equal(result.response.status, 429); assert.equal(payload.retryAfterMs, 7_200_000); diff --git a/tests/unit/executor-cursor-extended.test.ts b/tests/unit/executor-cursor-extended.test.ts index 6944a3e508..0f008bf9c9 100644 --- a/tests/unit/executor-cursor-extended.test.ts +++ b/tests/unit/executor-cursor-extended.test.ts @@ -197,7 +197,7 @@ test("CursorExecutor.transformProtobufToJSON aggregates text and split tool call ]); const response = executor.transformProtobufToJSON(buffer, "cursor-small", body); - const payload = await response.json(); + const payload = (await response.json()) as any; assert.equal(response.status, 200); assert.equal(payload.object, "chat.completion"); @@ -223,7 +223,7 @@ test("CursorExecutor.transformProtobufToJSON finalizes incomplete tool calls whe "cursor-small", { messages: [{ role: "user", content: "hi" }] } ); - const payload = await response.json(); + const payload = (await response.json()) as any; assert.equal(payload.choices[0].finish_reason, "tool_calls"); assert.equal(payload.choices[0].message.tool_calls[0].id, "call_2"); @@ -245,7 +245,7 @@ test("CursorExecutor.transformProtobufToJSON keeps prior content when an error f "cursor-small", { messages: [{ role: "user", content: "hi" }] } ); - const payload = await response.json(); + const payload = (await response.json()) as any; assert.equal(response.status, 200); assert.equal(payload.choices[0].message.content, "Partial answer"); @@ -259,7 +259,7 @@ test("CursorExecutor.transformProtobufToJSON decompresses gzip frames", async () "cursor-small", { messages: [{ role: "user", content: "hi" }] } ); - const payload = await response.json(); + const payload = (await response.json()) as any; assert.equal(payload.choices[0].message.content, "Compressed answer"); }); @@ -329,7 +329,7 @@ test("CursorExecutor.transformProtobufToSSE returns a JSON error before any cont "cursor-small", { messages: [{ role: "user", content: "hi" }] } ); - const payload = await response.json(); + const payload = (await response.json()) as any; assert.equal(response.status, 429); assert.equal(payload.error.type, "rate_limit_error"); @@ -407,7 +407,7 @@ test("CursorExecutor.transformProtobufToSSE converts JSON error frames into rate "cursor-small", { messages: [{ role: "user", content: "hi" }] } ); - const payload = await response.json(); + const payload = (await response.json()) as any; assert.equal(response.status, 429); assert.equal(payload.error.type, "rate_limit_error"); @@ -435,7 +435,7 @@ test("CursorExecutor.execute returns transformed JSON for non-stream responses", providerSpecificData: { machineId: "machine-1" }, }, }); - const payload = await result.response.json(); + const payload = (await result.response.json()) as any; assert.equal( result.url, @@ -494,7 +494,7 @@ test("CursorExecutor.execute maps non-200 upstream responses to OpenAI-style err providerSpecificData: { machineId: "machine-1" }, }, }); - const payload = await result.response.json(); + const payload = (await result.response.json()) as any; assert.equal(result.response.status, 403); assert.equal(payload.error.type, "invalid_request_error"); @@ -517,7 +517,7 @@ test("CursorExecutor.execute maps transport failures to connection_error and ref providerSpecificData: { machineId: "machine-1" }, }, }); - const payload = await result.response.json(); + const payload = (await result.response.json()) as any; assert.equal(result.response.status, 500); assert.equal(payload.error.type, "connection_error"); diff --git a/tests/unit/executor-default-base.test.ts b/tests/unit/executor-default-base.test.ts index 5d1ce319f9..ce1d8af75a 100644 --- a/tests/unit/executor-default-base.test.ts +++ b/tests/unit/executor-default-base.test.ts @@ -428,7 +428,7 @@ test("DefaultExecutor.transformRequest neutralizes incompatible tool_choice for const result = executor.transformRequest("qwen3-coder-plus", body, true, {}); assert.notEqual(result, body); - assert.equal(result.tool_choice, "auto"); + assert.equal((result as any).tool_choice, "auto"); }); test("DefaultExecutor.transformRequest applies GLMT preset defaults without overriding explicit values", () => { @@ -440,9 +440,9 @@ test("DefaultExecutor.transformRequest applies GLMT preset defaults without over const autoResult = executor.transformRequest("glm-5.1", autoBody, true, {}); assert.notEqual(autoResult, autoBody); - assert.equal(autoResult.max_tokens, 65536); - assert.equal(autoResult.temperature, 0.2); - assert.deepEqual(autoResult.thinking, { + assert.equal((autoResult as any).max_tokens, 65536); + (assert as any).equal((autoResult as any).temperature, 0.2); + (assert as any).deepEqual((autoResult as any).thinking, { type: "enabled", budget_tokens: 24576, }); @@ -456,9 +456,9 @@ test("DefaultExecutor.transformRequest applies GLMT preset defaults without over const explicitResult = executor.transformRequest("glm-5.1", explicitBody, true, {}); assert.notEqual(explicitResult, explicitBody); - assert.equal(explicitResult.max_tokens, 4096); - assert.equal(explicitResult.temperature, 0.7); - assert.deepEqual(explicitResult.thinking, { + assert.equal((explicitResult as any).max_tokens, 4096); + assert.equal((explicitResult as any).temperature, 0.7); + assert.deepEqual((explicitResult as any).thinking, { type: "enabled", budget_tokens: 4095, }); @@ -621,8 +621,8 @@ test("BaseExecutor.execute returns response metadata and merges headers", async assert.equal(result.url, "https://primary.example/v1/chat/completions"); assert.equal(result.response.status, 200); - assert.equal(result.transformedBody.transformed, true); - assert.equal(result.transformedBody.model, "gpt-4.1"); + (assert as any).equal((result.transformedBody as any).transformed, true); + assert.equal((result.transformedBody as any).model, "gpt-4.1"); assert.equal(result.headers.Authorization, "Bearer override"); assert.equal(result.headers["User-Agent"], "UpstreamAgent/2.0"); assert.equal(result.headers["user-agent"], undefined); diff --git a/tests/unit/executor-kiro.test.ts b/tests/unit/executor-kiro.test.ts index a886b4bef8..1a6bc1c2ee 100644 --- a/tests/unit/executor-kiro.test.ts +++ b/tests/unit/executor-kiro.test.ts @@ -110,7 +110,10 @@ test("KiroExecutor.transformRequest removes the top-level model field", () => { const result = executor.transformRequest("kiro-model", body, true, {}); assert.equal("model" in result, false); - assert.equal(result.conversationState.currentMessage.userInputMessage.modelId, "kiro-model"); + assert.equal( + (result as any).conversationState.currentMessage.userInputMessage.modelId, + "kiro-model" + ); }); test("KiroExecutor.transformEventStreamToSSE converts text, tool calls, usage and DONE", async () => { diff --git a/tests/unit/fetch-timeout.test.ts b/tests/unit/fetch-timeout.test.ts index b709643245..8e90d03927 100644 --- a/tests/unit/fetch-timeout.test.ts +++ b/tests/unit/fetch-timeout.test.ts @@ -64,9 +64,9 @@ test("fetchWithTimeout converts both pre-aborted and externally aborted requests }), (error) => { assert.equal(error instanceof mod.FetchTimeoutError, true); - assert.equal(error.timeoutMs, 9); - assert.equal(error.url, "https://example.test/pre-aborted"); - assert.match(error.message, /timed out after 9ms/); + assert.equal((error as any).timeoutMs, 9); + (assert as any).equal((error as any).url, "https://example.test/pre-aborted"); + (assert as any).match((error as any).message, /timed out after 9ms/); return true; } ); @@ -91,8 +91,8 @@ test("fetchWithTimeout converts both pre-aborted and externally aborted requests (error) => { assert.equal(fetchSawSignal, true); assert.equal(error instanceof mod.FetchTimeoutError, true); - assert.equal(error.timeoutMs, 15); - assert.equal(error.url, "https://example.test/external-abort"); + assert.equal((error as any).timeoutMs, 15); + assert.equal((error as any).url, "https://example.test/external-abort"); return true; } ); diff --git a/tests/unit/fixes-p1.test.ts b/tests/unit/fixes-p1.test.ts index 12133ed3fd..9fa9b92004 100644 --- a/tests/unit/fixes-p1.test.ts +++ b/tests/unit/fixes-p1.test.ts @@ -50,7 +50,7 @@ async function resetStorage() { fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); } break; - } catch (err) { + } catch (err: any) { if ((err?.code === "EBUSY" || err?.code === "EPERM") && attempt < 9) { await new Promise((r) => setTimeout(r, 100 * (attempt + 1))); } else { @@ -140,7 +140,7 @@ test( const row = reopenedDb .prepare("SELECT COUNT(*) AS cnt FROM provider_connections WHERE id = ?") .get("restore-test-conn"); - assert.equal(row.cnt, 1); + assert.equal((row as any).cnt, 1); } ); @@ -164,7 +164,7 @@ test("closeDbInstance checkpoints WAL changes into the primary SQLite file", asy const row = snapshotDb .prepare("SELECT name FROM provider_connections WHERE id = ?") .get("checkpoint-test-conn"); - assert.equal(row?.name, "checkpoint-test"); + (assert as any).equal((row as any).name, "checkpoint-test"); } finally { snapshotDb.close(); } @@ -273,13 +273,13 @@ test("provider connection persists rateLimitProtection across reopen", async () apiKey: "sk-test", }); - await providersDb.updateProviderConnection(created.id, { rateLimitProtection: true }); + await providersDb.updateProviderConnection((created as any).id, { rateLimitProtection: true }); - const firstRead = await providersDb.getProviderConnectionById(created.id); + const firstRead = await providersDb.getProviderConnectionById((created as any).id); assert.equal(firstRead.rateLimitProtection, true); core.resetDbInstance(); - const secondRead = await providersDb.getProviderConnectionById(created.id); + const secondRead = await providersDb.getProviderConnectionById((created as any).id); assert.equal(secondRead.rateLimitProtection, true); }); @@ -336,7 +336,7 @@ test('provider connection migration adds "group" column for existing databases', const reopened = core.getDbInstance(); const columns = reopened.prepare("PRAGMA table_info(provider_connections)").all(); - const names = new Set(columns.map((column) => column.name)); + const names = new Set(columns.map((column) => (column as any).name)); assert.equal(names.has("group"), true); }); @@ -358,13 +358,13 @@ test("resolveProxyForConnection applies combo proxy for object/string model entr models: ["openai/gpt-5", { model: "cc/claude-sonnet-4-5-20250929", weight: 100 }], }); - await settingsDb.setProxyForLevel("combo", combo.id, { + await settingsDb.setProxyForLevel("combo", (combo as any).id, { type: "http", host: "127.0.0.1", port: "8080", }); - const resolved = await settingsDb.resolveProxyForConnection(conn.id); + const resolved = await settingsDb.resolveProxyForConnection((conn as any).id); assert.equal(resolved.level, "combo"); assert.equal(resolved.levelId, combo.id); }); @@ -464,7 +464,7 @@ test("proxy settings route blocks socks5 with backend flag disabled", async () = }); const response = await proxySettingsRoute.PUT(request); - const payload = await response.json(); + const payload = (await response.json()) as any; assert.equal(response.status, 400); assert.match(payload.error.message, /SOCKS5 proxy is disabled/i); }); @@ -488,7 +488,7 @@ test("proxy settings route accepts socks5 with backend flag enabled", async () = }); const response = await proxySettingsRoute.PUT(request); - const payload = await response.json(); + const payload = (await response.json()) as any; assert.equal(response.status, 200); assert.equal(payload.global.type, "socks5"); }); @@ -509,7 +509,7 @@ test("proxy test route rejects socks5 when backend flag is disabled", async () = }); const response = await proxyTestRoute.POST(request); - const payload = await response.json(); + const payload = (await response.json()) as any; assert.equal(response.status, 400); assert.match(payload.error.message, /SOCKS5 proxy is disabled/i); @@ -531,7 +531,7 @@ test("proxy test route runs socks5 test when backend flag is enabled", async () }); const response = await proxyTestRoute.POST(request); - const payload = await response.json(); + const payload = (await response.json()) as any; assert.notEqual(response.status, 400); assert.equal(payload.success, false); @@ -549,7 +549,7 @@ test("proxy test route validates JSON, schema, and proxy types before dispatchin body: "{", }) ); - const invalidJsonBody = await invalidJsonResponse.json(); + const invalidJsonBody = (await invalidJsonResponse.json()) as any; assert.equal(invalidJsonResponse.status, 400); assert.equal(invalidJsonBody.error.message, "Invalid JSON body"); @@ -560,7 +560,7 @@ test("proxy test route validates JSON, schema, and proxy types before dispatchin body: JSON.stringify({ proxy: { port: "8080" } }), }) ); - const invalidBody = await invalidBodyResponse.json(); + const invalidBody = (await invalidBodyResponse.json()) as any; assert.equal(invalidBodyResponse.status, 400); assert.equal(invalidBody.error.message, "Invalid request"); @@ -577,7 +577,7 @@ test("proxy test route validates JSON, schema, and proxy types before dispatchin }), }) ); - const socks4Body = await socks4Response.json(); + const socks4Body = (await socks4Response.json()) as any; assert.equal(socks4Response.status, 400); assert.match(socks4Body.error.message, /proxy\.type must be http or https/i); @@ -594,7 +594,7 @@ test("proxy test route validates JSON, schema, and proxy types before dispatchin }), }) ); - const unsupportedBody = await unsupportedResponse.json(); + const unsupportedBody = (await unsupportedResponse.json()) as any; assert.equal(unsupportedResponse.status, 400); assert.match(unsupportedBody.error.message, /proxy\.type must be http or https/i); }); @@ -615,7 +615,7 @@ test("proxy test route handles invalid proxy ports and uses stored proxy config }), }) ); - const invalidPortBody = await invalidPortResponse.json(); + const invalidPortBody = (await invalidPortResponse.json()) as any; assert.equal(invalidPortResponse.status, 400); assert.match(invalidPortBody.error.message, /invalid proxy port/i); @@ -641,7 +641,7 @@ test("proxy test route handles invalid proxy ports and uses stored proxy config }), }) ); - const proxyIdBody = await proxyIdResponse.json(); + const proxyIdBody = (await proxyIdResponse.json()) as any; assert.notEqual(proxyIdResponse.status, 400); assert.equal(proxyIdBody.success, false); assert.equal(proxyIdBody.proxyUrl, "http://127.0.0.1:1"); diff --git a/tests/unit/glm-provider-model-import-route.test.ts b/tests/unit/glm-provider-model-import-route.test.ts index 90b060acdc..c8d1dfb20e 100644 --- a/tests/unit/glm-provider-model-import-route.test.ts +++ b/tests/unit/glm-provider-model-import-route.test.ts @@ -113,7 +113,7 @@ test("GLM import uses China coding endpoint when apiRegion is china", async () = { params: { id: connection.id } } ); assert.equal(response.status, 200); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(body.provider, "glm"); assert.equal(body.models.length, 1); } finally { diff --git a/tests/unit/grok-web.test.ts b/tests/unit/grok-web.test.ts index 1c1573272f..4559ad1276 100644 --- a/tests/unit/grok-web.test.ts +++ b/tests/unit/grok-web.test.ts @@ -93,7 +93,7 @@ test("Non-streaming: simple response", async () => { log: null, }); assert.equal(result.response.status, 200); - const json = await result.response.json(); + const json = (await result.response.json()) as any; assert.equal(json.object, "chat.completion"); assert.equal(json.choices[0].message.role, "assistant"); assert.equal(json.choices[0].message.content, "Hello world!"); @@ -152,7 +152,7 @@ test("Error: 401 returns auth error", async () => { log: null, }); assert.equal(result.response.status, 401); - const json = await result.response.json(); + const json = (await result.response.json()) as any; assert.ok(json.error.message.includes("auth failed")); assert.ok(json.error.message.includes("sso")); } finally { @@ -173,7 +173,7 @@ test("Error: 429 returns rate limit message", async () => { log: null, }); assert.equal(result.response.status, 429); - const json = await result.response.json(); + const json = (await result.response.json()) as any; assert.ok(json.error.message.includes("rate limited")); } finally { restore(); @@ -206,7 +206,7 @@ test("Error: Grok stream error returns 502", async () => { log: null, }); assert.equal(result.response.status, 502); - const json = await result.response.json(); + const json = (await result.response.json()) as any; assert.ok(json.error.message.includes("Internal error")); } finally { restore(); diff --git a/tests/unit/guide-settings-route.test.ts b/tests/unit/guide-settings-route.test.ts index cb44150d98..d408e98d3b 100644 --- a/tests/unit/guide-settings-route.test.ts +++ b/tests/unit/guide-settings-route.test.ts @@ -40,7 +40,7 @@ test.afterEach(async () => { test("guide-settings POST creates new qwen settings.json if it doesn't exist", async () => { const req = buildRequest({ baseUrl: "http://my-omni", apiKey: "sk-123", model: "qwen-max" }); const response = (await guideSettingsRoute.POST(req, { params: { toolId: "qwen" } })) as Response; - const data = await response.json(); + const data = (await response.json()) as any; assert.equal(response.status, 200, "Response should be OK"); assert.equal(data.success, true); diff --git a/tests/unit/image-generation-route.test.ts b/tests/unit/image-generation-route.test.ts index 164eb93680..e3ec9e157f 100644 --- a/tests/unit/image-generation-route.test.ts +++ b/tests/unit/image-generation-route.test.ts @@ -44,13 +44,13 @@ test.after(() => { test("v1 image models GET exposes image-only modalities for image-only models", async () => { const response = await imageRoute.GET(); - const body = await response.json(); + const body = (await response.json()) as any; const byId = new Map(body.data.map((item: { id: string }) => [item.id, item])); assert.equal(response.status, 200); - assert.deepEqual(byId.get("topaz/topaz-enhance")?.input_modalities, ["image"]); - assert.deepEqual(byId.get("stability-ai/remove-background")?.input_modalities, ["image"]); - assert.deepEqual(byId.get("stability-ai/fast")?.input_modalities, ["image"]); + assert.deepEqual((byId.get("topaz/topaz-enhance") as any).input_modalities, ["image"]); + assert.deepEqual((byId.get("stability-ai/remove-background") as any).input_modalities, ["image"]); + assert.deepEqual((byId.get("stability-ai/fast") as any).input_modalities, ["image"]); }); test("v1 image generation POST accepts promptless requests for image-only models", async () => { @@ -89,7 +89,7 @@ test("v1 image generation POST accepts promptless requests for image-only models }), }) ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.equal(body.data[0].b64_json, "BwcH"); @@ -106,7 +106,7 @@ test("v1 image generation POST still requires prompts for text-input models", as }), }) ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 400); assert.match(body.error.message, /Prompt is required for image model: openai\/dall-e-3/); diff --git a/tests/unit/log-retention.test.ts b/tests/unit/log-retention.test.ts index bf9510f12b..049a80abfe 100644 --- a/tests/unit/log-retention.test.ts +++ b/tests/unit/log-retention.test.ts @@ -127,12 +127,12 @@ test("cleanupExpiredLogs uses separate APP and CALL retention windows", () => { assert.equal(result.deletedMcpAuditLogs, 1); assert.deepEqual(compliance.getRetentionDays(), { app: 2, call: 1 }); - assert.equal(db.prepare("SELECT COUNT(*) AS cnt FROM usage_history").get().cnt, 1); - assert.equal(db.prepare("SELECT COUNT(*) AS cnt FROM call_logs").get().cnt, 1); - assert.equal(db.prepare("SELECT COUNT(*) AS cnt FROM proxy_logs").get().cnt, 1); - assert.equal(db.prepare("SELECT COUNT(*) AS cnt FROM request_detail_logs").get().cnt, 1); - assert.equal(db.prepare("SELECT COUNT(*) AS cnt FROM audit_log").get().cnt, 2); - assert.equal(db.prepare("SELECT COUNT(*) AS cnt FROM mcp_tool_audit").get().cnt, 1); + assert.equal((db.prepare("SELECT COUNT(*) AS cnt FROM usage_history").get() as any).cnt, 1); + assert.equal((db.prepare("SELECT COUNT(*) AS cnt FROM call_logs").get() as any).cnt, 1); + assert.equal((db.prepare("SELECT COUNT(*) AS cnt FROM proxy_logs").get() as any).cnt, 1); + assert.equal((db.prepare("SELECT COUNT(*) AS cnt FROM request_detail_logs").get() as any).cnt, 1); + assert.equal((db.prepare("SELECT COUNT(*) AS cnt FROM audit_log").get() as any).cnt, 2); + assert.equal((db.prepare("SELECT COUNT(*) AS cnt FROM mcp_tool_audit").get() as any).cnt, 1); }); test("cleanupExpiredLogs enforces row count limits", () => { @@ -166,8 +166,8 @@ test("cleanupExpiredLogs enforces row count limits", () => { ).run(`proxy-${i}`, now, "success", "direct", 1); } - assert.equal(db.prepare("SELECT COUNT(*) AS cnt FROM call_logs").get().cnt, 10); - assert.equal(db.prepare("SELECT COUNT(*) AS cnt FROM proxy_logs").get().cnt, 10); + assert.equal((db.prepare("SELECT COUNT(*) AS cnt FROM call_logs").get() as any).cnt, 10); + assert.equal((db.prepare("SELECT COUNT(*) AS cnt FROM proxy_logs").get() as any).cnt, 10); const result = compliance.cleanupExpiredLogs(); @@ -176,8 +176,8 @@ test("cleanupExpiredLogs enforces row count limits", () => { assert.equal(result.callLogsMaxRows, 5); assert.equal(result.proxyLogsMaxRows, 5); - assert.equal(db.prepare("SELECT COUNT(*) AS cnt FROM call_logs").get().cnt, 5); - assert.equal(db.prepare("SELECT COUNT(*) AS cnt FROM proxy_logs").get().cnt, 5); + assert.equal((db.prepare("SELECT COUNT(*) AS cnt FROM call_logs").get() as any).cnt, 5); + assert.equal((db.prepare("SELECT COUNT(*) AS cnt FROM proxy_logs").get() as any).cnt, 5); }); test("getCallLogsTableMaxRows returns configured value", async () => { diff --git a/tests/unit/login-bootstrap-route.test.ts b/tests/unit/login-bootstrap-route.test.ts index 440c2283c2..3ffa4f5e6e 100644 --- a/tests/unit/login-bootstrap-route.test.ts +++ b/tests/unit/login-bootstrap-route.test.ts @@ -42,7 +42,7 @@ test("public login bootstrap route exposes the metadata the login page consumes" }); const response = await route.GET(); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.deepEqual(body, { @@ -63,7 +63,7 @@ test("public login bootstrap route reports env-provided bootstrap password metad }); const response = await route.GET(); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.deepEqual(body, { @@ -83,7 +83,7 @@ test("public login bootstrap route reports stored password metadata and disabled }); const response = await route.GET(); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.deepEqual(body, { @@ -103,7 +103,7 @@ test("public login bootstrap route POST rejects invalid JSON bodies", async () = }); const response = await route.POST(request); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 400); assert.equal(body.error.message, "Invalid request"); @@ -118,7 +118,7 @@ test("public login bootstrap route POST rejects empty updates", async () => { }); const response = await route.POST(request); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 400); assert.equal(body.error.message, "Invalid request"); @@ -133,7 +133,7 @@ test("public login bootstrap route POST updates requireLogin without forcing pas }); const response = await route.POST(request); - const body = await response.json(); + const body = (await response.json()) as any; const settings = await settingsDb.getSettings(); assert.equal(response.status, 200); @@ -151,7 +151,7 @@ test("public login bootstrap route POST hashes and stores passwords", async () = }); const response = await route.POST(request); - const body = await response.json(); + const body = (await response.json()) as any; const settings = await settingsDb.getSettings(); assert.equal(response.status, 200); @@ -176,7 +176,7 @@ test("login bootstrap route POST rejects unauthenticated writes after setup is c }); const response = await route.POST(request); - const body = await response.json(); + const body = (await response.json()) as any; const settings = await settingsDb.getSettings(); assert.equal(response.status, 401); @@ -196,7 +196,7 @@ test("public login bootstrap route POST returns 500 when hashing fails", async ( }); const response = await route.POST(request); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 500); assert.deepEqual(body, { error: "hash failed" }); diff --git a/tests/unit/management-password.test.ts b/tests/unit/management-password.test.ts index 38db8a6872..7724888ec5 100644 --- a/tests/unit/management-password.test.ts +++ b/tests/unit/management-password.test.ts @@ -47,7 +47,10 @@ test("ensurePersistentManagementPasswordHash migrates INITIAL_PASSWORD into a pe assert.equal(managementPassword.isBcryptHash(settings.password), true); assert.notEqual(settings.password, "bootstrap-secret"); assert.equal( - await managementPassword.verifyManagementPassword("bootstrap-secret", settings.password), + await managementPassword.verifyManagementPassword( + "bootstrap-secret", + (settings as any).password + ), true ); assert.equal(settings.requireLogin, true); @@ -71,7 +74,10 @@ test("ensurePersistentManagementPasswordHash migrates legacy plaintext settings assert.equal(managementPassword.isBcryptHash(settings.password), true); assert.notEqual(settings.password, "legacy-password"); assert.equal( - await managementPassword.verifyManagementPassword("legacy-password", settings.password), + await managementPassword.verifyManagementPassword( + "legacy-password" as any, + (settings as any).password + ), true ); }); diff --git a/tests/unit/memory-route.test.ts b/tests/unit/memory-route.test.ts index 448318228f..f7dccce349 100644 --- a/tests/unit/memory-route.test.ts +++ b/tests/unit/memory-route.test.ts @@ -65,7 +65,7 @@ test("GET /api/memory filters by q and returns matching stats", async () => { ); assert.equal(response.status, 200); - const body = await response.json(); + const body = (await response.json()) as any; assert.deepEqual( body.data.map((memory) => memory.key), @@ -84,7 +84,7 @@ test("GET /api/memory continues to honor limit+offset requests", async () => { ); assert.equal(response.status, 200); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(body.data.length, 1); assert.equal(body.total, 3); diff --git a/tests/unit/memory-store.test.ts b/tests/unit/memory-store.test.ts index 79d65fcf26..de7009b5be 100644 --- a/tests/unit/memory-store.test.ts +++ b/tests/unit/memory-store.test.ts @@ -20,7 +20,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 { diff --git a/tests/unit/memory-summarization.test.ts b/tests/unit/memory-summarization.test.ts index 22cab54c88..381e1a22bb 100644 --- a/tests/unit/memory-summarization.test.ts +++ b/tests/unit/memory-summarization.test.ts @@ -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 { @@ -53,7 +53,7 @@ function insertMemory({ id, apiKeyId = "key-a", sessionId = "session-a", content function getContent(id) { const db = core.getDbInstance(); - return db.prepare("SELECT content FROM memories WHERE id = ?").get(id)?.content ?? null; + return (db.prepare("SELECT content FROM memories WHERE id = ?").get(id) as any).content ?? null; } test.beforeEach(async () => { diff --git a/tests/unit/messages-count-tokens-route.test.ts b/tests/unit/messages-count-tokens-route.test.ts index 25efb03f28..5bda3cc7b4 100644 --- a/tests/unit/messages-count-tokens-route.test.ts +++ b/tests/unit/messages-count-tokens-route.test.ts @@ -68,7 +68,7 @@ test("messages/count_tokens uses real provider count when Claude-compatible upst ); assert.equal(response.status, 200); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(body.input_tokens, 321); assert.equal(body.source, "provider"); assert.equal(body.provider, "anthropic"); @@ -96,7 +96,7 @@ test("messages/count_tokens falls back to estimate when model is missing", async ); assert.equal(response.status, 200); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(body.input_tokens, 3); assert.equal(body.source, "estimated"); }); @@ -120,7 +120,7 @@ test("messages/count_tokens falls back to estimate when real upstream count fail ); assert.equal(response.status, 200); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(body.input_tokens, 1); assert.equal(body.source, "estimated"); } finally { diff --git a/tests/unit/model-alias-route.test.ts b/tests/unit/model-alias-route.test.ts index b3b512e654..4682bdd25f 100644 --- a/tests/unit/model-alias-route.test.ts +++ b/tests/unit/model-alias-route.test.ts @@ -40,7 +40,7 @@ test("model alias route resolves a stored alias and emits diagnostics headers", headers: { "x-request-id": "req-model-alias-1" }, }) ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.equal(response.headers.get("X-Request-Id"), "req-model-alias-1"); @@ -54,7 +54,7 @@ test("model alias route returns typed ambiguity errors for ambiguous aliases", a const response = await route.GET( await makeManagementSessionRequest("http://localhost/api/models/alias?alias=claude-sonnet-4-6") ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 409); assert.equal(body.error.code, "MODEL_ALIAS_AMBIGUOUS"); @@ -75,8 +75,8 @@ test("model alias route requires a dashboard session when management auth is ena }) ); - 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"); @@ -91,7 +91,7 @@ test("api models catalog route reuses the unified catalog diagnostics headers", headers: { "x-request-id": "req-model-catalog-1" }, }) ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.equal(response.headers.get("X-Request-Id"), "req-model-catalog-1"); @@ -106,7 +106,7 @@ test("v1 models catalog emits diagnostics headers alongside the OpenAI-compatibl headers: { "x-request-id": "req-v1-models-1" }, }) ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.equal(response.headers.get("X-Request-Id"), "req-v1-models-1"); diff --git a/tests/unit/model-sync-route.test.ts b/tests/unit/model-sync-route.test.ts index 829b1c9137..320670e36d 100644 --- a/tests/unit/model-sync-route.test.ts +++ b/tests/unit/model-sync-route.test.ts @@ -79,7 +79,7 @@ test("model sync route skips success log when fetched models do not change store ); assert.equal(response.status, 200); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(body.logged, false); assert.deepEqual(body.modelChanges, { added: 0, removed: 0, updated: 0, total: 0 }); @@ -119,7 +119,7 @@ test("model sync route stores the real provider while keeping the account label" ); assert.equal(response.status, 200); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(body.logged, true); assert.deepEqual(body.modelChanges, { added: 1, removed: 0, updated: 0, total: 1 }); assert.equal(body.provider, "openrouter"); @@ -151,7 +151,7 @@ test("model sync route requires authentication for external requests when auth i }), { params: { id: connection.id } } ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 401); assert.equal(body.error.message, "Authentication required"); @@ -195,7 +195,7 @@ test("model sync route propagates upstream failures and records an error log ent }), { params: { id: connection.id } } ); - const body = await response.json(); + const body = (await response.json()) as any; const logs = await callLogs.getCallLogs({ model: "model-sync", limit: 10 }); assert.equal(response.status, 502); @@ -228,7 +228,7 @@ test("model sync route falls back to the upstream HTTP status when the models pa }), { params: { id: connection.id } } ); - const body = await response.json(); + const body = (await response.json()) as any; const logs = await callLogs.getCallLogs({ model: "model-sync", limit: 10 }); assert.equal(response.status, 429); @@ -268,7 +268,7 @@ test("model sync route preserves previously synced models when the upstream omit }), { params: { id: connection.id } } ); - const body = await response.json(); + const body = (await response.json()) as any; const logs = await callLogs.getCallLogs({ model: "model-sync", limit: 10 }); assert.equal(response.status, 200); @@ -321,7 +321,7 @@ test("model sync route writes synced available models for Gemini connections", a }), { params: { id: connection.id } } ); - const body = await response.json(); + const body = (await response.json()) as any; const synced = await modelsDb.getSyncedAvailableModels("gemini"); const logs = await callLogs.getCallLogs({ model: "model-sync", limit: 10 }); @@ -407,7 +407,7 @@ test("model sync route records added, removed, and updated model diffs with fall }), { params: { id: connection.id } } ); - const body = await response.json(); + const body = (await response.json()) as any; const logs = await callLogs.getCallLogs({ model: "model-sync", limit: 10 }); assert.equal(response.status, 200); @@ -484,7 +484,7 @@ test("model sync route forwards cookies, filters built-ins, and syncs aliases fo }), { params: { id: connection.id } } ); - const body = await response.json(); + const body = (await response.json()) as any; const aliases = await localDb.getModelAliases(); const logs = await callLogs.getCallLogs({ model: "model-sync", limit: 10 }); @@ -549,7 +549,7 @@ test("model sync route uses provider-node prefixes when syncing compatible-provi }), { params: { id: connection.id } } ); - const body = await response.json(); + const body = (await response.json()) as any; const aliases = await localDb.getModelAliases(); assert.equal(response.status, 200); @@ -579,7 +579,7 @@ test("model sync route returns 500 and records a failure when the internal model }), { params: { id: connection.id } } ); - const body = await response.json(); + const body = (await response.json()) as any; const logs = await callLogs.getCallLogs({ model: "model-sync", limit: 10 }); assert.equal(response.status, 500); diff --git a/tests/unit/model-sync-scheduler.test.ts b/tests/unit/model-sync-scheduler.test.ts index 547e7dbf26..ecdadf22d3 100644 --- a/tests/unit/model-sync-scheduler.test.ts +++ b/tests/unit/model-sync-scheduler.test.ts @@ -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 { diff --git a/tests/unit/models-catalog-route.test.ts b/tests/unit/models-catalog-route.test.ts index 1924043f42..e541353786 100644 --- a/tests/unit/models-catalog-route.test.ts +++ b/tests/unit/models-catalog-route.test.ts @@ -56,7 +56,7 @@ test("v1 models catalog requires auth when the route is protected and login is e const response = await v1ModelsCatalog.getUnifiedModelsResponse( new Request("http://localhost/api/v1/models") ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 401); assert.equal(body.error.code, "invalid_api_key"); @@ -87,7 +87,7 @@ test("v1 models catalog accepts bearer API keys and filters the list by allowed headers: { Authorization: `Bearer ${key.key}` }, }) ); - const body = await response.json(); + const body = (await response.json()) as any; const ids = body.data.map((item) => item.id); assert.equal(response.status, 200); @@ -115,13 +115,13 @@ test("v1 models catalog hides models excluded by every active connection while k let response = await v1ModelsCatalog.getUnifiedModelsResponse( new Request("http://localhost/api/v1/models") ); - let body = await response.json(); + let body = (await response.json()) as any; let ids = new Set(body.data.map((item) => item.id)); assert.equal(response.status, 200); assert.equal(ids.has("openai/gpt-4o-mini"), true); - await providersDb.updateProviderConnection(second.id, { + await providersDb.updateProviderConnection((second as any).id, { providerSpecificData: { excludedModels: ["gpt-4o*"], }, @@ -130,13 +130,13 @@ test("v1 models catalog hides models excluded by every active connection while k response = await v1ModelsCatalog.getUnifiedModelsResponse( new Request("http://localhost/api/v1/models") ); - body = await response.json(); + body = (await response.json()) as any; ids = new Set(body.data.map((item) => item.id)); assert.equal(response.status, 200); assert.equal(ids.has("openai/gpt-4o-mini"), false); - await providersDb.updateProviderConnection(first.id, { + await providersDb.updateProviderConnection((first as any).id, { providerSpecificData: { excludedModels: [], }, @@ -172,7 +172,7 @@ test("v1 models catalog includes combos and custom models while excluding hidden const response = await v1ModelsCatalog.getUnifiedModelsResponse( new Request("http://localhost/api/v1/models") ); - const body = await response.json(); + const body = (await response.json()) as any; const ids = new Set(body.data.map((item) => item.id)); assert.equal(response.status, 200); @@ -181,7 +181,7 @@ test("v1 models catalog includes combos and custom models while excluding hidden assert.ok(ids.has("kiro/custom-kiro")); assert.equal(ids.has("openai/gpt-4o-mini"), false); assert.equal( - [...ids].some((id) => id.startsWith("claude/") || id.startsWith("cc/")), + [...ids].some((id) => (id as any).startsWith("claude/") || (id as any).startsWith("cc/")), false ); }); @@ -192,7 +192,7 @@ test("v1 models catalog keeps only visible combos when no providers are active", strategy: "priority", models: ["openai/gpt-4o"], }); - await combosDb.updateCombo(visible.id, { context_length: 32000 }); + await combosDb.updateCombo((visible as any).id, { context_length: 32000 }); const hidden = await combosDb.createCombo({ name: "hidden-combo", strategy: "priority", @@ -204,12 +204,12 @@ test("v1 models catalog keeps only visible combos when no providers are active", strategy: "priority", models: ["openai/gpt-4o"], }); - await combosDb.updateCombo(inactive.id, { isActive: false }); + await combosDb.updateCombo((inactive as any).id, { isActive: false }); const response = await v1ModelsCatalog.getUnifiedModelsResponse( new Request("http://localhost/api/v1/models") ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.deepEqual( @@ -238,7 +238,7 @@ test("v1 models catalog exposes claude alias and provider-prefixed built-in mode const response = await v1ModelsCatalog.getUnifiedModelsResponse( new Request("http://localhost/api/v1/models") ); - const body = await response.json(); + const body = (await response.json()) as any; const aliasModel = body.data.find((item) => item.id === "cc/claude-sonnet-4-6"); const providerModel = body.data.find((item) => item.id === "claude/claude-sonnet-4-6"); @@ -262,7 +262,7 @@ test("v1 models catalog exposes Antigravity client-visible preview aliases inste const response = await v1ModelsCatalog.getUnifiedModelsResponse( new Request("http://localhost/api/v1/models") ); - const body = await response.json(); + const body = (await response.json()) as any; const ids = new Set(body.data.map((item) => item.id)); assert.equal(response.status, 200); @@ -294,7 +294,7 @@ test("v1 models catalog uses provider-node prefixes for compatible provider cust const response = await v1ModelsCatalog.getUnifiedModelsResponse( new Request("http://localhost/api/v1/models") ); - const body = await response.json(); + const body = (await response.json()) as any; const ids = new Set(body.data.map((item) => item.id)); assert.equal(response.status, 200); @@ -308,34 +308,38 @@ test("v1 models catalog includes synced Gemini models and duplicates audio model apiKey: "gm-key", }); - await modelsDb.replaceSyncedAvailableModelsForConnection("gemini", connection.id, [ - { - id: "gemini-audio-live", - name: "Gemini Audio Live", - source: "api-sync", - supportedEndpoints: ["audio"], - inputTokenLimit: 4096, - }, - { - id: "text-embedding-004", - name: "Text Embedding 004", - source: "api-sync", - supportedEndpoints: ["embeddings"], - inputTokenLimit: 2048, - }, - { - id: "gemini-hidden", - name: "Gemini Hidden", - source: "api-sync", - supportedEndpoints: ["chat"], - }, - ]); + await modelsDb.replaceSyncedAvailableModelsForConnection( + "gemini" as any, + (connection as any).id, + [ + { + id: "gemini-audio-live", + name: "Gemini Audio Live", + source: "api-sync", + supportedEndpoints: ["audio"], + inputTokenLimit: 4096, + }, + { + id: "text-embedding-004", + name: "Text Embedding 004", + source: "api-sync", + supportedEndpoints: ["embeddings"], + inputTokenLimit: 2048, + }, + { + id: "gemini-hidden", + name: "Gemini Hidden", + source: "api-sync", + supportedEndpoints: ["chat"], + }, + ] + ); modelsDb.mergeModelCompatOverride("gemini", "gemini-hidden", { isHidden: true }); const response = await v1ModelsCatalog.getUnifiedModelsResponse( new Request("http://localhost/api/v1/models") ); - const body = await response.json(); + const body = (await response.json()) as any; const audioVariants = body.data.filter((item) => item.id === "gemini/gemini-audio-live"); const embedding = body.data.find((item) => item.id === "gemini/text-embedding-004"); @@ -355,7 +359,7 @@ test("v1 models catalog keeps Gemini chat models untyped when synced endpoints a apiKey: "gm-chat-key", }); - await modelsDb.replaceSyncedAvailableModelsForConnection("gemini", connection.id, [ + await modelsDb.replaceSyncedAvailableModelsForConnection("gemini", (connection as any).id, [ { id: "gemini-2.5-pro-live", name: "Gemini 2.5 Pro Live", @@ -367,7 +371,7 @@ test("v1 models catalog keeps Gemini chat models untyped when synced endpoints a const response = await v1ModelsCatalog.getUnifiedModelsResponse( new Request("http://localhost/api/v1/models") ); - const body = await response.json(); + const body = (await response.json()) as any; const chatModel = body.data.find((item) => item.id === "gemini/gemini-2.5-pro-live"); assert.equal(response.status, 200); @@ -389,17 +393,17 @@ test("v1 models catalog includes media, moderation, rerank, video, and music mod const response = await v1ModelsCatalog.getUnifiedModelsResponse( new Request("http://localhost/api/v1/models") ); - const body = await response.json(); + const body = (await response.json()) as any; const byId = new Map(body.data.map((item) => [item.id, item])); assert.equal(response.status, 200); - assert.equal(byId.get("openai/gpt-image-1")?.type, "image"); - assert.equal(byId.get("openai/whisper-1")?.type, "audio"); - assert.equal(byId.get("openai/whisper-1")?.subtype, "transcription"); - assert.equal(byId.get("openai/omni-moderation-latest")?.type, "moderation"); - assert.equal(byId.get("cohere/rerank-v3.5")?.type, "rerank"); - assert.equal(byId.get("comfyui/animatediff")?.type, "video"); - assert.equal(byId.get("comfyui/stable-audio-open")?.type, "music"); + assert.equal((byId.get("openai/gpt-image-1") as any).type, "image"); + assert.equal((byId.get("openai/whisper-1") as any).type, "audio"); + assert.equal((byId.get("openai/whisper-1") as any).subtype, "transcription"); + assert.equal((byId.get("openai/omni-moderation-latest") as any).type, "moderation"); + assert.equal((byId.get("cohere/rerank-v3.5") as any).type, "rerank"); + assert.equal((byId.get("comfyui/animatediff") as any).type, "video"); + assert.equal((byId.get("comfyui/stable-audio-open") as any).type, "music"); }); test("v1 models catalog exposes image model input and output modalities for advanced image providers", async () => { @@ -409,16 +413,16 @@ test("v1 models catalog exposes image model input and output modalities for adva const response = await v1ModelsCatalog.getUnifiedModelsResponse( new Request("http://localhost/api/v1/models") ); - const body = await response.json(); + const body = (await response.json()) as any; const byId = new Map(body.data.map((item) => [item.id, item])); assert.equal(response.status, 200); - assert.deepEqual(byId.get("flux-redux")?.input_modalities, ["text", "image"]); - assert.deepEqual(byId.get("flux-redux")?.output_modalities, ["image"]); - assert.equal(byId.get("flux-redux")?.type, "image"); - assert.ok(byId.get("flux-redux")?.supported_sizes?.includes("1024x1024")); - assert.deepEqual(byId.get("topaz/topaz-enhance")?.input_modalities, ["image"]); - assert.deepEqual(byId.get("topaz/topaz-enhance")?.output_modalities, ["image"]); + assert.deepEqual((byId as any).get("flux-redux")?.input_modalities, ["text", "image"]); + (assert as any).deepEqual((byId.get("flux-redux") as any).output_modalities, ["image"]); + (assert as any).equal((byId.get("flux-redux") as any).type, "image"); + assert.ok((byId.get("flux-redux") as any).supported_sizes?.includes("1024x1024")); + (assert as any).deepEqual((byId.get("topaz/topaz-enhance") as any).input_modalities, ["image"]); + assert.deepEqual((byId.get("topaz/topaz-enhance") as any).output_modalities, ["image"]); }); test("v1 models catalog tolerates custom model lookup failures and keeps builtin models available", async () => { @@ -443,7 +447,7 @@ test("v1 models catalog tolerates custom model lookup failures and keeps builtin const response = await v1ModelsCatalog.getUnifiedModelsResponse( new Request("http://localhost/api/v1/models") ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.ok(body.data.some((item) => item.id === "openai/gpt-4o")); @@ -504,7 +508,7 @@ test("v1 models catalog exposes provider-prefixed custom models, filters by raw headers: { Authorization: `Bearer ${key.key}` }, }) ); - const body = await response.json(); + const body = (await response.json()) as any; const ids = new Set(body.data.map((item) => item.id)); const shortAlias = body.data.find((item) => item.id === "cl/demo-custom"); const providerAlias = body.data.find((item) => item.id === "cline/demo-custom"); @@ -557,7 +561,7 @@ test("v1 models catalog returns 500 when model compatibility lookup crashes", as const response = await v1ModelsCatalog.getUnifiedModelsResponse( new Request("http://localhost/api/v1/models") ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 500); assert.equal(body.error.type, "server_error"); @@ -585,7 +589,7 @@ test("v1 models catalog skips duplicate built-ins and custom models from inactiv const response = await v1ModelsCatalog.getUnifiedModelsResponse( new Request("http://localhost/api/v1/models") ); - const body = await response.json(); + const body = (await response.json()) as any; const duplicateBuiltins = body.data.filter((item) => item.id === "openai/gpt-4o"); assert.equal(response.status, 200); @@ -622,7 +626,7 @@ test("v1 models catalog adds managed fallback models for Claude-compatible provi const response = await v1ModelsCatalog.getUnifiedModelsResponse( new Request("http://localhost/api/v1/models") ); - const body = await response.json(); + const body = (await response.json()) as any; const ids = new Set(body.data.map((item) => item.id)); assert.equal(response.status, 200); diff --git a/tests/unit/modelsDevSync-extended.test.ts b/tests/unit/modelsDevSync-extended.test.ts index aac461f652..b422aac182 100644 --- a/tests/unit/modelsDevSync-extended.test.ts +++ b/tests/unit/modelsDevSync-extended.test.ts @@ -130,8 +130,8 @@ test.beforeEach(async () => { test.afterEach(async () => { for (const mod of loadedModules) { - if (typeof mod.stopPeriodicSync === "function") { - mod.stopPeriodicSync(); + if (typeof (mod as any).stopPeriodicSync === "function") { + (mod as any).stopPeriodicSync(); } } loadedModules.clear(); diff --git a/tests/unit/moderations-handler.test.ts b/tests/unit/moderations-handler.test.ts index 54fcb2bc5f..f3b5378b7e 100644 --- a/tests/unit/moderations-handler.test.ts +++ b/tests/unit/moderations-handler.test.ts @@ -14,7 +14,7 @@ test("handleModeration requires input", async () => { body: { model: "openai/omni-moderation-latest" }, credentials: { apiKey: "sk-test" }, }); - 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"); @@ -25,7 +25,7 @@ test("handleModeration rejects unknown moderation models", async () => { body: { model: "mystery/moderation", input: "hello" }, credentials: { apiKey: "sk-test" }, }); - const payload = await response.json(); + const payload = (await response.json()) as any; assert.equal(response.status, 400); assert.match(payload.error.message, /No moderation provider found/); @@ -36,7 +36,7 @@ test("handleModeration requires credentials for the resolved provider", async () body: { input: "hello" }, 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 moderation provider: openai"); @@ -104,7 +104,7 @@ test("handleModeration returns a 500 when the upstream request throws", async () body: { model: "openai/text-moderation-latest", input: "check this" }, credentials: { apiKey: "sk-test" }, }); - const payload = await response.json(); + const payload = (await response.json()) as any; assert.equal(response.status, 500); assert.match(payload.error.message, /Moderation request failed: socket closed/); diff --git a/tests/unit/nanobanana-image-generation.test.ts b/tests/unit/nanobanana-image-generation.test.ts index d2d10d72a6..fc01c1ebc5 100644 --- a/tests/unit/nanobanana-image-generation.test.ts +++ b/tests/unit/nanobanana-image-generation.test.ts @@ -85,7 +85,7 @@ test("nanobanana async flow (submit->poll->url) normalizes to OpenAI-style url i headers: { "Content-Type": "application/json", Authorization: "Bearer test" }, body: JSON.stringify({ prompt: "space" }), }); - const submitData = await submit.json(); + const submitData = (await submit.json()) as any; const taskId = submitData.data.taskId; let finalData; @@ -93,7 +93,7 @@ test("nanobanana async flow (submit->poll->url) normalizes to OpenAI-style url i const poll = await fetch( `https://api.nanobananaapi.ai/api/v1/nanobanana/record-info?taskId=${encodeURIComponent(taskId)}` ); - const pollData = await poll.json(); + const pollData = (await poll.json()) as any; if (pollData.data.successFlag === 1) { finalData = pollData.data; break; diff --git a/tests/unit/observability-fase04.test.ts b/tests/unit/observability-fase04.test.ts index 5a52b5b1eb..1d86b11b34 100644 --- a/tests/unit/observability-fase04.test.ts +++ b/tests/unit/observability-fase04.test.ts @@ -123,7 +123,7 @@ test("requestTimeout: withTimeout resolves before timeout", async () => { test("requestTimeout: withTimeout rejects on timeout", async () => { await assert.rejects( () => withTimeout(() => new Promise((r) => setTimeout(r, 500)), 10, "slow-op"), - (err) => err.name === "TimeoutError" + (err) => (err as any).name === "TimeoutError" ); }); diff --git a/tests/unit/openapi-spec-route.test.ts b/tests/unit/openapi-spec-route.test.ts index c70979470b..5b133e6aa8 100644 --- a/tests/unit/openapi-spec-route.test.ts +++ b/tests/unit/openapi-spec-route.test.ts @@ -7,7 +7,7 @@ test("openapi spec route resolves the repository spec file and returns a parsed const response = await GET(); assert.equal(response.status, 200); - const payload = await response.json(); + const payload = (await response.json()) as any; assert.equal(typeof payload.info, "object"); assert.ok(Array.isArray(payload.endpoints)); assert.ok(Array.isArray(payload.schemas)); diff --git a/tests/unit/orphaned-tool-filter.test.ts b/tests/unit/orphaned-tool-filter.test.ts index fb91017f4f..626f6807f4 100644 --- a/tests/unit/orphaned-tool-filter.test.ts +++ b/tests/unit/orphaned-tool-filter.test.ts @@ -18,7 +18,7 @@ test("openaiResponsesToOpenAIRequest: filters orphaned tool messages", () => { ], }; const result = openaiResponsesToOpenAIRequest("gpt-4", body, true, null); - const toolMessages = result.messages.filter((m) => m.role === "tool"); + const toolMessages = (result as any).messages.filter((m) => m.role === "tool"); assert.equal(toolMessages.length, 1, "should have exactly 1 tool message"); assert.equal(toolMessages[0].tool_call_id, "call_valid_1"); }); @@ -35,7 +35,7 @@ test("openaiResponsesToOpenAIRequest: preserves all messages when no orphans", ( ], }; const result = openaiResponsesToOpenAIRequest("gpt-4", body, true, null); - const toolMessages = result.messages.filter((m) => m.role === "tool"); + const toolMessages = (result as any).messages.filter((m) => m.role === "tool"); assert.equal(toolMessages.length, 2, "both valid tool results should be preserved"); }); @@ -56,7 +56,7 @@ test("openaiToOpenAIResponsesRequest: filters orphaned function_call_output", () ], }; const result = openaiToOpenAIResponsesRequest("gpt-4", body, true, null); - const outputs = result.input.filter((i) => i.type === "function_call_output"); + const outputs = (result as any).input.filter((i) => i.type === "function_call_output"); assert.equal(outputs.length, 1, "should have exactly 1 function_call_output"); assert.equal(outputs[0].call_id, "call_valid_2"); }); @@ -74,7 +74,7 @@ test("openaiToOpenAIResponsesRequest: preserves all items when no orphans", () = ], }; const result = openaiToOpenAIResponsesRequest("gpt-4", body, true, null); - const outputs = result.input.filter((i) => i.type === "function_call_output"); + const outputs = (result as any).input.filter((i) => i.type === "function_call_output"); assert.equal(outputs.length, 1, "valid function_call_output should be preserved"); }); diff --git a/tests/unit/payload-rules-route.test.ts b/tests/unit/payload-rules-route.test.ts index a846c1e441..9db6cf0117 100644 --- a/tests/unit/payload-rules-route.test.ts +++ b/tests/unit/payload-rules-route.test.ts @@ -59,7 +59,7 @@ test.after(async () => { test("payload rules route returns the neutral config by default", async () => { const response = await route.GET(new Request("http://localhost/api/settings/payload-rules")); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.deepEqual(body, { @@ -85,9 +85,9 @@ test("payload rules route requires a dashboard session when management auth is e await makeManagementSessionRequest("http://localhost/api/settings/payload-rules") ); - const unauthenticatedBody = await unauthenticated.json(); - const invalidTokenBody = await invalidToken.json(); - const authenticatedBody = await authenticated.json(); + const unauthenticatedBody = (await unauthenticated.json()) as any; + const invalidTokenBody = (await invalidToken.json()) as any; + const authenticatedBody = (await authenticated.json()) as any; assert.equal(unauthenticated.status, 401); assert.equal(unauthenticatedBody.error.message, "Authentication required"); @@ -140,7 +140,7 @@ test("payload rules route persists normalized config and hot reloads the runtime body: requestBody, }) ); - const body = await response.json(); + const body = (await response.json()) as any; const settings = await settingsDb.getSettings(); const runtimeConfig = await payloadRulesService.getPayloadRulesConfig(); @@ -172,8 +172,8 @@ test("payload rules route rejects malformed and schema-invalid payloads", async }) ); - const invalidJsonBody = await invalidJson.json(); - const invalidSchemaBody = await invalidSchema.json(); + const invalidJsonBody = (await invalidJson.json()) as any; + const invalidSchemaBody = (await invalidSchema.json()) as any; const runtimeConfig = await payloadRulesService.getPayloadRulesConfig(); assert.equal(invalidJson.status, 400); diff --git a/tests/unit/payload-rules.test.ts b/tests/unit/payload-rules.test.ts index 29ccc032b9..1e059d4402 100644 --- a/tests/unit/payload-rules.test.ts +++ b/tests/unit/payload-rules.test.ts @@ -71,10 +71,10 @@ test("payload rules apply default, default-raw, override, and filter operations" ); assert.equal(payload.temperature, 0.4); - assert.equal(payload.metadata.routeTag, "feature-110"); + assert.equal((payload.metadata as any).routeTag, "feature-110"); assert.equal("removeMe" in payload.metadata, false); assert.equal("dangerous" in payload, false); - assert.equal(payload.reasoning.effort, "high"); + assert.equal((payload as any).reasoning.effort, "high"); assert.deepEqual(payload.response_format, { type: "json_schema", json_schema: { diff --git a/tests/unit/perplexity-web.test.ts b/tests/unit/perplexity-web.test.ts index 851e6b5a17..ea10768ce6 100644 --- a/tests/unit/perplexity-web.test.ts +++ b/tests/unit/perplexity-web.test.ts @@ -104,7 +104,7 @@ test("Non-streaming: simple text response", async () => { }); assert.equal(result.response.status, 200); - const json = await result.response.json(); + const json = (await result.response.json()) as any; assert.equal(json.object, "chat.completion"); assert.equal(json.choices[0].message.role, "assistant"); assert.equal(json.choices[0].message.content, "Hello, world!"); @@ -144,7 +144,7 @@ test("Non-streaming: strips citations from response", async () => { log: null, }); - const json = await result.response.json(); + const json = (await result.response.json()) as any; assert.ok(!json.choices[0].message.content.includes("[1]")); assert.ok(!json.choices[0].message.content.includes("[2]")); assert.ok(!json.choices[0].message.content.includes("[3]")); @@ -292,7 +292,7 @@ test("Error: 401 returns auth error message", async () => { }); assert.equal(result.response.status, 401); - const json = await result.response.json(); + const json = (await result.response.json()) as any; assert.ok(json.error.message.includes("auth failed")); assert.ok(json.error.message.includes("session-token")); } finally { @@ -314,7 +314,7 @@ test("Error: 429 returns rate limit message", async () => { }); assert.equal(result.response.status, 429); - const json = await result.response.json(); + const json = (await result.response.json()) as any; assert.ok(json.error.message.includes("rate limited")); } finally { restore(); @@ -335,7 +335,7 @@ test("Error: fetch failure returns 502", async () => { }); assert.equal(result.response.status, 502); - const json = await result.response.json(); + const json = (await result.response.json()) as any; assert.ok(json.error.message.includes("ECONNREFUSED")); } finally { restore(); @@ -354,7 +354,7 @@ test("Error: empty messages returns 400", async () => { }); assert.equal(result.response.status, 400); - const json = await result.response.json(); + const json = (await result.response.json()) as any; assert.ok(json.error.message.includes("Missing or empty messages")); }); @@ -390,7 +390,7 @@ test("Non-streaming: Perplexity stream error returns 502", async () => { }); assert.equal(result.response.status, 502); - const json = await result.response.json(); + const json = (await result.response.json()) as any; assert.ok(json.error.message.includes("Too many requests")); } finally { restore(); @@ -700,7 +700,7 @@ test("Non-streaming: falls back to text field when no blocks", async () => { log: null, }); - const json = await result.response.json(); + const json = (await result.response.json()) as any; assert.ok(json.choices[0].message.content.includes("Fallback answer text")); } finally { restore(); diff --git a/tests/unit/plan3-p0.test.ts b/tests/unit/plan3-p0.test.ts index 133800e738..a8faa12570 100644 --- a/tests/unit/plan3-p0.test.ts +++ b/tests/unit/plan3-p0.test.ts @@ -346,15 +346,18 @@ test("translateNonStreamingResponse converts Responses API payload to OpenAI cha FORMATS.OPENAI ); - assert.equal(translated.object, "chat.completion"); - assert.equal(translated.model, "gpt-5.1-codex"); - assert.equal(translated.choices[0].message.role, "assistant"); - assert.equal(translated.choices[0].message.content, "Hello from responses API."); - assert.equal(translated.choices[0].finish_reason, "tool_calls"); - assert.equal(translated.choices[0].message.tool_calls.length, 1); - assert.equal(translated.usage.prompt_tokens, 11); - assert.equal(translated.usage.completion_tokens, 7); - assert.equal(translated.usage.total_tokens, 18); + assert.equal((translated as any).object, "chat.completion"); + assert.equal((translated as any).model, "gpt-5.1-codex"); + (assert as any).equal((translated as any).choices[0].message.role, "assistant"); + (assert as any).equal( + (translated as any).choices[0].message.content, + "Hello from responses API." + ); + assert.equal((translated as any).choices[0].finish_reason, "tool_calls"); + assert.equal(((translated as any).choices[0].message.tool_calls as any).length, 1); + assert.equal(((translated as any).usage as any).prompt_tokens, 11); + assert.equal((translated as any).usage.completion_tokens, 7); + assert.equal((translated as any).usage.total_tokens, 18); }); test("extractUsageFromResponse reads usage from Responses API payload", () => { diff --git a/tests/unit/prompt-injection-guard.test.ts b/tests/unit/prompt-injection-guard.test.ts index b0975950f5..7d177f6ea2 100644 --- a/tests/unit/prompt-injection-guard.test.ts +++ b/tests/unit/prompt-injection-guard.test.ts @@ -205,7 +205,7 @@ test("promptInjectionGuard: withInjectionGuard blocks suspicious POST bodies", a }); const response = await wrapped(request, {}); - const payload = await response.json(); + const payload = (await response.json()) as any; assert.equal(response.status, 400); assert.equal(payload.error.type, "injection_detected"); @@ -235,7 +235,7 @@ test("promptInjectionGuard: withInjectionGuard annotates downstream headers in w }); const response = await wrapped(request, {}); - const payload = await response.json(); + const payload = (await response.json()) as any; assert.equal(response.status, 200); assert.equal(payload.flagged, "true"); diff --git a/tests/unit/prompt-required-routes.test.ts b/tests/unit/prompt-required-routes.test.ts index cd5e5c979e..5bed27f11c 100644 --- a/tests/unit/prompt-required-routes.test.ts +++ b/tests/unit/prompt-required-routes.test.ts @@ -26,7 +26,7 @@ test("v1 video generation POST rejects requests without a prompt", async () => { }), }) ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 400); assert.match(body.error.message, /Prompt is required/); @@ -42,7 +42,7 @@ test("v1 music generation POST rejects requests without a prompt", async () => { }), }) ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 400); assert.match(body.error.message, /Prompt is required/); diff --git a/tests/unit/provider-models-management-route.test.ts b/tests/unit/provider-models-management-route.test.ts index 6926950f5d..cf8de63629 100644 --- a/tests/unit/provider-models-management-route.test.ts +++ b/tests/unit/provider-models-management-route.test.ts @@ -46,7 +46,7 @@ test("provider-models PATCH updates hidden flag for custom models", async () => isHidden: true, }) ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.equal(body.ok, true); @@ -62,7 +62,7 @@ test("provider-models PATCH persists visibility overrides for catalog models", a { isHidden: true } ) ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.equal(body.ok, true); @@ -85,7 +85,7 @@ test("provider-models PATCH supports bulk visibility updates", async () => { modelIds: ["claude-opus-4-6", "claude-sonnet-4-6"], }) ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.equal(body.updated, 2); @@ -101,7 +101,7 @@ test("provider-models PATCH validates required fields", async () => { modelIds: ["claude-sonnet-4-6"], }) ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 400); assert.equal(body.error.message, "isHidden boolean is required"); diff --git a/tests/unit/provider-models-route.test.ts b/tests/unit/provider-models-route.test.ts index 224c1524a6..379cf02c6a 100644 --- a/tests/unit/provider-models-route.test.ts +++ b/tests/unit/provider-models-route.test.ts @@ -155,7 +155,7 @@ test("provider models route falls back after OpenAI-compatible endpoint probes a }; const response = await callRoute(connection.id); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.equal(body.provider, "openai-compatible-fallback"); @@ -184,7 +184,7 @@ test("provider models route retries transient OpenAI-compatible probe failures b }; const response = await callRoute(connection.id); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.equal(body.source, "api"); @@ -201,7 +201,7 @@ test("provider models route returns static catalog entries for providers with ha }); const response = await callRoute(connection.id); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.equal(body.provider, "bailian-coding-plan"); @@ -214,7 +214,7 @@ test("provider models route returns the local catalog for built-in image provide }); const response = await callRoute(connection.id); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.equal(body.provider, "topaz"); @@ -228,7 +228,7 @@ test("provider models route returns the local catalog for new built-in chat-open }); const response = await callRoute(connection.id); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.equal(body.provider, "deepinfra"); @@ -278,7 +278,7 @@ test("provider models route maps Gemini CLI quota buckets into a model list", as }; const response = await callRoute(connection.id); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.deepEqual(body.models, [ @@ -311,7 +311,7 @@ test("provider models route retries Antigravity discovery endpoints before retur }; const response = await callRoute(connection.id); - const body = await response.json(); + const body = (await response.json()) as any; const discoveryUrls = seenUrls.filter((url) => url.includes("/v1internal:models")); assert.equal(response.status, 200); @@ -337,7 +337,7 @@ test("provider models route falls back through all Antigravity discovery endpoin }; const response = await callRoute(connection.id); - const body = await response.json(); + const body = (await response.json()) as any; const discoveryUrls = seenUrls.filter((url) => url.includes("/v1internal:models")); assert.equal(response.status, 200); @@ -359,7 +359,7 @@ test("provider models route returns the local catalog for OAuth-backed Qwen conn }); const response = await callRoute(connection.id); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.equal(body.source, "local_catalog"); @@ -375,7 +375,7 @@ test("provider models route filters hidden models from the static Claude catalog modelsDb.mergeModelCompatOverride("claude", "claude-sonnet-4-6", { isHidden: true }); const response = await callRoute(connection.id, "?excludeHidden=true"); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.equal(body.provider, "claude"); @@ -429,7 +429,7 @@ test("provider models route trims Anthropic-compatible message URLs and filters }; const response = await callRoute(connection.id, "?excludeHidden=true"); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.deepEqual(body.models, [{ id: "visible-model", name: "Visible Model" }]); @@ -496,7 +496,7 @@ test("provider models route paginates generic providers and filters hidden model }; const response = await callRoute(connection.id, "?excludeHidden=true"); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.deepEqual(body.models.map((model) => model.id).sort(), [ @@ -527,7 +527,7 @@ test("provider models route stops pagination when the upstream repeats the next }; const response = await callRoute(connection.id); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.deepEqual( @@ -611,7 +611,7 @@ test("provider models route uses provider-specific auth headers for Kimi Coding" }; const response = await callRoute(connection.id); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.equal(body.provider, "kimi-coding"); diff --git a/tests/unit/provider-nodes-route.test.ts b/tests/unit/provider-nodes-route.test.ts index 06d3599104..e09fddc420 100644 --- a/tests/unit/provider-nodes-route.test.ts +++ b/tests/unit/provider-nodes-route.test.ts @@ -49,7 +49,7 @@ test("provider nodes route lists stored nodes and exposes the CC feature flag", process.env.ENABLE_CC_COMPATIBLE_PROVIDER = "true"; const response = await providerNodesRoute.GET(); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.equal(body.ccCompatibleProviderEnabled, true); @@ -72,8 +72,8 @@ test("provider nodes route rejects malformed JSON and schema validation failures }) ); - const malformedBody = await malformed.json(); - const invalidBody = await invalid.json(); + const malformedBody = (await malformed.json()) as any; + const invalidBody = (await invalid.json()) as any; assert.equal(malformed.status, 400); assert.equal(malformedBody.error.message, "Invalid request"); @@ -98,7 +98,7 @@ test("provider nodes route creates OpenAI-compatible nodes with normalized defau modelsPath: "", }) ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 201); assert.match(body.node.id, new RegExp(`^${OPENAI_COMPATIBLE_PREFIX}chat-`)); @@ -120,7 +120,7 @@ test("provider nodes route creates Anthropics-compatible nodes and sanitizes mes modelsPath: "/models", }) ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 201); assert.match(body.node.id, new RegExp(`^${ANTHROPIC_COMPATIBLE_PREFIX}`)); @@ -141,7 +141,7 @@ test("provider nodes route blocks CC-compatible nodes when the feature flag is d baseUrl: "https://cc.example.com/v1/messages?beta=1", }) ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 403); assert.equal(body.error, "CC Compatible provider is disabled"); @@ -161,7 +161,7 @@ test("provider nodes route creates CC-compatible nodes with CC-specific URL norm modelsPath: "/models", }) ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 201); assert.match(body.node.id, new RegExp(`^${CLAUDE_CODE_COMPATIBLE_PREFIX}`)); diff --git a/tests/unit/providers-route-managed-catalog.test.ts b/tests/unit/providers-route-managed-catalog.test.ts index 7268e537a8..24f9c8e131 100644 --- a/tests/unit/providers-route-managed-catalog.test.ts +++ b/tests/unit/providers-route-managed-catalog.test.ts @@ -90,7 +90,7 @@ test("providers route accepts managed audio, web-cookie and search providers", a 201, `${entry.provider} should be accepted by POST /api/providers` ); - const payload = await response.json(); + const payload = (await response.json()) as any; assert.equal(payload.connection.provider, entry.provider); } }); diff --git a/tests/unit/providers-validate-route.test.ts b/tests/unit/providers-validate-route.test.ts index 0cd9fc3973..37bfa706c1 100644 --- a/tests/unit/providers-validate-route.test.ts +++ b/tests/unit/providers-validate-route.test.ts @@ -40,7 +40,7 @@ test("providers validate route returns 400 for invalid JSON", async () => { const response = await validateRoute.POST(request); assert.equal(response.status, 400); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(body.error.message, "Invalid request"); }); @@ -96,7 +96,7 @@ test("providers validate route forwards baseUrl to built-in specialty validators }); const response = await validateRoute.POST(request); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.equal(body.valid, true); @@ -175,7 +175,7 @@ test("providers validate route allows private baseUrl values when opt-in env is }); const response = await validateRoute.POST(request); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.equal(body.valid, true); @@ -212,7 +212,7 @@ test("providers validate route returns 504 on controlled outbound timeout", asyn }); const response = await validateRoute.POST(request); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 504); assert.match(body.error, /timed out/i); diff --git a/tests/unit/proxy-fetch.test.ts b/tests/unit/proxy-fetch.test.ts index 19e1f49ce0..5b175f76ea 100644 --- a/tests/unit/proxy-fetch.test.ts +++ b/tests/unit/proxy-fetch.test.ts @@ -136,7 +136,7 @@ test("proxy fetch uses TLS fingerprint transport when enabled and available", as assert.equal(isTlsFingerprintActive(), true); assert.equal(tracked.tlsFingerprintUsed, true); - assert.deepEqual(await tracked.result.json(), { via: "tls-client" }); + assert.deepEqual(await (tracked.result as any).json(), { via: "tls-client" }); } ); }); diff --git a/tests/unit/proxy-login-bootstrap-auth.test.ts b/tests/unit/proxy-login-bootstrap-auth.test.ts index b43c403632..d637679d68 100644 --- a/tests/unit/proxy-login-bootstrap-auth.test.ts +++ b/tests/unit/proxy-login-bootstrap-auth.test.ts @@ -76,7 +76,7 @@ test("proxy requires auth for POST /api/settings/require-login after setup", asy const { proxy } = await importFreshProxy(); const response = await proxy(makeRequest("/api/settings/require-login", "POST")); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 401); assert.equal(body.error.code, "AUTH_001"); @@ -95,7 +95,7 @@ test("proxy rejects bearer tokens for POST /api/settings/require-login after set authorization: "Bearer sk-invalid", }) ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 403); assert.equal(body.error.code, "AUTH_001"); diff --git a/tests/unit/proxy-management-v1-route.test.ts b/tests/unit/proxy-management-v1-route.test.ts index e02f2e267c..2c3a1deec2 100644 --- a/tests/unit/proxy-management-v1-route.test.ts +++ b/tests/unit/proxy-management-v1-route.test.ts @@ -103,7 +103,7 @@ test("v1 management proxies supports create/list/pagination", async () => { new Request("http://localhost/api/v1/management/proxies?limit=1&offset=0") ); assert.equal(listRes.status, 200); - const listPayload = await listRes.json(); + const listPayload = (await listRes.json()) as any; assert.equal(Array.isArray(listPayload.items), true); assert.equal(listPayload.items.length, 1); assert.equal(listPayload.page.total >= 2, true); @@ -172,13 +172,13 @@ test("v1 management proxies main route covers auth, lookup variants, update and }) ); assert.equal(createdRes.status, 201); - const created = await createdRes.json(); + const created = (await createdRes.json()) as any; const defaultListRes = await proxyV1Route.GET( new Request("http://localhost/api/v1/management/proxies") ); assert.equal(defaultListRes.status, 200); - const defaultListBody = await defaultListRes.json(); + const defaultListBody = (await defaultListRes.json()) as any; assert.equal(defaultListBody.page.limit, 50); assert.equal(defaultListBody.page.offset, 0); assert.equal(defaultListBody.items.length, 1); @@ -187,7 +187,7 @@ test("v1 management proxies main route covers auth, lookup variants, update and new Request(`http://localhost/api/v1/management/proxies?id=${created.id}`) ); assert.equal(byIdRes.status, 200); - const byIdBody = await byIdRes.json(); + const byIdBody = (await byIdRes.json()) as any; assert.equal(byIdBody.id, created.id); const assignRes = await proxyAssignmentsV1Route.PUT( @@ -207,7 +207,7 @@ test("v1 management proxies main route covers auth, lookup variants, update and new Request(`http://localhost/api/v1/management/proxies?id=${created.id}&where_used=1`) ); assert.equal(whereUsedRes.status, 200); - const whereUsedBody = await whereUsedRes.json(); + const whereUsedBody = (await whereUsedRes.json()) as any; assert.equal(whereUsedBody.count, 1); assert.equal(whereUsedBody.assignments[0].scopeId, providerConn.id); @@ -229,7 +229,7 @@ test("v1 management proxies main route covers auth, lookup variants, update and }) ); assert.equal(updatedRes.status, 200); - const updatedBody = await updatedRes.json(); + const updatedBody = (await updatedRes.json()) as any; assert.equal(updatedBody.host, "updated.local"); assert.equal(updatedBody.port, 9090); @@ -265,7 +265,7 @@ test("v1 management proxies main route covers auth, lookup variants, update and }) ); assert.equal(inUseDeleteRes.status, 409); - const inUseDeleteBody = await inUseDeleteRes.json(); + const inUseDeleteBody = (await inUseDeleteRes.json()) as any; assert.match(inUseDeleteBody.error.message, /remove assignments first/i); const forceDeleteRes = await proxyV1Route.DELETE( @@ -369,7 +369,7 @@ test("v1 management proxies main route returns server errors when persistence fa }) ); assert.equal(createdRes.status, 201); - const created = await createdRes.json(); + const created = (await createdRes.json()) as any; await withPrepareFailure("UPDATE proxy_registry", "update proxy failure", async () => { const response = await proxyV1Route.PATCH( @@ -419,7 +419,7 @@ test("v1 management assignments supports put and filtered get", async () => { }), }) ); - const created = await createdRes.json(); + const created = (await createdRes.json()) as any; const assignRes = await proxyAssignmentsV1Route.PUT( new Request("http://localhost/api/v1/management/proxies/assignments", { @@ -440,7 +440,7 @@ test("v1 management assignments supports put and filtered get", async () => { ) ); assert.equal(filteredRes.status, 200); - const payload = await filteredRes.json(); + const payload = (await filteredRes.json()) as any; assert.equal(payload.items.length, 1); assert.equal(payload.items[0].proxyId, created.id); }); @@ -452,7 +452,7 @@ test("v1 management assignments covers unfiltered listing and error branches", a new Request("http://localhost/api/v1/management/proxies/assignments?limit=5&offset=0") ); assert.equal(listRes.status, 200); - const listPayload = await listRes.json(); + const listPayload = (await listRes.json()) as any; assert.deepEqual(listPayload.items, []); assert.equal(listPayload.page.limit, 5); assert.equal(listPayload.page.offset, 0); @@ -490,7 +490,7 @@ test("v1 management assignments covers unfiltered listing and error branches", a }) ); assert.equal(invalidPayloadRes.status, 400); - const invalidPayloadBody = await invalidPayloadRes.json(); + const invalidPayloadBody = (await invalidPayloadRes.json()) as any; assert.equal(invalidPayloadBody.error.type, "invalid_request"); assert.equal(Array.isArray(invalidPayloadBody.error.details), true); @@ -536,7 +536,7 @@ test("v1 management health endpoint aggregates proxy log metrics", async () => { }), }) ); - const created = await createdRes.json(); + const created = (await createdRes.json()) as any; proxyLogger.logProxyEvent({ status: "success", @@ -559,7 +559,7 @@ test("v1 management health endpoint aggregates proxy log metrics", async () => { new Request("http://localhost/api/v1/management/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 === created.id); assert.ok(row); assert.equal(row.totalRequests >= 2, true); @@ -587,7 +587,7 @@ test("v1 management health endpoint covers default window and error handling", a new Request("http://localhost/api/v1/management/proxies/health") ); assert.equal(defaultRes.status, 200); - const defaultBody = await defaultRes.json(); + const defaultBody = (await defaultRes.json()) as any; assert.equal(defaultBody.windowHours, 24); assert.equal(defaultBody.total, 1); @@ -615,7 +615,7 @@ test("v1 bulk assignment updates multiple scope IDs in one request", async () => }), }) ); - const proxy = await proxyRes.json(); + const proxy = (await proxyRes.json()) as any; const bulkRes = await proxyBulkAssignV1Route.PUT( new Request("http://localhost/api/v1/management/proxies/bulk-assign", { @@ -629,13 +629,13 @@ test("v1 bulk assignment updates multiple scope IDs in one request", async () => }) ); assert.equal(bulkRes.status, 200); - const bulkPayload = await bulkRes.json(); + const bulkPayload = (await bulkRes.json()) as any; assert.equal(bulkPayload.updated, 2); const checkRes = await proxyAssignmentsV1Route.GET( new Request("http://localhost/api/v1/management/proxies/assignments?scope=provider") ); - const checkPayload = await checkRes.json(); + const checkPayload = (await checkRes.json()) as any; assert.equal(checkPayload.items.length >= 2, true); }); @@ -708,7 +708,7 @@ test("v1 assignments route resolves connection proxies and bulk assignment cover }), }) ); - const proxy = await proxyRes.json(); + const proxy = (await proxyRes.json()) as any; const assignRes = await proxyAssignmentsV1Route.PUT( new Request("http://localhost/api/v1/management/proxies/assignments", { @@ -729,7 +729,7 @@ test("v1 assignments route resolves connection proxies and bulk assignment cover ) ); assert.equal(resolveRes.status, 200); - const resolvePayload = await resolveRes.json(); + const resolvePayload = (await resolveRes.json()) as any; assert.equal(resolvePayload.level, "account"); assert.equal(resolvePayload.proxy.host, "resolve.local"); @@ -766,7 +766,7 @@ test("v1 assignments route resolves connection proxies and bulk assignment cover }) ); assert.equal(normalizedRes.status, 200); - const normalizedPayload = await normalizedRes.json(); + const normalizedPayload = (await normalizedRes.json()) as any; assert.equal(normalizedPayload.scope, "account"); assert.equal(normalizedPayload.requested, 2); assert.equal(normalizedPayload.updated, 1); @@ -782,7 +782,7 @@ test("v1 assignments route resolves connection proxies and bulk assignment cover }) ); assert.equal(globalRes.status, 200); - const globalPayload = await globalRes.json(); + const globalPayload = (await globalRes.json()) as any; assert.equal(globalPayload.scope, "global"); assert.equal(globalPayload.requested, 1); assert.equal(globalPayload.updated, 1); diff --git a/tests/unit/proxy-registry.test.ts b/tests/unit/proxy-registry.test.ts index f41de208bd..32037dc027 100644 --- a/tests/unit/proxy-registry.test.ts +++ b/tests/unit/proxy-registry.test.ts @@ -39,8 +39,8 @@ test("proxy registry blocks delete when proxy is still assigned", async () => { await assert.rejects( async () => proxiesDb.deleteProxyById(created.id), (error) => { - assert.equal(error.status, 409); - assert.equal(error.code, "proxy_in_use"); + assert.equal((error as any).status, 409); + (assert as any).equal((error as any).code, "proxy_in_use"); return true; } ); @@ -56,7 +56,7 @@ test("registry assignment takes precedence over legacy proxy config", async () = apiKey: "sk-test", }); - await settingsDb.setProxyForLevel("key", conn.id, { + await settingsDb.setProxyForLevel("key", (conn as any).id, { type: "http", host: "legacy-key.local", port: 8080, @@ -76,9 +76,9 @@ test("registry assignment takes precedence over legacy proxy config", async () = }); await proxiesDb.assignProxyToScope("provider", "openai", providerProxy.id); - await proxiesDb.assignProxyToScope("account", conn.id, accountProxy.id); + await proxiesDb.assignProxyToScope("account", (conn as any).id, accountProxy.id); - const resolved = await settingsDb.resolveProxyForConnection(conn.id); + const resolved = await settingsDb.resolveProxyForConnection((conn as any).id); assert.equal(resolved.level, "account"); assert.equal(resolved.source, "registry"); assert.equal(resolved.proxy.host, "account.local"); @@ -104,7 +104,7 @@ test("legacy proxy config migration imports global/provider/key assignments", as host: "provider-legacy.local", port: 443, }); - await settingsDb.setProxyForLevel("key", conn.id, { + await settingsDb.setProxyForLevel("key", (conn as any).id, { type: "http", host: "account-legacy.local", port: 8082, @@ -114,7 +114,7 @@ test("legacy proxy config migration imports global/provider/key assignments", as assert.equal(result.skipped, false); assert.equal(result.migrated >= 3, true); - const resolved = await settingsDb.resolveProxyForConnection(conn.id); + const resolved = await settingsDb.resolveProxyForConnection((conn as any).id); assert.equal(resolved.level, "account"); assert.equal(resolved.source, "registry"); assert.equal(resolved.proxy.host, "account-legacy.local"); diff --git a/tests/unit/qoder-executor.test.ts b/tests/unit/qoder-executor.test.ts index 5e6dfab2a2..7d5af39fbe 100644 --- a/tests/unit/qoder-executor.test.ts +++ b/tests/unit/qoder-executor.test.ts @@ -161,7 +161,7 @@ test("QoderExecutor: missing tokens return an authentication error response", as assert.equal(url, "https://dashscope.aliyuncs.com"); assert.equal(response.status, 401); - const payload = await response.json(); + const payload = (await response.json()) as any; assert.equal(payload.error.code, "token_required"); }); @@ -202,9 +202,9 @@ test("QoderExecutor: non-stream calls target DashScope and map alias models", as }); assert.equal(url, "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions"); - assert.equal(transformedBody.model, "coder-model"); + assert.equal((transformedBody as any).model, "coder-model"); assert.equal(response.status, 200); - const payload = await response.json(); + const payload = (await response.json()) as any; assert.equal(payload.object, "chat.completion"); assert.equal(payload.choices[0].message.role, "assistant"); assert.equal(payload.choices[0].message.content, "OK"); diff --git a/tests/unit/remaining-tasks.test.ts b/tests/unit/remaining-tasks.test.ts index 3899e99d37..7b71b4c4fa 100644 --- a/tests/unit/remaining-tasks.test.ts +++ b/tests/unit/remaining-tasks.test.ts @@ -41,7 +41,7 @@ test("RequestTelemetry: measure() records errors", async () => { t.measure("connect", async () => { throw new Error("timeout"); }), - (err) => err.message === "timeout" + (err) => (err as any).message === "timeout" ); assert.equal(t.getSummary().phases[0].error, "timeout"); }); diff --git a/tests/unit/response-sanitizer.test.ts b/tests/unit/response-sanitizer.test.ts index 1d6bf6a298..0eef836c54 100644 --- a/tests/unit/response-sanitizer.test.ts +++ b/tests/unit/response-sanitizer.test.ts @@ -55,12 +55,12 @@ test("sanitizeOpenAIResponse extracts thinking, collapses newlines, strips final ], }); - assert.equal(sanitized.choices[0].index, 2); - assert.equal(sanitized.choices[0].finish_reason, "tool_calls"); - assert.equal(sanitized.choices[0].message.content, "Hello\n\nworld"); - assert.equal(sanitized.choices[0].message.reasoning_content, undefined); - assert.deepEqual(sanitized.choices[0].message.tool_calls, [{ id: "call_1" }]); - assert.deepEqual(sanitized.choices[0].message.function_call, { name: "legacy" }); + assert.equal((sanitized as any).choices[0].index, 2); + assert.equal((sanitized as any).choices[0].finish_reason, "tool_calls"); + (assert as any).equal((sanitized as any).choices[0].message.content, "Hello\n\nworld"); + assert.equal((sanitized as any).choices[0].message.reasoning_content, undefined); + (assert as any).deepEqual((sanitized as any).choices[0].message.tool_calls, [{ id: "call_1" }]); + assert.deepEqual((sanitized as any).choices[0].message.function_call, { name: "legacy" }); }); test("sanitizeOpenAIResponse preserves native reasoning_content when no visible content remains", () => { @@ -77,8 +77,8 @@ test("sanitizeOpenAIResponse preserves native reasoning_content when no visible ], }); - assert.equal(sanitized.choices[0].message.content, ""); - assert.equal(sanitized.choices[0].message.reasoning_content, "provider reasoning"); + assert.equal(((sanitized as any).choices[0].message as any).content, ""); + assert.equal((sanitized as any).choices[0].message.reasoning_content, "provider reasoning"); }); test("sanitizeOpenAIResponse maps Claude-style usage fields and strips extras", () => { @@ -93,7 +93,7 @@ test("sanitizeOpenAIResponse maps Claude-style usage fields and strips extras", }, }); - assert.deepEqual(sanitized.usage, { + assert.deepEqual((sanitized as any).usage, { prompt_tokens: 11, completion_tokens: 7, total_tokens: 18, @@ -118,7 +118,7 @@ test("sanitizeOpenAIResponse strips reasoning_details-derived reasoning_content ], }); - assert.equal(sanitized.choices[0].message.reasoning_content, undefined); + assert.equal((sanitized as any).choices[0].message.reasoning_content, undefined); }); test("sanitizeOpenAIResponse keeps reasoning_details-derived reasoning_content for reasoning-only messages", () => { @@ -138,7 +138,7 @@ test("sanitizeOpenAIResponse keeps reasoning_details-derived reasoning_content f ], }); - assert.equal(sanitized.choices[0].message.reasoning_content, "first second"); + assert.equal((sanitized as any).choices[0].message.reasoning_content, "first second"); }); test("sanitizeResponsesApiResponse converts chat completions tool calls into Responses output items", () => { @@ -176,16 +176,16 @@ test("sanitizeResponsesApiResponse converts chat completions tool calls into Res }, }); - assert.equal(sanitized.object, "response"); - assert.equal(sanitized.id, "resp_chatcmpl_tool"); - assert.equal(sanitized.output[0].type, "reasoning"); - assert.equal(sanitized.output[1].type, "function_call"); - assert.equal(sanitized.output[1].call_id, "call_web_search"); - assert.equal(sanitized.output[1].name, "omniroute_web_search"); - assert.equal(sanitized.usage.input_tokens, 12); - assert.equal(sanitized.usage.output_tokens, 5); - assert.equal(sanitized.usage.input_tokens_details.cached_tokens, 3); - assert.equal(sanitized.usage.output_tokens_details.reasoning_tokens, 2); + assert.equal((sanitized as any).object, "response"); + assert.equal((sanitized as any).id, "resp_chatcmpl_tool"); + assert.equal((sanitized as any).output[0].type, "reasoning"); + (assert as any).equal((sanitized as any).output[1].type, "function_call"); + (assert as any).equal((sanitized as any).output[1].call_id, "call_web_search"); + (assert as any).equal((sanitized as any).output[1].name, "omniroute_web_search"); + assert.equal((sanitized as any).usage.input_tokens, 12); + assert.equal(((sanitized as any).usage as any).output_tokens, 5); + assert.equal((sanitized as any).usage.input_tokens_details.cached_tokens, 3); + assert.equal((sanitized as any).usage.output_tokens_details.reasoning_tokens, 2); }); test("sanitizeResponsesApiResponse preserves native Responses payloads and usage details", () => { @@ -219,15 +219,15 @@ test("sanitizeResponsesApiResponse preserves native Responses payloads and usage }, }); - assert.equal(sanitized.object, "response"); - assert.equal(sanitized.output[0].content[0].text, "Hello\n\nworld"); - assert.equal(sanitized.output[1].arguments, '{"path":"/tmp/a"}'); - assert.equal(sanitized.output_text, "Hello\n\nworld"); - assert.equal(sanitized.usage.input_tokens, 20); - assert.equal(sanitized.usage.output_tokens, 7); - assert.equal(sanitized.usage.input_tokens_details.cached_tokens, 4); - assert.equal(sanitized.usage.input_tokens_details.cache_creation_tokens, 1); - assert.equal(sanitized.usage.output_tokens_details.reasoning_tokens, 3); + assert.equal((sanitized as any).object, "response"); + assert.equal(((sanitized as any).output[0] as any).content[0].text, "Hello\n\nworld"); + assert.equal((sanitized as any).output[1].arguments, '{"path":"/tmp/a"}'); + assert.equal((sanitized as any).output_text, "Hello\n\nworld"); + assert.equal((sanitized as any).usage.input_tokens, 20); + (assert as any).equal((sanitized as any).usage.output_tokens, 7); + assert.equal((sanitized as any).usage.input_tokens_details.cached_tokens, 4); + assert.equal((sanitized as any).usage.input_tokens_details.cache_creation_tokens, 1); + assert.equal((sanitized as any).usage.output_tokens_details.reasoning_tokens, 3); }); test("sanitizeStreamingChunk keeps only safe chunk fields and maps reasoning aliases", () => { @@ -292,7 +292,7 @@ test("sanitizeStreamingChunk converts reasoning_details arrays in deltas", () => ], }); - assert.equal(sanitized.choices[0].delta.reasoning_content, "alphabeta"); + assert.equal((sanitized as any).choices[0].delta.reasoning_content, "alphabeta"); }); test("sanitize functions return non-object inputs unchanged", () => { diff --git a/tests/unit/responses-handler.test.ts b/tests/unit/responses-handler.test.ts index b27864b4a8..9f5022dde0 100644 --- a/tests/unit/responses-handler.test.ts +++ b/tests/unit/responses-handler.test.ts @@ -233,7 +233,7 @@ test("handleResponsesCore propagates upstream failures from chatCore unchanged", assert.equal(result.success, false); assert.equal(result.status, 401); - const payload = await result.response.json(); + const payload = (await result.response.json()) as any; assert.equal(payload.error.message, "[401]: unauthorized"); }); diff --git a/tests/unit/responses-translation-fixes.test.ts b/tests/unit/responses-translation-fixes.test.ts index c85415377a..5d83a95875 100644 --- a/tests/unit/responses-translation-fixes.test.ts +++ b/tests/unit/responses-translation-fixes.test.ts @@ -18,7 +18,7 @@ test("convertResponsesApiFormat filters orphaned function_call_output items", () ], }; const result = convertResponsesApiFormat(body); - const toolMsgs = result.messages.filter((m) => m.role === "tool"); + const toolMsgs = (result as any).messages.filter((m) => m.role === "tool"); assert.equal(toolMsgs.length, 0); }); @@ -31,7 +31,7 @@ test("convertResponsesApiFormat skips function_call items with empty names", () ], }; const result = convertResponsesApiFormat(body); - const assistantMsgs = result.messages.filter((m) => m.role === "assistant"); + const assistantMsgs = (result as any).messages.filter((m) => m.role === "assistant"); assert.equal(assistantMsgs.length, 0); }); @@ -50,7 +50,7 @@ test("Responsesโ†’Chat: input_image converted to image_url with detail", () => { ], }; const result = openaiResponsesToOpenAIRequest(null, body, null, null); - const userMsg = result.messages.find((m) => m.role === "user"); + const userMsg = (result as any).messages.find((m) => m.role === "user"); const imgPart = userMsg.content.find((c) => c.type === "image_url"); assert.ok(imgPart, "should have image_url content part"); assert.equal(imgPart.image_url.url, "https://example.com/img.png"); @@ -69,7 +69,7 @@ test("Responsesโ†’Chat: input_image without detail omits detail field", () => { ], }; const result = openaiResponsesToOpenAIRequest(null, body, null, null); - const userMsg = result.messages.find((m) => m.role === "user"); + const userMsg = (result as any).messages.find((m) => m.role === "user"); const imgPart = userMsg.content.find((c) => c.type === "image_url"); assert.ok(imgPart); assert.equal(imgPart.image_url.url, "https://example.com/img.png"); @@ -90,7 +90,7 @@ test("Chatโ†’Responses: image_url detail preserved as input_image", () => { ], }; const result = openaiToOpenAIResponsesRequest("gpt-4", body, true, null); - const userItem = result.input.find((i) => i.type === "message" && i.role === "user"); + const userItem = (result as any).input.find((i) => i.type === "message" && i.role === "user"); const imgPart = userItem.content.find((c) => c.type === "input_image"); assert.ok(imgPart, "should have input_image content part"); assert.equal(imgPart.image_url, "https://example.com/img.png"); @@ -108,7 +108,7 @@ test("Chatโ†’Responses: image_url without detail omits detail", () => { ], }; const result = openaiToOpenAIResponsesRequest("gpt-4", body, true, null); - const userItem = result.input.find((i) => i.type === "message" && i.role === "user"); + const userItem = (result as any).input.find((i) => i.type === "message" && i.role === "user"); const imgPart = userItem.content.find((c) => c.type === "input_image"); assert.ok(imgPart); assert.equal(imgPart.detail, undefined); @@ -126,7 +126,7 @@ test("Responsesโ†’Chat: input_file converted to file content part", () => { ], }; const result = openaiResponsesToOpenAIRequest(null, body, null, null); - const userMsg = result.messages.find((m) => m.role === "user"); + const userMsg = (result as any).messages.find((m) => m.role === "user"); const filePart = userMsg.content.find((c) => c.type === "file"); assert.ok(filePart, "should have file content part"); assert.equal(filePart.file.file_id, "file-abc"); @@ -144,7 +144,7 @@ test("Chatโ†’Responses: file content part converted to input_file", () => { ], }; const result = openaiToOpenAIResponsesRequest("gpt-4", body, true, null); - const userItem = result.input.find((i) => i.type === "message" && i.role === "user"); + const userItem = (result as any).input.find((i) => i.type === "message" && i.role === "user"); const filePart = userItem.content.find((c) => c.type === "input_file"); assert.ok(filePart, "should have input_file content part"); assert.equal(filePart.file_id, "file-abc"); @@ -159,7 +159,7 @@ test("Responsesโ†’Chat: tool_choice {type:'function', name} wrapped to {type:'fu tools: [{ type: "function", name: "get_weather", parameters: {} }], }; const result = openaiResponsesToOpenAIRequest(null, body, null, null); - assert.deepEqual(result.tool_choice, { + (assert as any).deepEqual((result as any).tool_choice, { type: "function", function: { name: "get_weather" }, }); @@ -173,7 +173,7 @@ test("Chatโ†’Responses: tool_choice {type:'function', function:{name}} unwrapped tools: [{ type: "function", function: { name: "get_weather", parameters: {} } }], }; const result = openaiToOpenAIResponsesRequest("gpt-4", body, true, null); - assert.deepEqual(result.tool_choice, { + (assert as any).deepEqual((result as any).tool_choice, { type: "function", name: "get_weather", }); @@ -182,7 +182,7 @@ test("Chatโ†’Responses: tool_choice {type:'function', function:{name}} unwrapped test("Responsesโ†’Chat: string tool_choice passes through unchanged", () => { const body = { model: "gpt-4", input: "hello", tool_choice: "auto" }; const result = openaiResponsesToOpenAIRequest(null, body, null, null); - assert.equal(result.tool_choice, "auto"); + assert.equal((result as any).tool_choice, "auto"); }); test("Chatโ†’Responses: string tool_choice passes through unchanged", () => { @@ -192,7 +192,7 @@ test("Chatโ†’Responses: string tool_choice passes through unchanged", () => { tool_choice: "required", }; const result = openaiToOpenAIResponsesRequest("gpt-4", body, true, null); - assert.equal(result.tool_choice, "required"); + assert.equal((result as any).tool_choice, "required"); }); test("Responsesโ†’Chat: built-in tool_choice type throws unsupported error", () => { @@ -203,7 +203,7 @@ test("Responsesโ†’Chat: built-in tool_choice type throws unsupported error", () }; assert.throws( () => openaiResponsesToOpenAIRequest(null, body, null, null), - (err) => err.message.includes("web_search_preview") + (err) => (err as any).message.includes("web_search_preview") ); }); @@ -215,7 +215,7 @@ test("Responsesโ†’Chat: web_search tool type throws unsupported error", () => { }; assert.throws( () => openaiResponsesToOpenAIRequest(null, body, null, null), - (err) => err.message.includes("web_search") + (err) => (err as any).message.includes("web_search") ); }); @@ -227,7 +227,7 @@ test("Responsesโ†’Chat: computer tool type throws unsupported error", () => { }; assert.throws( () => openaiResponsesToOpenAIRequest(null, body, null, null), - (err) => err.message.includes("computer") + (err) => (err as any).message.includes("computer") ); }); @@ -239,7 +239,7 @@ test("Responsesโ†’Chat: mcp tool type throws unsupported error", () => { }; assert.throws( () => openaiResponsesToOpenAIRequest(null, body, null, null), - (err) => err.message.includes("mcp") + (err) => (err as any).message.includes("mcp") ); }); @@ -252,7 +252,7 @@ test("Responsesโ†’Chat: non-string arguments are JSON-stringified", () => { ], }; const result = openaiResponsesToOpenAIRequest(null, body, null, null); - const assistantMsg = result.messages.find((m) => m.role === "assistant"); + const assistantMsg = (result as any).messages.find((m) => m.role === "assistant"); assert.equal(typeof assistantMsg.tool_calls[0].function.arguments, "string"); assert.equal(assistantMsg.tool_calls[0].function.arguments, '{"key":"val"}'); }); @@ -275,7 +275,7 @@ test("Chatโ†’Responses: array tool content converts textโ†’input_text types", () ], }; const result = openaiToOpenAIResponsesRequest("gpt-4", body, true, null); - const outputItem = result.input.find((i) => i.type === "function_call_output"); + const outputItem = (result as any).input.find((i) => i.type === "function_call_output"); assert.ok(Array.isArray(outputItem.output), "output should be array"); assert.equal(outputItem.output[0].type, "input_text"); assert.equal(outputItem.output[0].text, "result data"); @@ -288,8 +288,8 @@ test("Responsesโ†’Chat: function tool type passes through", () => { tools: [{ type: "function", name: "greet", parameters: {} }], }; const result = openaiResponsesToOpenAIRequest(null, body, null, null); - assert.equal(result.tools.length, 1); - assert.equal(result.tools[0].type, "function"); + assert.equal((result as any).tools.length, 1); + assert.equal((result as any).tools[0].type, "function"); }); test("Chatโ†’Responses: deprecated function_call field on assistant converted to function_call item", () => { @@ -305,7 +305,7 @@ test("Chatโ†’Responses: deprecated function_call field on assistant converted to ], }; const result = openaiToOpenAIResponsesRequest("gpt-4", body, true, null); - const fcItem = result.input.find((i) => i.type === "function_call"); + const fcItem = (result as any).input.find((i) => i.type === "function_call"); assert.ok(fcItem, "should have function_call input item"); assert.equal(fcItem.name, "get_weather"); assert.equal(fcItem.arguments, '{"city":"NYC"}'); @@ -326,11 +326,11 @@ test("Chatโ†’Responses: deprecated function role message converted to function_c ], }; const result = openaiToOpenAIResponsesRequest("gpt-4", body, true, null); - const fcOutput = result.input.find((i) => i.type === "function_call_output"); + const fcOutput = (result as any).input.find((i) => i.type === "function_call_output"); assert.ok(fcOutput, "should have function_call_output item"); assert.equal(fcOutput.output, '{"temp":72}'); // The call_ids should match between function_call and function_call_output - const fcItem = result.input.find((i) => i.type === "function_call"); + const fcItem = (result as any).input.find((i) => i.type === "function_call"); assert.equal(fcOutput.call_id, fcItem.call_id); }); diff --git a/tests/unit/route-edge-coverage.test.ts b/tests/unit/route-edge-coverage.test.ts index e966536189..53ab66ce94 100644 --- a/tests/unit/route-edge-coverage.test.ts +++ b/tests/unit/route-edge-coverage.test.ts @@ -158,7 +158,7 @@ test("api keys route covers auth, create, masking, pagination fallback and cloud body: { name: "Key / Prod #1", noLog: true }, }) ); - const createdBody = await created.json(); + const createdBody = (await created.json()) as any; const stored = await apiKeysDb.getApiKeyById(createdBody.id); await apiKeysDb.createApiKey("Alpha", MACHINE_ID); @@ -168,9 +168,9 @@ test("api keys route covers auth, create, masking, pagination fallback and cloud await makeManagementSessionRequest("http://localhost/api/keys?limit=0&offset=-25") ); - const unauthenticatedBody = await unauthenticated.json(); - const invalidTokenBody = await invalidToken.json(); - const pagedBody = await paged.json(); + const unauthenticatedBody = (await unauthenticated.json()) as any; + const invalidTokenBody = (await invalidToken.json()) as any; + const pagedBody = (await paged.json()) as any; assert.equal(unauthenticated.status, 401); assert.equal(unauthenticatedBody.error.message, "Authentication required"); @@ -214,7 +214,7 @@ test("api keys route rejects invalid payloads and malformed JSON", async () => { }) ); - const malformedBody = await malformed.json(); + const malformedBody = (await malformed.json()) as any; assert.equal(missingName.status, 400); assert.equal(malformed.status, 500); @@ -297,16 +297,16 @@ test("settings proxy route covers full config, resolve, validation, delete and g new Request("http://localhost/api/settings/proxy", { method: "DELETE" }) ); - const invalidJsonBody = await invalidJson.json(); - const invalidBodyPayload = await invalidBody.json(); - const validPutBody = await validPut.json(); - const legacyPutBody = await legacyPut.json(); - const providerGetBody = await providerGet.json(); - const resolveBody = await resolveGet.json(); - const fullConfigBody = await fullConfig.json(); - const deletedBody = await deleted.json(); - const resolveAfterDeleteBody = await resolveAfterDelete.json(); - const missingLevelBody = await missingLevel.json(); + const invalidJsonBody = (await invalidJson.json()) as any; + const invalidBodyPayload = (await invalidBody.json()) as any; + const validPutBody = (await validPut.json()) as any; + const legacyPutBody = (await legacyPut.json()) as any; + const providerGetBody = (await providerGet.json()) as any; + const resolveBody = (await resolveGet.json()) as any; + const fullConfigBody = (await fullConfig.json()) as any; + const deletedBody = (await deleted.json()) as any; + const resolveAfterDeleteBody = (await resolveAfterDelete.json()) as any; + const missingLevelBody = (await missingLevel.json()) as any; assert.equal(invalidJson.status, 400); assert.equal(invalidJsonBody.error.message, "Invalid JSON body"); @@ -346,7 +346,7 @@ test("settings proxy route prefers proxy registry assignments and enforces socks const registryBacked = await settingsProxyRoute.GET( new Request("http://localhost/api/settings/proxy?level=global") ); - const registryBackedBody = await registryBacked.json(); + const registryBackedBody = (await registryBacked.json()) as any; process.env.ENABLE_SOCKS5_PROXY = "false"; const disabledSocks = await settingsProxyRoute.PUT( @@ -370,8 +370,8 @@ test("settings proxy route prefers proxy registry assignments and enforces socks }) ); - const disabledSocksBody = await disabledSocks.json(); - const enabledSocksBody = await enabledSocks.json(); + const disabledSocksBody = (await disabledSocks.json()) as any; + const enabledSocksBody = (await enabledSocks.json()) as any; assert.equal(registryBacked.status, 200); assert.equal(registryBackedBody.proxy.host, "registry.local"); @@ -393,7 +393,7 @@ test("settings proxy route covers default types, null maps, registry fallback, a }) ); assert.equal(defaultTypePut.status, 200); - const defaultTypeBody = await defaultTypePut.json(); + const defaultTypeBody = (await defaultTypePut.json()) as any; assert.equal(defaultTypeBody.global.type, "http"); const clearMapPut = await settingsProxyRoute.PUT( @@ -406,7 +406,7 @@ test("settings proxy route covers default types, null maps, registry fallback, a }) ); assert.equal(clearMapPut.status, 200); - const clearMapBody = await clearMapPut.json(); + const clearMapBody = (await clearMapPut.json()) as any; assert.equal(clearMapBody.global, null); assert.equal(Object.prototype.hasOwnProperty.call(clearMapBody.providers || {}, "openai"), false); @@ -441,7 +441,7 @@ test("settings proxy route covers default types, null maps, registry fallback, a new Request("http://localhost/api/settings/proxy?level=global") ); assert.equal(response.status, 200); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(body.level, "global"); assert.equal(body.proxy.host, "legacy-fallback.local"); } @@ -473,7 +473,7 @@ test("settings proxy route covers default types, null maps, registry fallback, a }) ); assert.equal(response.status, 500); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(body.error.type, "server_error"); assert.match(body.error.message, /proxy config write failure/i); } @@ -518,7 +518,7 @@ test("management proxies route covers auth, pagination, lookup, where-used, patc }, }) ); - const created = await createdResponse.json(); + const created = (await createdResponse.json()) as any; await localDb.assignProxyToScope("provider", "openai", created.id); const pagedList = await managementProxiesRoute.GET( @@ -580,18 +580,18 @@ test("management proxies route covers auth, pagination, lookup, where-used, patc ) ); - const unauthenticatedBody = await unauthenticated.json(); - const invalidTokenBody = await invalidToken.json(); - const pagedListBody = await pagedList.json(); - const byIdBody = await byId.json(); - const whereUsedBody = await whereUsed.json(); - const missingGetBody = await missingGet.json(); - const invalidJsonPatchBody = await invalidJsonPatch.json(); - const invalidPatchBody = await invalidPatch.json(); - const patchedBody = await patched.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 pagedListBody = (await pagedList.json()) as any; + const byIdBody = (await byId.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 patchedBody = (await patched.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"); @@ -633,7 +633,7 @@ test("embeddings route covers options, custom-model listing and defensive POST b const optionsResponse = await embeddingsRoute.OPTIONS(); const getResponse = await embeddingsRoute.GET(); - const getBody = await getResponse.json(); + const getBody = (await getResponse.json()) as any; const invalidJson = await embeddingsRoute.POST( new Request("http://localhost/v1/embeddings", { @@ -656,9 +656,9 @@ test("embeddings route covers options, custom-model listing and defensive POST b ); const optionsHeaders = Object.fromEntries(optionsResponse.headers.entries()); - const invalidJsonBody = await invalidJson.json(); - const validationFailureBody = await validationFailure.json(); - const invalidModelBody = await invalidModel.json(); + const invalidJsonBody = (await invalidJson.json()) as any; + const validationFailureBody = (await validationFailure.json()) as any; + const invalidModelBody = (await invalidModel.json()) as any; assert.equal(optionsHeaders["access-control-allow-origin"], "*"); assert.equal(getResponse.status, 200); @@ -726,10 +726,10 @@ test("embeddings route enforces caller auth, missing credentials and provider ra }) ); - const missingKeyBody = await missingKey.json(); - const invalidKeyBody = await invalidKey.json(); - const missingCredentialsBody = await missingCredentials.json(); - const allRateLimitedBody = await allRateLimited.json(); + const missingKeyBody = (await missingKey.json()) as any; + const invalidKeyBody = (await invalidKey.json()) as any; + const missingCredentialsBody = (await missingCredentials.json()) as any; + const allRateLimitedBody = (await allRateLimited.json()) as any; assert.equal(missingKey.status, 401); assert.equal(missingKeyBody.error.message, "Missing API key"); @@ -757,7 +757,7 @@ test("embeddings route tolerates custom-model and provider-node lookup failures" "custom models unavailable", async () => { const response = await embeddingsRoute.GET(); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.ok(body.data.some((model) => model.id === "openai/text-embedding-3-small")); @@ -774,7 +774,7 @@ test("embeddings route tolerates custom-model and provider-node lookup failures" body: { model: "openai/text-embedding-3-small", input: "hello" }, }) ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.equal(body.model, "openai/text-embedding-3-small"); @@ -820,7 +820,7 @@ test("embeddings route supports local provider nodes without credentials and enf }, }) ); - const localBody = await localResponse.json(); + const localBody = (await localResponse.json()) as any; assert.equal(localResponse.status, 200); assert.equal(localBody.model, "localembed/demo-embed"); @@ -849,7 +849,7 @@ test("embeddings route supports local provider nodes without credentials and enf }), }) ); - const rejectedBody = await rejected.json(); + const rejectedBody = (await rejected.json()) as any; assert.equal(rejected.status, 403); assert.match(rejectedBody.error.message, /not allowed/i); @@ -871,7 +871,7 @@ test("embeddings route returns normalized upstream failures", async () => { body: { model: "openai/text-embedding-3-small", input: "hello" }, }) ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 502); assert.equal(body.error.message, "upstream boom"); @@ -907,7 +907,7 @@ test("embeddings route GET skips malformed, non-embedding, and duplicate custom ); const response = await embeddingsRoute.GET(); - const body = await response.json(); + const body = (await response.json()) as any; const ids = body.data.map((model) => model.id); assert.equal(response.status, 200); @@ -976,7 +976,7 @@ test("embeddings route tolerates non-array provider nodes and remote fallback lo }) ) ); - const remoteFallbackBody = await remoteFallback.json(); + const remoteFallbackBody = (await remoteFallback.json()) as any; assert.equal(remoteFallback.status, 400); assert.match(remoteFallbackBody.error.message, /Unknown embedding provider|No matching/i); @@ -1063,7 +1063,7 @@ test("embeddings route handles responses provider nodes, invalid local nodes, an }) ) ); - const remoteBody = await remoteResponse.json(); + const remoteBody = (await remoteResponse.json()) as any; assert.equal(remoteResponse.status, 200); assert.equal(fetchCalls[1].url, "https://remote.example.com/v1beta/openai/embeddings"); diff --git a/tests/unit/safe-outbound-fetch.test.ts b/tests/unit/safe-outbound-fetch.test.ts index 94416bae1a..67da7f3884 100644 --- a/tests/unit/safe-outbound-fetch.test.ts +++ b/tests/unit/safe-outbound-fetch.test.ts @@ -58,9 +58,9 @@ test("safeOutboundFetch normalizes timeout failures", async () => { }), (error) => { assert.equal(error instanceof SafeOutboundFetchError, true); - assert.equal(error.code, "TIMEOUT"); - assert.equal(error.timeoutMs, 5); - assert.equal(error.url, "https://example.test/slow"); + assert.equal((error as any).code, "TIMEOUT"); + (assert as any).equal((error as any).timeoutMs, 5); + (assert as any).equal((error as any).url, "https://example.test/slow"); return true; } ); @@ -81,9 +81,9 @@ test("safeOutboundFetch blocks redirects when allowRedirect is disabled", async }), (error) => { assert.equal(error instanceof SafeOutboundFetchError, true); - assert.equal(error.code, "REDIRECT_BLOCKED"); - assert.equal(error.status, 302); - assert.equal(error.location, "https://redirect.example.test/login"); + assert.equal((error as any).code, "REDIRECT_BLOCKED"); + assert.equal((error as any).status, 302); + assert.equal((error as any).location, "https://redirect.example.test/login"); return true; } ); @@ -104,7 +104,7 @@ test("safeOutboundFetch blocks private hosts when public-only guard is enabled", }), (error) => { assert.equal(error instanceof SafeOutboundFetchError, true); - assert.equal(error.code, "URL_GUARD_BLOCKED"); + assert.equal((error as any).code, "URL_GUARD_BLOCKED"); assert.equal(called, false); return true; } diff --git a/tests/unit/schema-coercion.test.ts b/tests/unit/schema-coercion.test.ts index 5b6310d863..669a6249e5 100644 --- a/tests/unit/schema-coercion.test.ts +++ b/tests/unit/schema-coercion.test.ts @@ -22,9 +22,9 @@ test("coerceSchemaNumericFields converts string numbers to actual numbers", () = const result = coerceSchemaNumericFields(schema); - assert.strictEqual(result.minimum, 5); - assert.strictEqual(result.properties.items.minItems, 1); - assert.strictEqual(result.properties.items.maxItems, 2); + assert.strictEqual((result as any).minimum, 5); + (assert as any).strictEqual((result as any).properties.items.minItems, 1); + (assert as any).strictEqual((result as any).properties.items.maxItems, 2); }); test("coerceSchemaNumericFields ignores non-numeric strings", () => { @@ -35,8 +35,8 @@ test("coerceSchemaNumericFields ignores non-numeric strings", () => { const result = coerceSchemaNumericFields(schema); - assert.strictEqual(result.minimum, "abc"); - assert.strictEqual(result.maximum, 10.5); + (assert as any).strictEqual((result as any).minimum, "abc"); + assert.strictEqual((result as any).maximum, 10.5); }); test("coerceToolSchemas applies coercion to OpenAI tools", () => { @@ -76,7 +76,7 @@ test("sanitizeToolDescription converts null to empty string (OpenAI format)", () function: { name: "test", description: null, parameters: {} }, }; const result = sanitizeToolDescription(tool); - assert.equal(result.function.description, ""); + assert.equal((result as any).function.description, ""); }); test("sanitizeToolDescription converts number to string (OpenAI format)", () => { @@ -85,13 +85,13 @@ test("sanitizeToolDescription converts number to string (OpenAI format)", () => function: { name: "test", description: 42, parameters: {} }, }; const result = sanitizeToolDescription(tool); - assert.equal(result.function.description, "42"); + assert.equal((result as any).function.description, "42"); }); test("sanitizeToolDescription handles Claude format", () => { const tool = { name: "test", description: null, input_schema: {} }; const result = sanitizeToolDescription(tool); - assert.equal(result.description, ""); + assert.equal((result as any).description, ""); }); test("sanitizeToolDescription preserves valid string descriptions", () => { @@ -100,7 +100,7 @@ test("sanitizeToolDescription preserves valid string descriptions", () => { function: { name: "test", description: "A useful tool", parameters: {} }, }; const result = sanitizeToolDescription(tool); - assert.equal(result.function.description, "A useful tool"); + assert.equal((result as any).function.description, "A useful tool"); }); test("sanitizeToolDescriptions works on arrays", () => { diff --git a/tests/unit/search-route.test.ts b/tests/unit/search-route.test.ts index 05ecfc4911..69d957bc76 100644 --- a/tests/unit/search-route.test.ts +++ b/tests/unit/search-route.test.ts @@ -47,7 +47,7 @@ test.after(() => { test("v1 search GET lists all 9 search providers", async () => { const response = await searchRoute.GET(); - const body = await response.json(); + const body = (await response.json()) as any; const ids = body.data.map((item: { id: string }) => item.id); assert.equal(response.status, 200); @@ -105,7 +105,7 @@ test("v1 search POST uses stored Linkup credentials and returns normalized resul }), }) ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.equal(capturedUrl, "https://api.linkup.so/v1/search"); @@ -162,7 +162,7 @@ test("v1 search POST accepts authless SearXNG with provider_options baseUrl", as }), }) ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.equal( @@ -210,7 +210,7 @@ test("v1 search POST accepts authless SearXNG with the built-in default base URL }), }) ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.equal( @@ -265,7 +265,7 @@ test("v1 search POST preserves stored SearXNG baseUrl for authless providers", a }), }) ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.equal( @@ -311,7 +311,7 @@ test("v1 search POST auto-select uses authless SearXNG when no API-key providers }), }) ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.equal( diff --git a/tests/unit/services-branch-hardening.test.ts b/tests/unit/services-branch-hardening.test.ts index 6adef33e1f..a6384e42de 100644 --- a/tests/unit/services-branch-hardening.test.ts +++ b/tests/unit/services-branch-hardening.test.ts @@ -174,7 +174,7 @@ test("rate limit semaphore covers immediate acquire, timeout, cooldown drain and maxConcurrency: 1, timeoutMs: 15, }); - await assert.rejects(timeoutPromise, (error) => error?.code === "SEMAPHORE_TIMEOUT"); + await assert.rejects(timeoutPromise, (error) => (error as any).code === "SEMAPHORE_TIMEOUT"); heldRelease(); const firstRelease = await rateLimitSemaphore.acquire("model-c", { maxConcurrency: 1 }); diff --git a/tests/unit/settings-api.test.ts b/tests/unit/settings-api.test.ts index 1767dcc794..47c39ddcd9 100644 --- a/tests/unit/settings-api.test.ts +++ b/tests/unit/settings-api.test.ts @@ -141,7 +141,7 @@ describe("Settings API - debugMode and hiddenSidebarItems", () => { body: { antigravitySignatureCacheMode: "bypass" }, }) ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.equal(body.antigravitySignatureCacheMode, "bypass"); diff --git a/tests/unit/settings-route-password.test.ts b/tests/unit/settings-route-password.test.ts index 8576d4cc86..3b0fee671a 100644 --- a/tests/unit/settings-route-password.test.ts +++ b/tests/unit/settings-route-password.test.ts @@ -53,11 +53,14 @@ test("settings route password update requires the current INITIAL_PASSWORD after assert.equal(response.status, 200); assert.equal(managementPassword.isBcryptHash(settings.password), true); assert.equal( - await managementPassword.verifyManagementPassword("rotated-secret", settings.password), + await managementPassword.verifyManagementPassword("rotated-secret", (settings as any).password), true ); assert.equal( - await managementPassword.verifyManagementPassword("bootstrap-secret", settings.password), + await managementPassword.verifyManagementPassword( + "bootstrap-secret" as any, + (settings as any).password + ), false ); }); diff --git a/tests/unit/shared-api-utils.test.ts b/tests/unit/shared-api-utils.test.ts index ccde74d910..56b8f709a0 100644 --- a/tests/unit/shared-api-utils.test.ts +++ b/tests/unit/shared-api-utils.test.ts @@ -99,9 +99,12 @@ test("shared api utils throw enriched errors for non-OK responses", async () => await assert.rejects( () => del("http://localhost/delete"), (error) => { - assert.equal(error.message, "bad request"); - assert.equal(error.status, 400); - assert.deepEqual(error.data, { error: "bad request", detail: "broken payload" }); + assert.equal((error as any).message, "bad request"); + (assert as any).equal((error as any).status, 400); + (assert as any).deepEqual((error as any).data, { + error: "bad request", + detail: "broken payload", + }); return true; } ); diff --git a/tests/unit/signature-cache.test.ts b/tests/unit/signature-cache.test.ts index 0d6f9b1b93..547fe952fe 100644 --- a/tests/unit/signature-cache.test.ts +++ b/tests/unit/signature-cache.test.ts @@ -60,7 +60,7 @@ test("detectAndLearn: finds known signatures", () => { const result = detectAndLearn("I think... Hello!", {}); assert.ok(result.found.includes("")); assert.ok(result.found.includes("")); - assert.ok(!result.cleaned.includes("")); + assert.ok(!(result.cleaned as any).includes("")); }); test("detectAndLearn: auto-learns new thinking tags", () => { diff --git a/tests/unit/skills-routes.test.ts b/tests/unit/skills-routes.test.ts index a58092607b..0dc3ba150e 100644 --- a/tests/unit/skills-routes.test.ts +++ b/tests/unit/skills-routes.test.ts @@ -71,7 +71,7 @@ test("skills route GET loads skills from the database and lists them", async () const response = await skillsRoute.GET( new Request("http://localhost/api/skills?page=1&limit=50") ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 200); assert.ok(Array.isArray(body.data)); @@ -90,7 +90,7 @@ test("skills route GET returns 500 when the registry load fails", async () => { const response = await skillsRoute.GET( new Request("http://localhost/api/skills?page=1&limit=50") ); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(response.status, 500); assert.equal(body.error, "skill db unavailable"); @@ -105,12 +105,12 @@ test("skills by-id DELETE removes existing skills, returns 404 for missing ones, const deleted = await skillByIdRoute.DELETE(new Request("http://localhost/api/skills/id"), { params: Promise.resolve({ id: created.id }), }); - const deletedBody = await deleted.json(); + const deletedBody = (await deleted.json()) as any; const missing = await skillByIdRoute.DELETE(new Request("http://localhost/api/skills/id"), { params: Promise.resolve({ id: created.id }), }); - const missingBody = await missing.json(); + const missingBody = (await missing.json()) as any; const originalUnregisterById = skillRegistry.unregisterById; skillRegistry.unregisterById = async () => { @@ -121,7 +121,7 @@ test("skills by-id DELETE removes existing skills, returns 404 for missing ones, const failed = await skillByIdRoute.DELETE(new Request("http://localhost/api/skills/id"), { params: Promise.resolve({ id: "broken-skill" }), }); - const failedBody = await failed.json(); + const failedBody = (await failed.json()) as any; assert.equal(deleted.status, 200); assert.deepEqual(deletedBody, { success: true }); @@ -167,14 +167,14 @@ test("skills by-id PUT updates enabled state, validates input, and surfaces pars { params: Promise.resolve({ id: created.id }) } ); - const updatedBody = await updated.json(); - const invalidBody = await invalid.json(); - const malformedBody = await malformed.json(); + const updatedBody = (await updated.json()) as any; + const invalidBody = (await invalid.json()) as any; + const malformedBody = (await malformed.json()) as any; const loadedSkillRow = core .getDbInstance() .prepare("SELECT enabled FROM skills WHERE id = ?") .get(created.id); - const isEnabled = loadedSkillRow ? loadedSkillRow.enabled === 1 : false; + const isEnabled = loadedSkillRow ? (loadedSkillRow as any).enabled === 1 : false; assert.equal(updated.status, 200); assert.deepEqual(updatedBody, { success: true, enabled: true }); diff --git a/tests/unit/skills-skillssh.test.ts b/tests/unit/skills-skillssh.test.ts index 45536523ba..7d72aa844e 100644 --- a/tests/unit/skills-skillssh.test.ts +++ b/tests/unit/skills-skillssh.test.ts @@ -121,7 +121,7 @@ test("searchSkillsSh throws on non-ok response", async () => { await assert.rejects( () => searchSkillsSh("fail"), - (err) => err.message.includes("skills.sh API error: 500") + (err) => (err as any).message.includes("skills.sh API error: 500") ); }); @@ -145,7 +145,7 @@ test("fetchSkillMd throws on 404", async () => { await assert.rejects( () => fetchSkillMd("owner/repo", "missing-skill"), - (err) => err.message.includes("Failed to fetch SKILL.md: 404") + (err) => (err as any).message.includes("Failed to fetch SKILL.md: 404") ); }); @@ -179,7 +179,7 @@ test("skillssh search route returns skills from the API", async () => { const req = new Request("http://localhost/api/skills/skillssh?q=docker&limit=10"); const res = await searchRoute.GET(req); - const body = await res.json(); + const body = (await res.json()) as any; assert.equal(res.status, 200); assert.equal(body.skills.length, 2); @@ -192,7 +192,7 @@ test("skillssh search route returns 500 when upstream fails", async () => { const req = new Request("http://localhost/api/skills/skillssh?q=broken"); const res = await searchRoute.GET(req); - const body = await res.json(); + const body = (await res.json()) as any; assert.equal(res.status, 500); assert.ok(body.error.includes("skills.sh API error")); @@ -223,7 +223,7 @@ test("skillssh install route registers a skill from skills.sh", async () => { }); const res = await installRoute.POST(req); - const body = await res.json(); + const body = (await res.json()) as any; assert.equal(res.status, 200); assert.equal(body.success, true); @@ -269,7 +269,7 @@ test("skillssh install route returns 500 when SKILL.md fetch fails", async () => }); const res = await installRoute.POST(req); - const body = await res.json(); + const body = (await res.json()) as any; assert.equal(res.status, 500); assert.ok(body.error.includes("Failed to fetch SKILL.md")); @@ -295,7 +295,7 @@ test("skillssh install route defaults version to 1.0.0 when omitted", async () = }); const res = await installRoute.POST(req); - const body = await res.json(); + const body = (await res.json()) as any; assert.equal(res.status, 200); assert.equal(body.success, true); diff --git a/tests/unit/spend-batch-writer.test.ts b/tests/unit/spend-batch-writer.test.ts index 85b994bc3e..29253602f6 100644 --- a/tests/unit/spend-batch-writer.test.ts +++ b/tests/unit/spend-batch-writer.test.ts @@ -31,7 +31,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 { diff --git a/tests/unit/sse-auth.test.ts b/tests/unit/sse-auth.test.ts index 04d558a7c2..346bd1fa53 100644 --- a/tests/unit/sse-auth.test.ts +++ b/tests/unit/sse-auth.test.ts @@ -134,7 +134,7 @@ test("getProviderCredentials enforces generic quota policy unless explicitly byp }, }); const resetAt = futureIso(); - quotaCache.setQuotaCache(connection.id, "openai", { + quotaCache.setQuotaCache((connection as any).id, "openai", { daily: { remainingPercentage: 10, resetAt }, }); @@ -301,17 +301,17 @@ test("getProviderCredentials round-robin stays on the current account while belo priority: 2, }); - await providersDb.updateProviderConnection(current.id, { + await providersDb.updateProviderConnection((current as any).id, { lastUsedAt: new Date().toISOString(), consecutiveUseCount: 1, }); - await providersDb.updateProviderConnection(other.id, { + await providersDb.updateProviderConnection((other as any).id, { lastUsedAt: new Date(Date.now() - 60_000).toISOString(), consecutiveUseCount: 0, }); const selected = await auth.getProviderCredentials("openai"); - const updated = await providersDb.getProviderConnectionById(current.id); + const updated = await providersDb.getProviderConnectionById((current as any).id); assert.equal(selected.connectionId, current.id); assert.equal(updated.consecutiveUseCount, 2); @@ -409,7 +409,7 @@ test("getProviderCredentials retains terminal accounts for combo live tests", as const bypassed = await auth.getProviderCredentials("openai", null, null, null, { allowSuppressedConnections: true, }); - const updated = await providersDb.getProviderConnectionById(connection.id); + const updated = await providersDb.getProviderConnectionById((connection as any).id); assert.equal(blocked, null); assert.equal(bypassed.connectionId, connection.id); @@ -450,9 +450,15 @@ test("getProviderCredentials reports allRateLimited when every account is model- name: "gemini-model-lock-second", }); - 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", @@ -479,7 +485,7 @@ test("getProviderCredentials auto-decays stale backoff metadata for recovered ac const selected = await auth.getProviderCredentials("openai"); await flushWrites(); - const updated = await providersDb.getProviderConnectionById(connection.id); + const updated = await providersDb.getProviderConnectionById((connection as any).id); assert.equal(selected.connectionId, connection.id); assert.equal(updated.backoffLevel, 0); @@ -497,7 +503,7 @@ test("getProviderCredentials falls back to a five-minute retry window when quota }, }); - quotaCache.setQuotaCache(connection.id, "openai", { + quotaCache.setQuotaCache((connection as any).id, "openai", { daily: { remainingPercentage: 0, resetAt: null }, }); @@ -522,10 +528,10 @@ test("getProviderCredentials prioritizes accounts that still have quota availabl apiKey: "sk-available", }); - quotaCache.setQuotaCache(exhausted.id, "openai", { + quotaCache.setQuotaCache((exhausted as any).id, "openai", { daily: { remainingPercentage: 0, resetAt: futureIso() }, }); - quotaCache.setQuotaCache(available.id, "openai", { + quotaCache.setQuotaCache((available as any).id, "openai", { daily: { remainingPercentage: 65, resetAt: futureIso() }, }); @@ -549,17 +555,17 @@ test("getProviderCredentials round-robin switches to the least recently used acc priority: 2, }); - await providersDb.updateProviderConnection(current.id, { + await providersDb.updateProviderConnection((current as any).id, { lastUsedAt: new Date().toISOString(), consecutiveUseCount: 2, }); - await providersDb.updateProviderConnection(fallback.id, { + await providersDb.updateProviderConnection((fallback as any).id, { lastUsedAt: new Date(Date.now() - 120_000).toISOString(), consecutiveUseCount: 0, }); const selected = await auth.getProviderCredentials("openai"); - const updated = await providersDb.getProviderConnectionById(fallback.id); + const updated = await providersDb.getProviderConnectionById((fallback as any).id); assert.equal(selected.connectionId, fallback.id); assert.equal(updated.consecutiveUseCount, 1); @@ -579,17 +585,17 @@ test("getProviderCredentials round-robin fallback mode excludes the failed accou priority: 2, }); - await providersDb.updateProviderConnection(failed.id, { + await providersDb.updateProviderConnection((failed as any).id, { lastUsedAt: new Date().toISOString(), consecutiveUseCount: 3, }); - await providersDb.updateProviderConnection(fallback.id, { + await providersDb.updateProviderConnection((fallback as any).id, { lastUsedAt: new Date(Date.now() - 120_000).toISOString(), consecutiveUseCount: 0, }); - const selected = await auth.getProviderCredentials("openai", failed.id); - const updated = await providersDb.getProviderConnectionById(fallback.id); + const selected = await auth.getProviderCredentials("openai" as any, (failed as any).id); + const updated = await providersDb.getProviderConnectionById((fallback as any).id); assert.equal(selected.connectionId, fallback.id); assert.equal(updated.consecutiveUseCount, 1); @@ -619,10 +625,10 @@ test("getProviderCredentials least-used prefers accounts that were never used", name: "least-used-never", priority: 9, }); - await providersDb.updateProviderConnection(recentlyUsed.id, { + await providersDb.updateProviderConnection((recentlyUsed as any).id, { lastUsedAt: new Date().toISOString(), }); - await providersDb.updateProviderConnection(neverUsed.id, { + await providersDb.updateProviderConnection((neverUsed as any).id, { lastUsedAt: null, }); @@ -643,10 +649,10 @@ test("getProviderCredentials least-used prefers the oldest timestamp when all ac priority: 1, }); - await providersDb.updateProviderConnection(oldest.id, { + await providersDb.updateProviderConnection((oldest as any).id, { lastUsedAt: new Date(Date.now() - 120_000).toISOString(), }); - await providersDb.updateProviderConnection(newest.id, { + await providersDb.updateProviderConnection((newest as any).id, { lastUsedAt: new Date().toISOString(), }); @@ -698,10 +704,10 @@ test("getProviderCredentials p2c prefers the account with more quota headroom ov }, }); - quotaCache.setQuotaCache(nearLimit.id, "openai", { + (quotaCache as any).setQuotaCache(nearLimit.id, "openai", { daily: { remainingPercentage: 12, resetAt: futureIso(180_000) }, }); - quotaCache.setQuotaCache(healthy.id, "openai", { + (quotaCache as any).setQuotaCache(healthy.id, "openai", { daily: { remainingPercentage: 78, resetAt: futureIso(180_000) }, }); @@ -782,13 +788,13 @@ test("markAccountUnavailable keeps local 404 failures model-scoped with the loca }); const result = await auth.markAccountUnavailable( - connection.id, + (connection as any).id, 404, "model not found", "openai", "local-model" ); - const updated = await providersDb.getProviderConnectionById(connection.id); + const updated = await (providersDb as any).getProviderConnectionById(connection.id); assert.equal(result.shouldFallback, true); assert.equal(result.cooldownMs, COOLDOWN_MS.notFoundLocal); @@ -804,14 +810,14 @@ test("markAccountUnavailable applies a model-only lockout for Gemini 429 respons }); const result = await auth.markAccountUnavailable( - connection.id, + (connection as any).id, 429, "too many requests", "gemini", "gemini-2.5-pro" ); await flushWrites(); - const updated = await providersDb.getProviderConnectionById(connection.id); + const updated = await providersDb.getProviderConnectionById((connection as any).id); assert.equal(result.shouldFallback, true); assert.ok(result.cooldownMs > 0); @@ -827,14 +833,14 @@ test("markAccountUnavailable applies a model-only lockout for compatible provide }); const result = await auth.markAccountUnavailable( - connection.id, + (connection as any).id, 429, "The upstream compatible service exhausted its capacity", "openai-compatible-custom-node", "custom-model-a" ); await flushWrites(); - const updated = await providersDb.getProviderConnectionById(connection.id); + const updated = await providersDb.getProviderConnectionById((connection as any).id); assert.equal(result.shouldFallback, true); assert.ok(result.cooldownMs > 0); @@ -862,7 +868,7 @@ test("markAccountUnavailable uses the unified configured api-key connection cool }); const result = await auth.markAccountUnavailable( - connection.id, + (connection as any).id, 429, "too many requests", "openai", @@ -884,20 +890,20 @@ test("markAccountUnavailable stores Codex scope-specific cooldowns without a glo }); const result = await auth.markAccountUnavailable( - connection.id, + (connection as any).id, 429, "quota reached", "codex", "codex-spark-mini" ); - const updated = await providersDb.getProviderConnectionById(connection.id); + const updated = await providersDb.getProviderConnectionById((connection as any).id); const selected = await auth.getProviderCredentials("codex", null, null, "codex-spark-mini"); assert.equal(result.shouldFallback, true); assert.ok(result.cooldownMs > 0); assert.equal(updated.testStatus, "unavailable"); assert.equal(updated.rateLimitedUntil, undefined); - assert.ok(updated.providerSpecificData.codexScopeRateLimitedUntil.spark); + assert.ok((updated.providerSpecificData as any).codexScopeRateLimitedUntil.spark); assert.equal(selected.allRateLimited, true); }); @@ -907,13 +913,13 @@ test("markAccountUnavailable returns without fallback on bad requests", async () }); const result = await auth.markAccountUnavailable( - connection.id, + (connection as any).id, 400, "schema mismatch", "openai", "gpt-4o" ); - const updated = await providersDb.getProviderConnectionById(connection.id); + const updated = await providersDb.getProviderConnectionById((connection as any).id); assert.deepEqual(result, { shouldFallback: false, cooldownMs: 0 }); assert.equal(updated.testStatus, "active"); @@ -927,8 +933,13 @@ test("markAccountUnavailable preserves terminal statuses without overwriting the rateLimitedUntil: null, }); - const result = await auth.markAccountUnavailable(connection.id, 503, "upstream error", "openai"); - const updated = await providersDb.getProviderConnectionById(connection.id); + const result = await auth.markAccountUnavailable( + (connection as any).id, + 503, + "upstream error", + "openai" + ); + const updated = await providersDb.getProviderConnectionById((connection as any).id); assert.equal(result.shouldFallback, true); assert.equal(result.cooldownMs, 0); @@ -943,8 +954,13 @@ test("markAccountUnavailable reuses an existing connection-wide cooldown", async rateLimitedUntil: retryAfter, }); - const result = await auth.markAccountUnavailable(connection.id, 503, "upstream error", "openai"); - const updated = await providersDb.getProviderConnectionById(connection.id); + const result = await auth.markAccountUnavailable( + (connection as any).id, + 503, + "upstream error", + "openai" + ); + const updated = await providersDb.getProviderConnectionById((connection as any).id); assert.equal(result.shouldFallback, true); assert.ok(result.cooldownMs > 0); @@ -968,18 +984,21 @@ test("markAccountUnavailable reuses an existing Codex scope cooldown", async () }); const result = await auth.markAccountUnavailable( - connection.id, + (connection as any).id, 429, "quota reached", "codex", "codex-spark-mini" ); - const updated = await providersDb.getProviderConnectionById(connection.id); + const updated = await providersDb.getProviderConnectionById((connection as any).id); assert.equal(result.shouldFallback, true); assert.ok(result.cooldownMs > 0); assert.equal(updated.rateLimitedUntil, undefined); - assert.equal(updated.providerSpecificData.codexScopeRateLimitedUntil.spark, retryAfter); + (assert as any).equal( + (updated.providerSpecificData as any).codexScopeRateLimitedUntil.spark, + retryAfter + ); }); test("markAccountUnavailable uses a connection-wide cooldown for non-local 404 errors", async () => { @@ -991,13 +1010,13 @@ test("markAccountUnavailable uses a connection-wide cooldown for non-local 404 e }); const result = await auth.markAccountUnavailable( - connection.id, + (connection as any).id, 404, "model not found", "openai", "gpt-missing" ); - const updated = await providersDb.getProviderConnectionById(connection.id); + const updated = await providersDb.getProviderConnectionById((connection as any).id); assert.equal(result.shouldFallback, true); assert.ok(result.cooldownMs > 0); @@ -1012,13 +1031,13 @@ test("markAccountUnavailable auto-disables permanently banned accounts when the }); const result = await auth.markAccountUnavailable( - connection.id, + (connection as any).id, 401, "Verify your account to continue", "openai", "gpt-4o" ); - const updated = await providersDb.getProviderConnectionById(connection.id); + const updated = await providersDb.getProviderConnectionById((connection as any).id); assert.equal(result.shouldFallback, true); assert.equal(updated.isActive, false); @@ -1032,13 +1051,13 @@ test("markAccountUnavailable leaves permanently banned accounts active when auto }); const result = await auth.markAccountUnavailable( - connection.id, + (connection as any).id, 401, "Verify your account to continue", "openai", "gpt-4o" ); - const updated = await providersDb.getProviderConnectionById(connection.id); + const updated = await providersDb.getProviderConnectionById((connection as any).id); assert.equal(result.shouldFallback, true); assert.equal(updated.isActive, true); @@ -1076,13 +1095,13 @@ test("markAccountUnavailable swallows auto-disable persistence errors", async () try { const result = await auth.markAccountUnavailable( - connection.id, + (connection as any).id, 401, "Verify your account to continue", "openai", "gpt-4o" ); - const updated = await providersDb.getProviderConnectionById(connection.id); + const updated = await providersDb.getProviderConnectionById((connection as any).id); assert.equal(result.shouldFallback, true); assert.equal(updated.isActive, true); diff --git a/tests/unit/sse-parser.test.ts b/tests/unit/sse-parser.test.ts index ed2e91b028..82b4fd655f 100644 --- a/tests/unit/sse-parser.test.ts +++ b/tests/unit/sse-parser.test.ts @@ -103,12 +103,12 @@ test("parseSSEToClaudeResponse parses text, thinking, tool_use, and usage events assert.equal(parsed.id, "msg_1"); assert.equal(parsed.model, "claude-3-5-sonnet"); - assert.equal(parsed.content[0].type, "thinking"); - assert.equal(parsed.content[0].thinking, "step 1"); - assert.equal(parsed.content[0].signature, "sig-1"); - assert.equal(parsed.content[1].text, "Hello"); - assert.equal(parsed.content[2].type, "tool_use"); - assert.deepEqual(parsed.content[2].input, { q: "docs" }); + assert.equal((parsed.content[0] as any).type, "thinking"); + assert.equal((parsed as any).content[0].thinking, "step 1"); + assert.equal((parsed as any).content[0].signature, "sig-1"); + (assert as any).equal((parsed.content[1] as any).text, "Hello"); + assert.equal((parsed.content[2] as any).type, "tool_use"); + (assert as any).deepEqual((parsed.content[2] as any).input, { q: "docs" }); assert.equal(parsed.stop_reason, "tool_use"); assert.equal(parsed.stop_sequence, "END"); assert.deepEqual(parsed.usage, { input_tokens: 10, output_tokens: 4 }); diff --git a/tests/unit/stream-utils.test.ts b/tests/unit/stream-utils.test.ts index 51b5098b27..9117d4fca8 100644 --- a/tests/unit/stream-utils.test.ts +++ b/tests/unit/stream-utils.test.ts @@ -281,11 +281,11 @@ test("buildStreamSummaryFromEvents falls back to response.output_text.delta when "gpt-5.4" ); - assert.equal(summary.object, "response"); - assert.equal(summary.output[0].type, "message"); - assert.equal(summary.output[0].content[0].type, "output_text"); - assert.equal(summary.output[0].content[0].text, "Hello world"); - assert.equal(summary.usage.output_tokens, 2); + assert.equal((summary as any).object, "response"); + assert.equal((summary as any).output[0].type, "message"); + assert.equal((summary as any).output[0].content[0].type, "output_text"); + assert.equal((summary as any).output[0].content[0].text, "Hello world"); + assert.equal((summary as any).usage.output_tokens, 2); }); test("createSSEStream translate mode aborts on Responses failure with rate limit error", async () => { @@ -678,10 +678,10 @@ test("buildStreamSummaryFromEvents compacts Responses API deltas into a syntheti "gpt-4.1-mini" ); - assert.equal(summary.object, "response"); - assert.equal(summary.model, "gpt-4.1-mini"); - assert.equal(summary.output[0].content[0].text, "Hello world"); - assert.deepEqual(summary.usage, { input_tokens: 2, output_tokens: 3, total_tokens: 5 }); + assert.equal((summary as any).object, "response"); + assert.equal((summary as any).model, "gpt-4.1-mini"); + assert.equal((summary as any).output[0].content[0].text, "Hello world"); + assert.deepEqual((summary as any).usage, { input_tokens: 2, output_tokens: 3, total_tokens: 5 }); }); test("buildStreamSummaryFromEvents preserves Gemini thought parts and function calls", () => { @@ -731,13 +731,13 @@ test("buildStreamSummaryFromEvents preserves Gemini thought parts and function c "gemini-2.5-pro" ); - assert.equal(summary.modelVersion, "gemini-2.5-pro"); - assert.equal(summary.candidates[0].content.parts[0].text, "Thinking aloud"); - assert.equal(summary.candidates[0].content.parts[0].thought, true); - assert.deepEqual(summary.candidates[0].content.parts[2], { + assert.equal((summary as any).modelVersion, "gemini-2.5-pro"); + assert.equal((summary as any).candidates[0].content.parts[0].text, "Thinking aloud"); + assert.equal((summary as any).candidates[0].content.parts[0].thought, true); + assert.deepEqual((summary as any).candidates[0].content.parts[2], { functionCall: { name: "read_file", args: { path: "/tmp/a" } }, }); - assert.deepEqual(summary.usageMetadata, { + assert.deepEqual((summary as any).usageMetadata, { promptTokenCount: 4, candidatesTokenCount: 5, totalTokenCount: 9, diff --git a/tests/unit/sync-bundle.test.ts b/tests/unit/sync-bundle.test.ts index c60a38b96c..3febe02d58 100644 --- a/tests/unit/sync-bundle.test.ts +++ b/tests/unit/sync-bundle.test.ts @@ -82,7 +82,7 @@ test("config sync bundle is deterministic, strips auth settings, and ignores vol assert.equal(first.bundle.providerConnections[0].apiKey, "sk-live-secret"); assert.equal(first.bundle.modelAliases["smart-default"], "openai/gpt-4o-mini"); - await providersDb.updateProviderConnection(connection.id, { + await providersDb.updateProviderConnection((connection as any).id, { lastError: "temporary upstream failure", lastErrorAt: "2026-04-14T12:00:00.000Z", rateLimitedUntil: "2026-04-14T12:30:00.000Z", @@ -91,7 +91,7 @@ test("config sync bundle is deterministic, strips auth settings, and ignores vol const afterVolatileChange = await syncBundle.buildConfigSyncEnvelope(); assert.equal(afterVolatileChange.version, first.version); - await providersDb.updateProviderConnection(connection.id, { + await providersDb.updateProviderConnection((connection as any).id, { defaultModel: "gpt-4.1-mini", }); diff --git a/tests/unit/sync-routes.test.ts b/tests/unit/sync-routes.test.ts index 3bc1f09efa..c12c0756ca 100644 --- a/tests/unit/sync-routes.test.ts +++ b/tests/unit/sync-routes.test.ts @@ -86,7 +86,7 @@ test("sync token routes issue, list, use and revoke dedicated tokens", async () ); assert.equal(createResponse.status, 201); - const createdBody = await createResponse.json(); + const createdBody = (await createResponse.json()) as any; assert.match(createdBody.token, /^osync_/); assert.equal(createdBody.syncToken.name, "Desktop client"); assert.equal(createdBody.syncToken.syncApiKeyId, managementKey.id); @@ -97,7 +97,7 @@ test("sync token routes issue, list, use and revoke dedicated tokens", async () }) ); assert.equal(listResponse.status, 200); - const listed = await listResponse.json(); + const listed = (await listResponse.json()) as any; assert.equal(listed.total, 1); assert.equal(listed.tokens[0].name, "Desktop client"); assert.equal(listed.tokens[0].lastUsedAt, null); @@ -113,7 +113,7 @@ test("sync token routes issue, list, use and revoke dedicated tokens", async () assert.equal(bundleResponse.status, 200); assert.match(bundleResponse.headers.get("etag") || "", /^"[a-f0-9]{64}"$/); assert.match(bundleResponse.headers.get("x-config-version") || "", /^[a-f0-9]{64}$/); - const bundlePayload = await bundleResponse.json(); + const bundlePayload = (await bundleResponse.json()) as any; assert.equal(bundlePayload.version, bundleResponse.headers.get("x-config-version")); assert.equal(typeof bundlePayload.bundle, "object"); @@ -122,7 +122,7 @@ test("sync token routes issue, list, use and revoke dedicated tokens", async () token: managementKey.key, }) ); - const secondListBody = await secondListResponse.json(); + const secondListBody = (await secondListResponse.json()) as any; assert.equal(typeof secondListBody.tokens[0].lastUsedAt, "string"); const notModifiedResponse = await syncBundleRoute.GET( @@ -151,7 +151,7 @@ test("sync token routes issue, list, use and revoke dedicated tokens", async () ); assert.equal(revokeResponse.status, 200); - const revokeBody = await revokeResponse.json(); + const revokeBody = (await revokeResponse.json()) as any; assert.equal(typeof revokeBody.syncToken.revokedAt, "string"); const revokedBundleResponse = await syncBundleRoute.GET( diff --git a/tests/unit/t14-proxy-fast-fail.test.ts b/tests/unit/t14-proxy-fast-fail.test.ts index 20a8b11327..8de0f643d3 100644 --- a/tests/unit/t14-proxy-fast-fail.test.ts +++ b/tests/unit/t14-proxy-fast-fail.test.ts @@ -28,7 +28,7 @@ test("T14: runWithProxyContext fast-fails when proxy is unreachable", async () = executed = true; return "ok"; }), - (err) => err?.code === "PROXY_UNREACHABLE" + (err) => (err as any).code === "PROXY_UNREACHABLE" ); assert.equal(executed, false); diff --git a/tests/unit/t19-codex-responses-empty-content.test.ts b/tests/unit/t19-codex-responses-empty-content.test.ts index cc488073c8..008f1bd7b2 100644 --- a/tests/unit/t19-codex-responses-empty-content.test.ts +++ b/tests/unit/t19-codex-responses-empty-content.test.ts @@ -34,7 +34,7 @@ test("T19: picks the last non-empty message content from Responses output", () = FORMATS.OPENAI ); - assert.equal(translated.choices[0].message.content, "Resposta final"); + assert.equal((translated as any).choices[0].message.content, "Resposta final"); }); test("T19: falls back to last message block when all message texts are empty", () => { @@ -61,6 +61,6 @@ test("T19: falls back to last message block when all message texts are empty", ( FORMATS.OPENAI ); - assert.equal(translated.choices[0].message.content, ""); - assert.equal(translated.choices[0].finish_reason, "stop"); + assert.equal((translated as any).choices[0].message.content, ""); + (assert as any).equal((translated as any).choices[0].finish_reason, "stop"); }); diff --git a/tests/unit/t23-t24-fallback-resilience.test.ts b/tests/unit/t23-t24-fallback-resilience.test.ts index db64ac4289..d72a0b275f 100644 --- a/tests/unit/t23-t24-fallback-resilience.test.ts +++ b/tests/unit/t23-t24-fallback-resilience.test.ts @@ -137,7 +137,7 @@ test("T24: all inactive accounts return 503 service_unavailable (not 406)", asyn }); assert.equal(result.status, 503); - const body = await result.json(); + const body = (await result.json()) as any; assert.equal(body.error?.code, "ALL_ACCOUNTS_INACTIVE"); }); diff --git a/tests/unit/tag-routing.test.ts b/tests/unit/tag-routing.test.ts index 3d8995c3a9..c87f929dc7 100644 --- a/tests/unit/tag-routing.test.ts +++ b/tests/unit/tag-routing.test.ts @@ -106,7 +106,7 @@ test("handleComboChat filters priority targets by metadata.tags using any-match log: createLog(), }); - const payload = await response.json(); + const payload = (await response.json()) as any; assert.equal(response.status, 200); assert.equal(attempts.length, 1); assert.equal(attempts[0].model, "fireworks/gpt-4o-mini"); diff --git a/tests/unit/telemetry-summary-route.test.ts b/tests/unit/telemetry-summary-route.test.ts index ce50cc3e9f..1fc8d58ded 100644 --- a/tests/unit/telemetry-summary-route.test.ts +++ b/tests/unit/telemetry-summary-route.test.ts @@ -25,7 +25,7 @@ test("telemetry summary route includes totalRequests alias plus session/quota mo const response = await GET( new Request("http://localhost:20128/api/telemetry/summary?windowMs=600000") ); - const payload = await response.json(); + const payload = (await response.json()) as any; assert.equal(response.status, 200); assert.ok(payload.totalRequests >= 1); diff --git a/tests/unit/token-health-check.test.ts b/tests/unit/token-health-check.test.ts index 8cc9bea5e6..8f7cb92fcd 100644 --- a/tests/unit/token-health-check.test.ts +++ b/tests/unit/token-health-check.test.ts @@ -26,7 +26,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 { @@ -251,7 +251,7 @@ test("checkConnection uses the resolved proxy payload when refreshing tokens", a isActive: true, }); - await settingsDb.setProxyForLevel("key", connection.id, { + await settingsDb.setProxyForLevel("key", (connection as any).id, { type: "http", host: proxy.host, port: proxy.port, @@ -259,7 +259,7 @@ test("checkConnection uses the resolved proxy payload when refreshing tokens", a await tokenHealthCheck.checkConnection(connection); - const updated = await providersDb.getProviderConnectionById(connection.id); + const updated = await providersDb.getProviderConnectionById((connection as any).id); assert.equal(refreshRequests.length, 1); assert.equal(refreshRequests[0].method, "POST"); @@ -325,14 +325,14 @@ test("checkConnection uses the latest stored refresh token instead of a stale sw }); const staleCheckTime = new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(); - await providersDb.updateProviderConnection(connection.id, { + await providersDb.updateProviderConnection((connection as any).id, { refreshToken: "snapshot-refresh-current", lastHealthCheckAt: staleCheckTime, }); await tokenHealthCheck.checkConnection(connection); - const updated = await providersDb.getProviderConnectionById(connection.id); + const updated = await providersDb.getProviderConnectionById((connection as any).id); assert.equal(refreshRequests.length, 1); assert.match(refreshRequests[0], /refresh_token=snapshot-refresh-current/); assert.equal(updated?.refreshToken, "snapshot-refresh-next"); @@ -382,13 +382,13 @@ test("checkConnection skips interval refresh when token expiry is known and stil }); const staleCheckTime = new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(); - await providersDb.updateProviderConnection(connection.id, { + await providersDb.updateProviderConnection((connection as any).id, { lastHealthCheckAt: staleCheckTime, }); await tokenHealthCheck.checkConnection(connection); - const updated = await providersDb.getProviderConnectionById(connection.id); + const updated = await providersDb.getProviderConnectionById((connection as any).id); assert.equal(refreshCount, 0); assert.equal(updated?.accessToken, "known-expiry-access"); assert.equal(updated?.refreshToken, "known-expiry-refresh"); diff --git a/tests/unit/token-refresh-route-service.test.ts b/tests/unit/token-refresh-route-service.test.ts index 7780b318fe..546f9ca56a 100644 --- a/tests/unit/token-refresh-route-service.test.ts +++ b/tests/unit/token-refresh-route-service.test.ts @@ -186,13 +186,13 @@ test("updateProviderCredentials persists rotated tokens and returns false for mi refreshToken: "refresh-old", }); - const updated = await tokenRefresh.updateProviderCredentials(connection.id, { + const updated = await tokenRefresh.updateProviderCredentials((connection as any).id, { accessToken: "access-new", refreshToken: "refresh-new", expiresIn: 600, providerSpecificData: { tenant: "team-a" }, }); - const stored = await providersDb.getProviderConnectionById(connection.id); + const stored = await providersDb.getProviderConnectionById((connection as any).id); const missing = await tokenRefresh.updateProviderCredentials("missing", { accessToken: "nope" }); assert.equal(updated, true); @@ -232,7 +232,7 @@ test("checkAndRefreshToken refreshes expiring OAuth access tokens and updates th ...connection, connectionId: connection.id, }); - const stored = await providersDb.getProviderConnectionById(connection.id); + const stored = await providersDb.getProviderConnectionById((connection as any).id); assert.equal(refreshed.accessToken, "claude-access-fresh"); assert.equal(refreshed.refreshToken, "claude-refresh-fresh"); @@ -275,12 +275,12 @@ test("checkAndRefreshToken refreshes expiring GitHub copilot tokens and syncs th ...connection, connectionId: connection.id, }); - const stored = await providersDb.getProviderConnectionById(connection.id); + const stored = await providersDb.getProviderConnectionById((connection as any).id); assert.equal(refreshed.copilotToken, "copilot-fresh"); assert.equal(refreshed.providerSpecificData.copilotToken, "copilot-fresh"); - assert.equal(stored.providerSpecificData.copilotToken, "copilot-fresh"); - assert.equal(stored.providerSpecificData.copilotTokenExpiresAt, 1_700_001_000); + assert.equal((stored as any).providerSpecificData.copilotToken, "copilot-fresh"); + assert.equal((stored as any).providerSpecificData.copilotTokenExpiresAt, 1_700_001_000); } ); }); diff --git a/tests/unit/tool-request-sanitization.test.ts b/tests/unit/tool-request-sanitization.test.ts index 47e435bbe3..08e458b7b1 100644 --- a/tests/unit/tool-request-sanitization.test.ts +++ b/tests/unit/tool-request-sanitization.test.ts @@ -25,10 +25,10 @@ test("tool sanitization: coerces numeric JSON Schema fields recursively", () => }; const result = coerceSchemaNumericFields(schema); - assert.equal(result.properties.count.minimum, 1); - assert.equal(result.properties.count.maximum, 10); - assert.equal(result.properties.items.minItems, 2); - assert.equal(result.properties.items.items.minLength, 3); + assert.equal((result as any).properties.count.minimum, 1); + (assert as any).equal((result as any).properties.count.maximum, 10); + (assert as any).equal((result as any).properties.items.minItems, 2); + assert.equal((result as any).properties.items.items.minLength, 3); }); test("tool sanitization: preserves non-numeric JSON Schema strings", () => { @@ -40,7 +40,7 @@ test("tool sanitization: preserves non-numeric JSON Schema strings", () => { }; const result = coerceSchemaNumericFields(schema); - assert.equal(result.properties.value.minimum, "abc"); + assert.equal((result as any).properties.value.minimum, "abc"); }); test("tool sanitization: normalizes descriptions across OpenAI, Claude, and Gemini shapes", () => { @@ -57,9 +57,9 @@ test("tool sanitization: normalizes descriptions across OpenAI, Claude, and Gemi functionDeclarations: [{ name: "sum", description: false, parameters: {} }], }); - assert.equal(openAITool.function.description, ""); - assert.equal(claudeTool.description, "42"); - assert.equal(geminiTool.functionDeclarations[0].description, "false"); + (assert as any).equal(((openAITool as any).function as any).description, ""); + assert.equal((claudeTool as any).description, "42"); + assert.equal((geminiTool as any).functionDeclarations[0].description, "false"); }); test("tool sanitization: coerces schemas and descriptions in tool arrays", () => { diff --git a/tests/unit/translator-antigravity-to-openai.test.ts b/tests/unit/translator-antigravity-to-openai.test.ts index 0473ebaa26..0886c131be 100644 --- a/tests/unit/translator-antigravity-to-openai.test.ts +++ b/tests/unit/translator-antigravity-to-openai.test.ts @@ -182,7 +182,7 @@ test("Antigravity -> OpenAI lowers schema types recursively", () => { false ); - assert.deepEqual(result.tools[0].function.parameters, { + assert.deepEqual((result.tools[0].function as any).parameters, { type: "object", properties: { items: { diff --git a/tests/unit/translator-claude-to-openai.test.ts b/tests/unit/translator-claude-to-openai.test.ts index febcfae8b1..f9f8fb3c99 100644 --- a/tests/unit/translator-claude-to-openai.test.ts +++ b/tests/unit/translator-claude-to-openai.test.ts @@ -37,7 +37,7 @@ test("Claude -> OpenAI maps system blocks, parameters, tool declarations and too role: "user", content: "Hello", }); - assert.equal(result.tools.length, 1); + assert.equal((result.tools as any).length, 1); assert.deepEqual(result.tools[0], { type: "function", function: { diff --git a/tests/unit/translator-helper-branches.test.ts b/tests/unit/translator-helper-branches.test.ts index 33a59a0302..9f80646b07 100644 --- a/tests/unit/translator-helper-branches.test.ts +++ b/tests/unit/translator-helper-branches.test.ts @@ -47,24 +47,24 @@ test("schemaCoercion recursively coerces schema numeric fields across object var else: { minItems: "18" }, }); - assert.equal(result.minimum, 1); - assert.equal(result.maxItems, 5); - assert.equal(result.properties.nested.minLength, 2); - assert.equal(result.properties.nested.items.maximum, 7); - assert.equal(result.patternProperties["^x-"].minProperties, 1); - assert.equal(result.definitions.one.exclusiveMaximum, 9); - assert.equal(result.$defs.two.minItems, 3); - assert.equal(result.dependentSchemas.dep.maxProperties, 4); - assert.equal(result.additionalProperties.maximum, 8); - assert.equal(result.unevaluatedProperties.minimum, 0); - assert.equal(result.prefixItems[0].minimum, 11); - assert.equal(result.anyOf[0].maximum, 12); - assert.equal(result.oneOf[0].minimum, 13); - assert.equal(result.allOf[0].maxLength, 14); - assert.equal(result.not.minimum, 15); - assert.equal(result.if.minimum, 16); - assert.equal(result.then.maximum, 17); - assert.equal(result.else.minItems, 18); + assert.equal((result as any).minimum, 1); + (assert as any).equal((result as any).maxItems, 5); + (assert as any).equal((result as any).properties.nested.minLength, 2); + assert.equal((result as any).properties.nested.items.maximum, 7); + assert.equal((result as any).patternProperties["^x-"].minProperties, 1); + assert.equal((result as any).definitions.one.exclusiveMaximum, 9); + assert.equal((result as any).$defs.two.minItems, 3); + assert.equal((result as any).dependentSchemas.dep.maxProperties, 4); + assert.equal((result as any).additionalProperties.maximum, 8); + assert.equal((result as any).unevaluatedProperties.minimum, 0); + assert.equal((result as any).prefixItems[0].minimum, 11); + assert.equal((result as any).anyOf[0].maximum, 12); + assert.equal((result as any).oneOf[0].minimum, 13); + assert.equal((result as any).allOf[0].maxLength, 14); + assert.equal((result as any).not.minimum, 15); + assert.equal((result as any).if.minimum, 16); + assert.equal((result as any).then.maximum, 17); + assert.equal((result as any).else.minItems, 18); assert.equal(schemaCoercion.coerceSchemaNumericFields("unchanged"), "unchanged"); assert.deepEqual(schemaCoercion.coerceSchemaNumericFields(["2", { minimum: "3" }]), [ @@ -78,19 +78,19 @@ test("schemaCoercion sanitizes descriptions, tool schemas, tool ids and deepseek type: "function", function: { name: "weather", description: 42 }, }); - assert.equal(sanitizedOpenAI.function.description, "42"); + (assert as any).equal((sanitizedOpenAI as any).function.description, "42"); const sanitizedClaude = schemaCoercion.sanitizeToolDescription({ name: "weather", description: null, }); - assert.equal(sanitizedClaude.description, ""); + assert.equal((sanitizedClaude as any).description, ""); const sanitizedGemini = schemaCoercion.sanitizeToolDescription({ functionDeclarations: [{ name: "one", description: 12 }, { name: "two" }], }); - assert.equal(sanitizedGemini.functionDeclarations[0].description, "12"); - assert.equal(sanitizedGemini.functionDeclarations[1].name, "two"); + assert.equal((sanitizedGemini as any).functionDeclarations[0].description, "12"); + assert.equal((sanitizedGemini as any).functionDeclarations[1].name, "two"); assert.equal(schemaCoercion.sanitizeToolDescription("plain"), "plain"); const coercedTools = schemaCoercion.coerceToolSchemas([ diff --git a/tests/unit/translator-openai-responses-req.test.ts b/tests/unit/translator-openai-responses-req.test.ts index bf907e7e19..118a3a4fd7 100644 --- a/tests/unit/translator-openai-responses-req.test.ts +++ b/tests/unit/translator-openai-responses-req.test.ts @@ -45,7 +45,7 @@ test("Responses -> Chat converts instructions, inputs, function calls, outputs, null ); - assert.deepEqual(result.messages, [ + assert.deepEqual((result as any).messages, [ { role: "system", content: "Rules" }, { role: "user", @@ -68,7 +68,7 @@ test("Responses -> Chat converts instructions, inputs, function calls, outputs, }, { role: "tool", tool_call_id: "call_1", content: '{"ok":true}' }, ]); - assert.deepEqual(result.tools, [ + (assert as any).deepEqual((result as any).tools, [ { type: "function", function: { @@ -79,7 +79,10 @@ test("Responses -> Chat converts instructions, inputs, function calls, outputs, }, }, ]); - assert.deepEqual(result.tool_choice, { type: "function", function: { name: "read_file" } }); + (assert as any).deepEqual((result as any).tool_choice, { + type: "function", + function: { name: "read_file" }, + }); }); test("Responses -> Chat filters orphan tool outputs and supports role-based message items", () => { @@ -97,10 +100,10 @@ test("Responses -> Chat filters orphan tool outputs and supports role-based mess null ); - assert.equal(result.messages.length, 3); - assert.equal(result.messages[0].role, "user"); - assert.equal(result.messages[1].tool_calls[0].id, "call_2"); - assert.deepEqual(result.messages[2], { + assert.equal((result as any).messages.length, 3); + assert.equal((result as any).messages[0].role, "user"); + assert.equal((result as any).messages[1].tool_calls[0].id, "call_2"); + (assert as any).deepEqual((result as any).messages[2], { role: "tool", tool_call_id: "call_2", content: "found", @@ -119,7 +122,7 @@ test("Responses -> Chat rejects unsupported built-in tools and background mode", false, null ), - (error) => error.statusCode === 400 && error.errorType === "unsupported_feature" + (error: any) => error.statusCode === 400 && error.errorType === "unsupported_feature" ); assert.throws( @@ -133,7 +136,7 @@ test("Responses -> Chat rejects unsupported built-in tools and background mode", false, null ), - (error) => error.statusCode === 400 && error.errorType === "unsupported_feature" + (error: any) => error.statusCode === 400 && error.errorType === "unsupported_feature" ); }); @@ -188,11 +191,11 @@ test("Chat -> Responses converts messages, tool calls, tool outputs, tools and p null ); - assert.equal(result.instructions, "Rules"); - assert.equal(result.stream, true); - assert.equal(result.store, false); - assert.equal(result.previous_response_id, "resp_prev_123"); - assert.deepEqual(result.input, [ + assert.equal((result as any).instructions, "Rules"); + assert.equal((result as any).stream, true); + assert.equal((result as any).store, false); + assert.equal((result as any).previous_response_id, "resp_prev_123"); + assert.deepEqual((result as any).input, [ { type: "message", role: "user", @@ -219,7 +222,7 @@ test("Chat -> Responses converts messages, tool calls, tool outputs, tools and p output: [{ type: "input_text", text: "ok" }], }, ]); - assert.deepEqual(result.tools, [ + assert.deepEqual((result as any).tools, [ { type: "function", name: "read_file", @@ -228,10 +231,10 @@ test("Chat -> Responses converts messages, tool calls, tool outputs, tools and p strict: true, }, ]); - assert.deepEqual(result.tool_choice, { type: "function", name: "read_file" }); - assert.equal(result.temperature, 0.2); - assert.equal(result.max_output_tokens, 100); - assert.equal(result.top_p, 0.9); + assert.deepEqual((result as any).tool_choice, { type: "function", name: "read_file" }); + assert.equal((result as any).temperature, 0.2); + assert.equal((result as any).max_output_tokens, 100); + assert.equal((result as any).top_p, 0.9); }); test("Responses round-trip preserves store and previous_response_id when opt-in is enabled", () => { @@ -255,9 +258,9 @@ test("Responses round-trip preserves store and previous_response_id when opt-in const result = openaiToOpenAIResponsesRequest("gpt-4o", chatBody, false, credentials); - assert.equal(result.previous_response_id, "resp_prev_store"); - assert.equal(result.store, true); - assert.equal(result.instructions, "Rules"); + assert.equal((result as any).previous_response_id, "resp_prev_store"); + assert.equal((result as any).store, true); + assert.equal((result as any).instructions, "Rules"); }); test("Chat -> Responses preserves prompt_cache_key and session affinity fields", () => { @@ -273,10 +276,10 @@ test("Chat -> Responses preserves prompt_cache_key and session affinity fields", { providerSpecificData: { openaiStoreEnabled: true } } ); - assert.equal(result.prompt_cache_key, "cache-key-1"); - assert.equal(result.session_id, "omniroute-session-abc"); - assert.equal(result.conversation_id, "conv-123"); - assert.equal(result.store, undefined); + (assert as any).equal((result as any).prompt_cache_key, "cache-key-1"); + (assert as any).equal((result as any).session_id, "omniroute-session-abc"); + assert.equal((result as any).conversation_id, "conv-123"); + assert.equal((result as any).store, undefined); }); test("Chat -> Responses preserves explicit reasoning objects", () => { @@ -290,8 +293,8 @@ test("Chat -> Responses preserves explicit reasoning objects", () => { null ); - assert.deepEqual(result.reasoning, { effort: "low" }); - assert.equal(result.store, false); + assert.deepEqual((result as any).reasoning, { effort: "low" }); + assert.equal((result as any).store, false); }); test("Chat -> Responses maps reasoning_effort into Responses reasoning", () => { @@ -305,9 +308,9 @@ test("Chat -> Responses maps reasoning_effort into Responses reasoning", () => { null ); - assert.deepEqual(result.reasoning, { effort: "low" }); - assert.equal(result.reasoning_effort, undefined); - assert.equal(result.store, false); + assert.deepEqual((result as any).reasoning, { effort: "low" }); + assert.equal((result as any).reasoning_effort, undefined); + assert.equal((result as any).store, false); }); test("Chat -> Responses filters orphan function_call_output items and leaves empty instructions when absent", () => { @@ -334,13 +337,19 @@ test("Chat -> Responses filters orphan function_call_output items and leaves emp null ); - assert.equal(result.instructions, ""); + assert.equal((result as any).instructions, ""); assert.equal( - result.input.some((item) => item.call_id === "orphan"), + (result as any).input.some((item) => item.call_id === "orphan"), false ); - assert.equal(result.input.filter((item) => item.type === "function_call_output").length, 1); - assert.equal(result.input.find((item) => item.type === "function_call_output").call_id, "call_2"); + assert.equal( + (result as any).input.filter((item) => item.type === "function_call_output").length, + 1 + ); + assert.equal( + (result as any).input.find((item) => item.type === "function_call_output").call_id, + "call_2" + ); }); test("Chat -> Responses maps max_completion_tokens to max_output_tokens", () => { @@ -354,9 +363,9 @@ test("Chat -> Responses maps max_completion_tokens to max_output_tokens", () => null ); - assert.equal(result.max_output_tokens, 2048); - assert.equal(result.max_tokens, undefined); - assert.equal(result.max_completion_tokens, undefined); + (assert as any).equal((result as any).max_output_tokens, 2048); + assert.equal((result as any).max_tokens, undefined); + assert.equal((result as any).max_completion_tokens, undefined); }); test("Chat -> Responses maps legacy max_tokens to max_output_tokens when max_completion_tokens is absent", () => { @@ -370,8 +379,8 @@ test("Chat -> Responses maps legacy max_tokens to max_output_tokens when max_com null ); - assert.equal(result.max_output_tokens, 512); - assert.equal(result.max_tokens, undefined); + assert.equal((result as any).max_output_tokens, 512); + assert.equal((result as any).max_tokens, undefined); }); test("Chat -> Responses prefers max_completion_tokens over max_tokens when both are present", () => { @@ -386,7 +395,7 @@ test("Chat -> Responses prefers max_completion_tokens over max_tokens when both null ); - assert.equal(result.max_output_tokens, 4096); - assert.equal(result.max_tokens, undefined); - assert.equal(result.max_completion_tokens, undefined); + (assert as any).equal((result as any).max_output_tokens, 4096); + assert.equal((result as any).max_tokens, undefined); + assert.equal((result as any).max_completion_tokens, undefined); }); diff --git a/tests/unit/translator-openai-to-kiro.test.ts b/tests/unit/translator-openai-to-kiro.test.ts index 73419eea08..9694128f13 100644 --- a/tests/unit/translator-openai-to-kiro.test.ts +++ b/tests/unit/translator-openai-to-kiro.test.ts @@ -96,7 +96,7 @@ test("OpenAI -> Kiro preserves prior history, tool uses and accumulated tool res }); const context = result.conversationState.currentMessage.userInputMessage.userInputMessageContext; - assert.equal(context.toolResults.length, 2); + assert.equal((context.toolResults as any).length, 2); assert.deepEqual(context.toolResults[0], { toolUseId: "call_1", status: "success", @@ -114,8 +114,14 @@ test("OpenAI -> Kiro derives a stable conversationId for the same first history const first = buildSamplePayload(); const second = buildSamplePayload(); - assert.equal(first.conversationState.history[0].userInputMessage.content, "Rules\n\nHello"); - assert.equal(second.conversationState.history[0].userInputMessage.content, "Rules\n\nHello"); + assert.equal( + (first.conversationState as any).history[0].userInputMessage.content, + "Rules\n\nHello" + ); + assert.equal( + (second as any).conversationState.history[0].userInputMessage.content, + "Rules\n\nHello" + ); assert.equal(first.conversationState.conversationId, second.conversationState.conversationId); }); diff --git a/tests/unit/translator-resp-claude-to-openai.test.ts b/tests/unit/translator-resp-claude-to-openai.test.ts index af7f873610..7d3b598dd4 100644 --- a/tests/unit/translator-resp-claude-to-openai.test.ts +++ b/tests/unit/translator-resp-claude-to-openai.test.ts @@ -40,18 +40,18 @@ test("Claude non-stream: text, thinking and tool_use become OpenAI assistant mes new Map([["proxy_read_file", "read_file"]]) ); - assert.equal(result.id, "chatcmpl-msg_123"); - assert.equal(result.model, "claude-3-7-sonnet"); - assert.equal(result.choices[0].message.content, "Final answer"); - assert.equal(result.choices[0].message.reasoning_content, "Plan first."); - assert.equal(result.choices[0].message.tool_calls[0].id, "tool_1"); - assert.equal(result.choices[0].message.tool_calls[0].function.name, "read_file"); - assert.equal( - result.choices[0].message.tool_calls[0].function.arguments, + assert.equal((result as any).id, "chatcmpl-msg_123"); + (assert as any).equal((result as any).model, "claude-3-7-sonnet"); + (assert as any).equal((result as any).choices[0].message.content, "Final answer"); + assert.equal((result as any).choices[0].message.reasoning_content, "Plan first."); + assert.equal((result as any).choices[0].message.tool_calls[0].id, "tool_1"); + assert.equal((result as any).choices[0].message.tool_calls[0].function.name, "read_file"); + (assert as any).equal( + (result as any).choices[0].message.tool_calls[0].function.arguments, JSON.stringify({ path: "/tmp/a" }) ); - assert.equal(result.choices[0].finish_reason, "tool_calls"); - assert.deepEqual(result.usage, { + assert.equal((result as any).choices[0].finish_reason, "tool_calls"); + assert.deepEqual((result as any).usage, { prompt_tokens: 10, completion_tokens: 4, total_tokens: 14, @@ -68,12 +68,12 @@ test("Claude non-stream: end_turn becomes stop and empty text is preserved", () usage: { input_tokens: 2, output_tokens: 1 }, }, FORMATS.CLAUDE, - FORMATS.OPENAI + (FORMATS as any).OPENAI ); - assert.equal(result.choices[0].message.content, ""); - assert.equal(result.choices[0].finish_reason, "stop"); - assert.equal(result.model, "claude-3-5-haiku"); + assert.equal(((result as any).choices[0] as any).message.content, ""); + assert.equal((result as any).choices[0].finish_reason, "stop"); + assert.equal((result as any).model, "claude-3-5-haiku"); }); test("Claude stream: message_start emits initial assistant role chunk", () => { diff --git a/tests/unit/translator-resp-gemini-to-openai.test.ts b/tests/unit/translator-resp-gemini-to-openai.test.ts index a63875f41d..e16539c375 100644 --- a/tests/unit/translator-resp-gemini-to-openai.test.ts +++ b/tests/unit/translator-resp-gemini-to-openai.test.ts @@ -37,14 +37,14 @@ test("Gemini non-stream: single candidate text maps to one OpenAI choice", () => FORMATS.OPENAI ); - assert.equal(result.object, "chat.completion"); - assert.equal(result.id, "chatcmpl-resp-single"); - assert.equal(result.model, "gemini-2.5-flash"); - assert.equal(result.choices.length, 1); - assert.equal(result.choices[0].message.role, "assistant"); - assert.equal(result.choices[0].message.content, "Hello from Gemini"); - assert.equal(result.choices[0].finish_reason, "stop"); - assert.deepEqual(result.usage, { + assert.equal((result as any).object, "chat.completion"); + (assert as any).equal((result as any).id, "chatcmpl-resp-single"); + (assert as any).equal((result as any).model, "gemini-2.5-flash"); + assert.equal((result as any).choices.length, 1); + assert.equal((result as any).choices[0].message.role, "assistant"); + assert.equal((result as any).choices[0].message.content, "Hello from Gemini"); + assert.equal((result as any).choices[0].finish_reason, "stop"); + (assert as any).deepEqual((result as any).usage, { prompt_tokens: 3, completion_tokens: 5, total_tokens: 8, @@ -85,26 +85,29 @@ test("Gemini non-stream: multiple candidates keep multimodal content, reasoning }, }, FORMATS.GEMINI, - FORMATS.OPENAI + (FORMATS as any).OPENAI ); - assert.equal(result.choices.length, 2); - assert.equal(result.choices[0].finish_reason, "tool_calls"); - assert.equal(result.choices[0].message.reasoning_content, "Plan first."); - assert.equal(result.choices[0].message.content[0].text, "Answer:"); - assert.equal(result.choices[0].message.content[1].image_url.url, "data:image/png;base64,abc123"); - assert.equal(result.choices[0].message.tool_calls[0].function.name, "read_file"); + assert.equal((result as any).choices.length, 2); + assert.equal(((result as any).choices as any)[0].finish_reason, "tool_calls"); + assert.equal(((result as any).choices[0] as any).message.reasoning_content, "Plan first."); + assert.equal((result as any).choices[0].message.content[0].text, "Answer:"); assert.equal( - result.choices[0].message.tool_calls[0].function.arguments, + ((result as any).choices[0].message as any).content[1].image_url.url, + "data:image/png;base64,abc123" + ); + assert.equal((result as any).choices[0].message.tool_calls[0].function.name, "read_file"); + assert.equal( + ((result as any).choices[0].message as any).tool_calls[0].function.arguments, JSON.stringify({ path: "/tmp/a" }) ); - assert.equal(result.choices[1].message.content, "Second option"); - assert.equal(result.choices[1].finish_reason, "length"); - assert.equal(result.usage.prompt_tokens, 4); - assert.equal(result.usage.completion_tokens, 8); - assert.equal(result.usage.total_tokens, 12); - assert.equal(result.usage.prompt_tokens_details.cached_tokens, 1); - assert.equal(result.usage.completion_tokens_details.reasoning_tokens, 2); + assert.equal(((result as any).choices[1].message as any).content, "Second option"); + (assert as any).equal((result as any).choices[1].finish_reason, "length"); + assert.equal((result as any).usage.prompt_tokens, 4); + assert.equal((result as any).usage.completion_tokens, 8); + (assert as any).equal((result as any).usage.total_tokens, 12); + assert.equal((result as any).usage.prompt_tokens_details.cached_tokens, 1); + assert.equal((result as any).usage.completion_tokens_details.reasoning_tokens, 2); }); test("Gemini non-stream: promptFeedback-only block becomes content_filter", () => { @@ -115,13 +118,13 @@ test("Gemini non-stream: promptFeedback-only block becomes content_filter", () = promptFeedback: { blockReason: "SAFETY" }, }, FORMATS.GEMINI, - FORMATS.OPENAI + (FORMATS as any).OPENAI ); - assert.equal(result.object, "chat.completion"); - assert.equal(result.choices.length, 1); - assert.equal(result.choices[0].message.content, ""); - assert.equal(result.choices[0].finish_reason, "content_filter"); + assert.equal((result as any).object, "chat.completion"); + assert.equal((result as any).choices.length, 1); + assert.equal((result as any).choices[0].message.content, ""); + assert.equal((result as any).choices[0].finish_reason, "content_filter"); }); test("Gemini non-stream: restores sanitized tool names from the request map", () => { @@ -154,7 +157,7 @@ test("Gemini non-stream: restores sanitized tool names from the request map", () new Map([[sanitizedToolName, originalToolName]]) ); - assert.equal(result.choices[0].message.tool_calls[0].function.name, originalToolName); + assert.equal((result as any).choices[0].message.tool_calls[0].function.name, originalToolName); }); test("Gemini stream: first text chunk emits assistant role then content delta", () => { diff --git a/tests/unit/translator-resp-openai-responses.test.ts b/tests/unit/translator-resp-openai-responses.test.ts index 515256c273..78ba82d5bb 100644 --- a/tests/unit/translator-resp-openai-responses.test.ts +++ b/tests/unit/translator-resp-openai-responses.test.ts @@ -291,10 +291,10 @@ test("Responses -> OpenAI: tool-call delta, reasoning delta and completed usage assert.equal(args.choices[0].delta.tool_calls[0].function.arguments, '{"city":"SP"}'); assert.equal(reasoning.choices[0].delta.reasoning.summary, "Need weather info."); assert.equal(completed.choices[0].finish_reason, "tool_calls"); - assert.equal(completed.usage.prompt_tokens, 8); - assert.equal(completed.usage.completion_tokens, 2); - assert.equal(completed.usage.prompt_tokens_details.cached_tokens, 1); - assert.equal(completed.usage.prompt_tokens_details.cache_creation_tokens, 2); + assert.equal((completed as any).usage.prompt_tokens, 8); + assert.equal((completed as any).usage.completion_tokens, 2); + (assert as any).equal((completed as any).usage.prompt_tokens_details.cached_tokens, 1); + assert.equal((completed as any).usage.prompt_tokens_details.cache_creation_tokens, 2); }); test("Responses -> OpenAI: preserves upstream model instead of defaulting to gpt-4", () => { diff --git a/tests/unit/translator-resp-openai-to-claude.test.ts b/tests/unit/translator-resp-openai-to-claude.test.ts index 89d3e96186..508d0b7c01 100644 --- a/tests/unit/translator-resp-openai-to-claude.test.ts +++ b/tests/unit/translator-resp-openai-to-claude.test.ts @@ -184,17 +184,17 @@ test("OpenAI non-stream: chat completion becomes Claude message with thinking an FORMATS.CLAUDE ); - assert.equal(result.type, "message"); - assert.equal(result.model, "gpt-4.1"); - assert.equal(result.content[0].type, "thinking"); - assert.equal(result.content[0].thinking, "Think first"); - assert.equal(result.content[1].type, "text"); - assert.equal(result.content[1].text, "Final answer"); - assert.equal(result.content[2].type, "tool_use"); - assert.equal(result.content[2].name, "read_file"); - assert.deepEqual(result.content[2].input, { path: "/tmp/a" }); - assert.equal(result.stop_reason, "tool_use"); - assert.deepEqual(result.usage, { + assert.equal((result as any).type, "message"); + (assert as any).equal((result as any).model, "gpt-4.1"); + (assert as any).equal((result as any).content[0].type, "thinking"); + assert.equal((result as any).content[0].thinking, "Think first"); + assert.equal((result as any).content[1].type, "text"); + assert.equal((result as any).content[1].text, "Final answer"); + assert.equal((result as any).content[2].type, "tool_use"); + assert.equal((result as any).content[2].name, "read_file"); + (assert as any).deepEqual((result as any).content[2].input, { path: "/tmp/a" }); + assert.equal((result as any).stop_reason, "tool_use"); + assert.deepEqual((result as any).usage, { input_tokens: 5, output_tokens: 3, }); diff --git a/tests/unit/usage-analytics.test.ts b/tests/unit/usage-analytics.test.ts index ed148ac656..45b4c25a45 100644 --- a/tests/unit/usage-analytics.test.ts +++ b/tests/unit/usage-analytics.test.ts @@ -224,9 +224,24 @@ test("getUsageStats aggregates totals, buckets, pending requests, and cost break timestamp: new Date(Date.now() - 20 * 60 * 1000).toISOString(), }); - usageHistory.trackPendingRequest("pricing-model", "pricing-provider", connection.id, true); - usageHistory.trackPendingRequest("pricing-model", "pricing-provider", connection.id, true); - usageHistory.trackPendingRequest("pricing-model", "pricing-provider", connection.id, false); + usageHistory.trackPendingRequest( + "pricing-model", + "pricing-provider", + (connection as any).id, + true + ); + usageHistory.trackPendingRequest( + "pricing-model", + "pricing-provider" as any, + (connection as any).id, + true + ); + usageHistory.trackPendingRequest( + "pricing-model", + "pricing-provider" as any, + (connection as any).id, + false + ); const stats = await usageStats.getUsageStats(); const expectedCost = diff --git a/tests/unit/usage-migrations.test.ts b/tests/unit/usage-migrations.test.ts index 4738890a1a..7a69df1216 100644 --- a/tests/unit/usage-migrations.test.ts +++ b/tests/unit/usage-migrations.test.ts @@ -296,23 +296,23 @@ test("migrateUsageJsonToSqlite migrates call logs to summary rows and ignores du account: "acct-a", connection_id: "conn-a", detail_state: "ready", - artifact_relpath: rows[0].artifact_relpath, + artifact_relpath: (rows[0] as any).artifact_relpath, has_request_body: 1, has_response_body: 1, error_summary: "bad upstream", }); assert.equal(typeof rows[0].artifact_relpath, "string"); - assert.equal(rows[1].id.length > 0, true); - assert.equal(rows[1].method, "POST"); - assert.equal(rows[1].path, null); - assert.equal(rows[1].status, 0); - assert.equal(rows[1].provider, null); - assert.equal(rows[1].account, null); - assert.equal(rows[1].connection_id, null); - assert.equal(rows[1].detail_state, "ready"); - assert.equal(rows[1].has_request_body, 1); - assert.equal(rows[1].has_response_body, 0); - assert.equal(rows[1].error_summary, null); + assert.equal((rows[1] as any).id.length > 0, true); + assert.equal((rows[1] as any).method, "POST"); + assert.equal((rows[1] as any).path, null); + assert.equal((rows[1] as any).status, 0); + assert.equal((rows[1] as any).provider, null); + assert.equal((rows[1] as any).account, null); + assert.equal((rows[1] as any).connection_id, null); + assert.equal((rows[1] as any).detail_state, "ready"); + assert.equal((rows[1] as any).has_request_body, 1); + assert.equal((rows[1] as any).has_response_body, 0); + assert.equal((rows[1] as any).error_summary, null); const firstArtifact = JSON.parse( fs.readFileSync(path.join(TEST_DATA_DIR, "call_logs", rows[0].artifact_relpath), "utf8") @@ -321,7 +321,10 @@ test("migrateUsageJsonToSqlite migrates call logs to summary rows and ignores du assert.deepEqual(firstArtifact.responseBody, { id: "resp-1" }); const secondArtifact = JSON.parse( - fs.readFileSync(path.join(TEST_DATA_DIR, "call_logs", rows[1].artifact_relpath), "utf8") + fs.readFileSync( + path.join(TEST_DATA_DIR, "call_logs", (rows as any)[1].artifact_relpath), + "utf8" + ) ); assert.deepEqual(secondArtifact.requestBody, { foo: "bar" }); assert.equal(secondArtifact.responseBody, null); @@ -337,8 +340,8 @@ test("migrateUsageJsonToSqlite renames empty JSON payloads without inserting row assert.equal(fs.existsSync(`${CALL_LOGS_JSON_FILE}.migrated`), true); const db = getDbInstance(); - assert.equal(db.prepare("SELECT COUNT(*) AS count FROM usage_history").get().count, 0); - assert.equal(db.prepare("SELECT COUNT(*) AS count FROM call_logs").get().count, 0); + assert.equal((db.prepare("SELECT COUNT(*) AS count FROM usage_history").get() as any).count, 0); + assert.equal((db.prepare("SELECT COUNT(*) AS count FROM call_logs").get() as any).count, 0); }); test("migrateUsageJsonToSqlite leaves malformed JSON files in place and reports both failures", () => { @@ -364,8 +367,8 @@ test("migrateUsageJsonToSqlite leaves malformed JSON files in place and reports assert.equal(fs.existsSync(`${CALL_LOGS_JSON_FILE}.migrated`), false); const db = getDbInstance(); - assert.equal(db.prepare("SELECT COUNT(*) AS count FROM usage_history").get().count, 0); - assert.equal(db.prepare("SELECT COUNT(*) AS count FROM call_logs").get().count, 0); + assert.equal((db.prepare("SELECT COUNT(*) AS count FROM usage_history").get() as any).count, 0); + assert.equal((db.prepare("SELECT COUNT(*) AS count FROM call_logs").get() as any).count, 0); assert.ok(errors.some((entry) => entry.includes("Failed to migrate usage.json"))); assert.ok(errors.some((entry) => entry.includes("Failed to migrate call_logs.json"))); }); diff --git a/tests/unit/v1-ws-bridge.test.ts b/tests/unit/v1-ws-bridge.test.ts index e98c49ae88..859ad6b805 100644 --- a/tests/unit/v1-ws-bridge.test.ts +++ b/tests/unit/v1-ws-bridge.test.ts @@ -43,7 +43,7 @@ function waitFor(predicate, { timeoutMs = 3000, intervalMs = 10 } = {}) { clearInterval(timer); reject(new Error("Timed out waiting for condition")); } - } catch (error) { + } catch (error: any) { clearInterval(timer); reject(error); } @@ -62,7 +62,7 @@ test("v1 ws bridge streams correlated request chunks and survives protocol error } if (url.pathname === "/v1/chat/completions" || url.pathname === "/v1/messages") { - const body = JSON.parse((await readRequestBody(req)) || "{}"); + const body = JSON.parse(((await readRequestBody(req)) as any) || "{}"); const firstMessage = Array.isArray(body.messages) ? body.messages[0] : null; const content = typeof firstMessage?.content === "string" ? firstMessage.content : body.model; diff --git a/tests/unit/v1-ws-route.test.ts b/tests/unit/v1-ws-route.test.ts index 5b19f41d5c..e4e75ae34b 100644 --- a/tests/unit/v1-ws-route.test.ts +++ b/tests/unit/v1-ws-route.test.ts @@ -60,7 +60,7 @@ test("v1 ws handshake succeeds without credentials when wsAuth is disabled", asy ); assert.equal(response.status, 200); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(body.ok, true); assert.equal(body.wsAuth, false); assert.equal(body.authenticated, false); @@ -73,7 +73,7 @@ test("v1 ws handshake requires credentials when wsAuth is enabled", async () => const response = await wsRoute.GET(new Request("http://localhost/api/v1/ws?handshake=1")); assert.equal(response.status, 401); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(body.error.code, "ws_auth_required"); assert.equal(body.wsAuth, true); }); @@ -87,7 +87,7 @@ test("v1 ws handshake accepts valid API key query credentials when wsAuth is ena ); assert.equal(response.status, 200); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(body.ok, true); assert.equal(body.authenticated, true); assert.equal(body.authType, "api_key"); @@ -98,6 +98,6 @@ test("v1 ws HTTP GET reports upgrade required outside handshake mode", async () assert.equal(response.status, 426); assert.equal(response.headers.get("upgrade"), "websocket"); - const body = await response.json(); + const body = (await response.json()) as any; assert.equal(body.error.code, "upgrade_required"); }); diff --git a/tests/unit/version-manager.test.ts b/tests/unit/version-manager.test.ts index 87a0967a86..6aea04adc3 100644 --- a/tests/unit/version-manager.test.ts +++ b/tests/unit/version-manager.test.ts @@ -38,7 +38,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 { diff --git a/tests/unit/versionManager-orchestrator.test.ts b/tests/unit/versionManager-orchestrator.test.ts index 8afa6c3efb..6a48514438 100644 --- a/tests/unit/versionManager-orchestrator.test.ts +++ b/tests/unit/versionManager-orchestrator.test.ts @@ -35,7 +35,7 @@ describe("versionManager orchestrator (index.ts)", () => { try { await mod.startTool("nonexistent-tool-xyz"); assert.fail("Should have thrown"); - } catch (err) { + } catch (err: any) { assert.ok(true); // Expected to throw } }); @@ -44,7 +44,7 @@ describe("versionManager orchestrator (index.ts)", () => { try { await mod.restartTool("nonexistent-tool-xyz"); assert.fail("Should have thrown"); - } catch (err) { + } catch (err: any) { assert.ok(true); // Expected to throw } }); diff --git a/tests/unit/web-runtime-env.test.ts b/tests/unit/web-runtime-env.test.ts index 8d1296499e..f7c84159bd 100644 --- a/tests/unit/web-runtime-env.test.ts +++ b/tests/unit/web-runtime-env.test.ts @@ -67,7 +67,7 @@ test("getWebRuntimeEnv throws sanitized messages without leaking secret values", assert.throws( () => getWebRuntimeEnv(env), - (error) => { + (error: any) => { assert.match(error.message, /API_KEY_SECRET/); assert.doesNotMatch(error.message, /short-secret/); return true;