"use server"; import { NextResponse } from "next/server"; import fs from "fs/promises"; import path from "path"; import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth"; import { ensureCliConfigWriteAllowed, getCliPrimaryConfigPath, 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"; import { resolveApiKey } from "@/shared/services/apiKeyResolver"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts"; const TOOL_ID = "jcode"; const getJcodeConfigPath = (): string => getCliPrimaryConfigPath(TOOL_ID) ?? path.join(process.env.HOME ?? "~", ".jcode", "config.json"); const getJcodeDir = () => path.dirname(getJcodeConfigPath()); /** * Check if the config file contains OmniRoute settings. */ const hasOmniRouteConfig = (settings: Record | null): boolean => { if (!settings) return false; return ( typeof settings.baseUrl === "string" && settings.baseUrl.length > 0 && settings._managedBy === "omniroute" ); }; // Read current config.json const readConfig = async (): Promise | null> => { try { const content = await fs.readFile(getJcodeConfigPath(), "utf-8"); return JSON.parse(content) as Record; } catch (err) { if ((err as NodeJS.ErrnoException).code === "ENOENT") return null; throw err; } }; // GET — check jcode CLI and return current config export async function GET(request: Request) { const authError = await requireCliToolsAuth(request); if (authError) return authError; try { const runtime = await getCliRuntimeStatus(TOOL_ID); 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, config: null, message: runtime.installed && !runtime.runnable ? "jcode CLI is installed but not runnable" : "jcode CLI is not installed", }); } const config = await readConfig(); return NextResponse.json({ installed: runtime.installed, runnable: runtime.runnable, command: runtime.command, commandPath: runtime.commandPath, runtimeMode: runtime.runtimeMode, reason: runtime.reason, config, hasOmniRoute: hasOmniRouteConfig(config), configPath: getJcodeConfigPath(), }); } catch (err) { return NextResponse.json( { error: { message: sanitizeErrorMessage(err) } }, { status: 500 } ); } } // POST — write OmniRoute settings to jcode config.json export async function POST(request: Request) { const authError = await requireCliToolsAuth(request); if (authError) return authError; let rawBody; try { rawBody = await request.json(); } catch { return NextResponse.json( { error: { message: "Invalid JSON body" } }, { status: 400 } ); } try { const writeGuard = ensureCliConfigWriteAllowed(); if (writeGuard) { return NextResponse.json({ error: writeGuard }, { status: 403 }); } // Extract keyId BEFORE Zod validation — Zod strips unknown fields const keyId = typeof rawBody?.keyId === "string" ? rawBody.keyId.trim() : null; const validation = validateBody(cliModelConfigSchema, rawBody); if (isValidationFailure(validation)) { return NextResponse.json({ error: validation.error }, { status: 400 }); } const { baseUrl, model } = validation.data; const apiKey = await resolveApiKey(keyId, validation.data.apiKey); const configPath = getJcodeConfigPath(); const jcodeDir = getJcodeDir(); // Ensure directory exists await fs.mkdir(jcodeDir, { recursive: true }); // Backup current config before modifying await createBackup(TOOL_ID, configPath); // Read existing config or start fresh let existing: Record = {}; try { const raw = await fs.readFile(configPath, "utf-8"); existing = JSON.parse(raw) as Record; } catch { /* No existing config */ } // Merge OmniRoute settings (jcode uses OpenAI-compatible config) const normalizedBaseUrl = baseUrl.endsWith("/v1") ? baseUrl : `${baseUrl}/v1`; const updated: Record = { ...existing, baseUrl: normalizedBaseUrl, apiKey, model, _managedBy: "omniroute", }; await fs.writeFile(configPath, JSON.stringify(updated, null, 2), "utf-8"); // Persist last-configured timestamp try { saveCliToolLastConfigured(TOOL_ID); } catch { /* non-critical */ } return NextResponse.json({ success: true, message: "jcode settings applied successfully!", configPath, }); } catch (err) { return NextResponse.json( { error: { message: sanitizeErrorMessage(err) } }, { status: 500 } ); } } // DELETE — remove OmniRoute settings from jcode config export async function DELETE(request: Request) { const authError = await requireCliToolsAuth(request); if (authError) return authError; try { const writeGuard = ensureCliConfigWriteAllowed(); if (writeGuard) { return NextResponse.json({ error: writeGuard }, { status: 403 }); } const configPath = getJcodeConfigPath(); // Backup before modifying await createBackup(TOOL_ID, configPath); // Read existing config let existing: Record = {}; try { const raw = await fs.readFile(configPath, "utf-8"); existing = JSON.parse(raw) as Record; } catch (err) { if ((err as NodeJS.ErrnoException).code === "ENOENT") { return NextResponse.json({ success: true, message: "No config file to reset" }); } throw err; } // Remove OmniRoute-managed fields delete existing.baseUrl; delete existing.apiKey; delete existing.model; delete existing._managedBy; if (Object.keys(existing).length === 0) { await fs.rm(configPath, { force: true }); } else { await fs.writeFile(configPath, JSON.stringify(existing, null, 2), "utf-8"); } // Clear last-configured timestamp try { deleteCliToolLastConfigured(TOOL_ID); } catch { /* non-critical */ } return NextResponse.json({ success: true, message: "jcode OmniRoute settings removed" }); } catch (err) { return NextResponse.json( { error: { message: sanitizeErrorMessage(err) } }, { status: 500 } ); } }