Files
OmniRoute/src/app/api/cli-tools/cline-settings/route.ts
diegosouzapw 751ff77b7c fix: extract validation helpers to fix webpack barrel-file resolution bug
Extracted validateBody, isValidationFailure, and loginSchema from the
935-line schemas.ts barrel file into a dedicated helpers.ts module.
Updated 70 API route files to import directly from helpers.ts.

Root cause: webpack on certain environments fails to resolve exports
from the bottom of large barrel files (schemas.ts), causing
'(0, O.Jb) is not a function' errors in production builds.

Fix: Split validation helpers into a small dedicated module (helpers.ts)
so webpack can correctly resolve all exports regardless of file size.

- TypeScript compiles with 0 errors
- All API routes updated to import from helpers.ts
- schemas.ts re-exports from helpers.ts for backward compatibility
2026-03-05 08:05:58 -03:00

254 lines
7.9 KiB
TypeScript

"use server";
import { NextResponse } from "next/server";
import fs from "fs/promises";
import path from "path";
import os from "os";
import { ensureCliConfigWriteAllowed, getCliRuntimeStatus } from "@/shared/services/cliRuntime";
import { createBackup } from "@/shared/services/backupService";
import { saveCliToolLastConfigured, deleteCliToolLastConfigured } from "@/lib/db/cliToolState";
import { cliModelConfigSchema } from "@/shared/validation/schemas";
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
const CLINE_DATA_DIR = path.join(os.homedir(), ".cline", "data");
const GLOBAL_STATE_PATH = path.join(CLINE_DATA_DIR, "globalState.json");
const SECRETS_PATH = path.join(CLINE_DATA_DIR, "secrets.json");
// Read globalState.json
const readGlobalState = async () => {
try {
const content = await fs.readFile(GLOBAL_STATE_PATH, "utf-8");
return JSON.parse(content);
} catch (error: any) {
if (error.code === "ENOENT") return null;
throw error;
}
};
// Read secrets.json
const readSecrets = async () => {
try {
const content = await fs.readFile(SECRETS_PATH, "utf-8");
return JSON.parse(content);
} catch (error: any) {
if (error.code === "ENOENT") return {};
throw error;
}
};
// Check if OmniRoute is configured as OpenAI-compatible provider
const hasOmniRouteConfig = (globalState: any) => {
if (!globalState) return false;
const isOpenAi =
globalState.actModeApiProvider === "openai" || globalState.planModeApiProvider === "openai";
const baseUrl = globalState.openAiBaseUrl || "";
return (
isOpenAi &&
(baseUrl.includes("localhost") ||
baseUrl.includes("127.0.0.1") ||
baseUrl.includes("omniroute"))
);
};
// GET - Check cline CLI and read current settings
export async function GET() {
try {
const runtime = await getCliRuntimeStatus("cline");
if (!runtime.installed || !runtime.runnable) {
return NextResponse.json({
installed: runtime.installed,
runnable: runtime.runnable,
command: runtime.command,
commandPath: runtime.commandPath,
runtimeMode: runtime.runtimeMode,
reason: runtime.reason,
settings: null,
message:
runtime.installed && !runtime.runnable
? "Cline CLI is installed but not runnable"
: "Cline CLI is not installed",
});
}
const globalState = await readGlobalState();
const secrets = await readSecrets();
return NextResponse.json({
installed: runtime.installed,
runnable: runtime.runnable,
command: runtime.command,
commandPath: runtime.commandPath,
runtimeMode: runtime.runtimeMode,
reason: runtime.reason,
settings: {
actModeApiProvider: globalState?.actModeApiProvider,
planModeApiProvider: globalState?.planModeApiProvider,
openAiBaseUrl: globalState?.openAiBaseUrl,
openAiModelId: globalState?.openAiModelId,
planModeOpenAiModelId: globalState?.planModeOpenAiModelId,
},
hasOmniRoute: hasOmniRouteConfig(globalState),
globalStatePath: GLOBAL_STATE_PATH,
secretsPath: SECRETS_PATH,
});
} catch (error) {
console.log("Error checking cline settings:", error);
return NextResponse.json({ error: "Failed to check cline settings" }, { status: 500 });
}
}
// POST - Configure Cline to use OmniRoute as OpenAI-compatible provider
export async function POST(request: Request) {
let rawBody;
try {
rawBody = await request.json();
} catch {
return NextResponse.json(
{
error: {
message: "Invalid request",
details: [{ field: "body", message: "Invalid JSON body" }],
},
},
{ status: 400 }
);
}
try {
const writeGuard = ensureCliConfigWriteAllowed();
if (writeGuard) {
return NextResponse.json({ error: writeGuard }, { status: 403 });
}
const validation = validateBody(cliModelConfigSchema, rawBody);
if (isValidationFailure(validation)) {
return NextResponse.json({ error: validation.error }, { status: 400 });
}
const { baseUrl, apiKey, model } = validation.data;
// Ensure directory exists
await fs.mkdir(CLINE_DATA_DIR, { recursive: true });
// Backup current files before modifying
await createBackup("cline", GLOBAL_STATE_PATH);
await createBackup("cline", SECRETS_PATH);
// Read existing globalState or create new
let globalState: Record<string, any> = {};
try {
const existing = await fs.readFile(GLOBAL_STATE_PATH, "utf-8");
globalState = JSON.parse(existing);
} catch {
/* No existing config */
}
// Normalize baseUrl - Cline expects the base without /v1
const normalizedBaseUrl = baseUrl.endsWith("/v1") ? baseUrl.slice(0, -3) : baseUrl;
// Set OpenAI-compatible provider for both act and plan modes
globalState.actModeApiProvider = "openai";
globalState.planModeApiProvider = "openai";
globalState.openAiBaseUrl = normalizedBaseUrl;
globalState.openAiModelId = model;
globalState.planModeOpenAiModelId = model;
// Write globalState
await fs.writeFile(GLOBAL_STATE_PATH, JSON.stringify(globalState, null, 2));
// Write API key to secrets
let secrets: Record<string, any> = {};
try {
const existing = await fs.readFile(SECRETS_PATH, "utf-8");
secrets = JSON.parse(existing);
} catch {
/* No existing secrets */
}
secrets.openAiApiKey = apiKey || "sk_omniroute";
await fs.writeFile(SECRETS_PATH, JSON.stringify(secrets, null, 2));
// Persist last-configured timestamp
try {
saveCliToolLastConfigured("cline");
} catch {
/* non-critical */
}
return NextResponse.json({
success: true,
message: "Cline settings applied successfully!",
globalStatePath: GLOBAL_STATE_PATH,
});
} catch (error) {
console.log("Error updating cline settings:", error);
return NextResponse.json({ error: "Failed to update cline settings" }, { status: 500 });
}
}
// DELETE - Remove OmniRoute OpenAI-compatible provider config
export async function DELETE() {
try {
const writeGuard = ensureCliConfigWriteAllowed();
if (writeGuard) {
return NextResponse.json({ error: writeGuard }, { status: 403 });
}
// Backup before reset
await createBackup("cline", GLOBAL_STATE_PATH);
await createBackup("cline", SECRETS_PATH);
// Read existing state
let globalState: Record<string, any> = {};
try {
const existing = await fs.readFile(GLOBAL_STATE_PATH, "utf-8");
globalState = JSON.parse(existing);
} catch (error: any) {
if (error.code === "ENOENT") {
return NextResponse.json({ success: true, message: "No settings file to reset" });
}
throw error;
}
// Only reset if currently set to openai mode with our config
if (globalState.actModeApiProvider === "openai") {
delete globalState.openAiBaseUrl;
delete globalState.openAiModelId;
delete globalState.planModeOpenAiModelId;
// Reset provider to default (cline)
globalState.actModeApiProvider = "cline";
globalState.planModeApiProvider = "cline";
}
await fs.writeFile(GLOBAL_STATE_PATH, JSON.stringify(globalState, null, 2));
// Remove API key from secrets
let secrets: Record<string, any> = {};
try {
const existing = await fs.readFile(SECRETS_PATH, "utf-8");
secrets = JSON.parse(existing);
} catch {
/* ignore */
}
delete secrets.openAiApiKey;
await fs.writeFile(SECRETS_PATH, JSON.stringify(secrets, null, 2));
// Clear last-configured timestamp
try {
deleteCliToolLastConfigured("cline");
} catch {
/* non-critical */
}
return NextResponse.json({
success: true,
message: "OmniRoute settings removed from Cline",
});
} catch (error) {
console.log("Error resetting cline settings:", error);
return NextResponse.json({ error: "Failed to reset cline settings" }, { status: 500 });
}
}