| "use server"; |
|
|
| import { NextResponse } from "next/server"; |
| import fs from "fs/promises"; |
| import path from "path"; |
| import { |
| ensureCliConfigWriteAllowed, |
| getCliConfigPaths, |
| getCliRuntimeStatus, |
| } from "@/shared/services/cliRuntime"; |
| import { createMultiBackup } from "@/shared/services/backupService"; |
| import { saveCliToolLastConfigured, deleteCliToolLastConfigured } from "@/lib/db/cliToolState"; |
|
|
| const getCodexConfigPath = () => getCliConfigPaths("codex").config; |
| const getCodexAuthPath = () => getCliConfigPaths("codex").auth; |
| const getCodexDir = () => path.dirname(getCodexConfigPath()); |
|
|
| |
| const parseToml = (content: string) => { |
| const result: Record<string, any> = { _root: {}, _sections: {} }; |
| let currentSection = "_root"; |
|
|
| content.split("\n").forEach((line) => { |
| const trimmed = line.trim(); |
| if (!trimmed || trimmed.startsWith("#")) return; |
|
|
| |
| const sectionMatch = trimmed.match(/^\[(.+)\]$/); |
| if (sectionMatch) { |
| currentSection = sectionMatch[1]; |
| result._sections[currentSection] = {}; |
| return; |
| } |
|
|
| |
| const kvMatch = trimmed.match(/^([^=]+)\s*=\s*(.+)$/); |
| if (kvMatch) { |
| const key = kvMatch[1].trim(); |
| let value = kvMatch[2].trim(); |
| |
| if ( |
| (value.startsWith('"') && value.endsWith('"')) || |
| (value.startsWith("'") && value.endsWith("'")) |
| ) { |
| value = value.slice(1, -1); |
| } |
| if (currentSection === "_root") { |
| result._root[key] = value; |
| } else { |
| result._sections[currentSection][key] = value; |
| } |
| } |
| }); |
|
|
| return result; |
| }; |
|
|
| |
| const toToml = (parsed: Record<string, any>) => { |
| let lines: string[] = []; |
|
|
| |
| Object.entries(parsed._root).forEach(([key, value]) => { |
| lines.push(`${key} = "${value}"`); |
| }); |
|
|
| |
| Object.entries(parsed._sections).forEach(([section, values]) => { |
| lines.push(""); |
| lines.push(`[${section}]`); |
| Object.entries(values).forEach(([key, value]) => { |
| lines.push(`${key} = "${value}"`); |
| }); |
| }); |
|
|
| return lines.join("\n") + "\n"; |
| }; |
|
|
| |
| const readConfig = async () => { |
| try { |
| const configPath = getCodexConfigPath(); |
| const content = await fs.readFile(configPath, "utf-8"); |
| return content; |
| } catch (error: any) { |
| if (error.code === "ENOENT") return null; |
| throw error; |
| } |
| }; |
|
|
| |
| const hasOmniRouteConfig = (config: string | null) => { |
| if (!config) return false; |
| return ( |
| config.includes('model_provider = "omniroute"') || |
| config.includes("[model_providers.omniroute]") |
| ); |
| }; |
|
|
| |
| export async function GET() { |
| try { |
| const runtime = await getCliRuntimeStatus("codex"); |
|
|
| 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 |
| ? "Codex CLI is installed but not runnable" |
| : "Codex 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: getCodexConfigPath(), |
| }); |
| } catch (error) { |
| console.log("Error checking codex settings:", error); |
| return NextResponse.json({ error: "Failed to check codex settings" }, { status: 500 }); |
| } |
| } |
|
|
| |
| export async function POST(request: Request) { |
| try { |
| const writeGuard = ensureCliConfigWriteAllowed(); |
| if (writeGuard) { |
| return NextResponse.json({ error: writeGuard }, { status: 403 }); |
| } |
|
|
| const { baseUrl, apiKey, model } = await request.json(); |
|
|
| if (!baseUrl || !apiKey || !model) { |
| return NextResponse.json( |
| { error: "baseUrl, apiKey and model are required" }, |
| { status: 400 } |
| ); |
| } |
|
|
| const codexDir = getCodexDir(); |
| const configPath = getCodexConfigPath(); |
| const authPath = getCodexAuthPath(); |
|
|
| |
| await fs.mkdir(codexDir, { recursive: true }); |
|
|
| |
| await createMultiBackup("codex", [configPath, authPath]); |
|
|
| |
| let parsed: Record<string, any> = { _root: {}, _sections: {} }; |
| try { |
| const existingConfig = await fs.readFile(configPath, "utf-8"); |
| parsed = parseToml(existingConfig); |
| } catch { |
| |
| } |
|
|
| |
| parsed._root.model = model; |
| parsed._root.model_provider = "omniroute"; |
|
|
| |
| |
| const normalizedBaseUrl = baseUrl.endsWith("/v1") ? baseUrl : `${baseUrl}/v1`; |
| parsed._sections["model_providers.omniroute"] = { |
| name: "OmniRoute", |
| base_url: normalizedBaseUrl, |
| wire_api: "responses", |
| }; |
|
|
| |
| const configContent = toToml(parsed); |
| await fs.writeFile(configPath, configContent); |
|
|
| |
| let authData: Record<string, any> = {}; |
| try { |
| const existingAuth = await fs.readFile(authPath, "utf-8"); |
| authData = JSON.parse(existingAuth); |
| } catch { |
| |
| } |
|
|
| authData.OPENAI_API_KEY = apiKey; |
| await fs.writeFile(authPath, JSON.stringify(authData, null, 2)); |
|
|
| |
| try { |
| saveCliToolLastConfigured("codex"); |
| } catch { |
| |
| } |
|
|
| return NextResponse.json({ |
| success: true, |
| message: "Codex settings applied successfully!", |
| configPath, |
| }); |
| } catch (error) { |
| console.log("Error updating codex settings:", error); |
| return NextResponse.json({ error: "Failed to update codex settings" }, { status: 500 }); |
| } |
| } |
|
|
| |
| export async function DELETE() { |
| try { |
| const writeGuard = ensureCliConfigWriteAllowed(); |
| if (writeGuard) { |
| return NextResponse.json({ error: writeGuard }, { status: 403 }); |
| } |
|
|
| const configPath = getCodexConfigPath(); |
|
|
| |
| await createMultiBackup("codex", [configPath, getCodexAuthPath()]); |
|
|
| |
| let parsed: Record<string, any> = { _root: {}, _sections: {} }; |
| try { |
| const existingConfig = await fs.readFile(configPath, "utf-8"); |
| parsed = parseToml(existingConfig); |
| } catch (error: any) { |
| if (error.code === "ENOENT") { |
| return NextResponse.json({ |
| success: true, |
| message: "No config file to reset", |
| }); |
| } |
| throw error; |
| } |
|
|
| |
| if (parsed._root.model_provider === "omniroute") { |
| delete parsed._root.model; |
| delete parsed._root.model_provider; |
| } |
|
|
| |
| delete parsed._sections["model_providers.omniroute"]; |
|
|
| |
| const configContent = toToml(parsed); |
| await fs.writeFile(configPath, configContent); |
|
|
| |
| const authPath = getCodexAuthPath(); |
| try { |
| const existingAuth = await fs.readFile(authPath, "utf-8"); |
| const authData = JSON.parse(existingAuth); |
| delete authData.OPENAI_API_KEY; |
|
|
| |
| if (Object.keys(authData).length === 0) { |
| await fs.unlink(authPath); |
| } else { |
| await fs.writeFile(authPath, JSON.stringify(authData, null, 2)); |
| } |
| } catch { |
| |
| } |
|
|
| |
| try { |
| deleteCliToolLastConfigured("codex"); |
| } catch { |
| |
| } |
|
|
| return NextResponse.json({ |
| success: true, |
| message: "OmniRoute settings removed successfully", |
| }); |
| } catch (error) { |
| console.log("Error resetting codex settings:", error); |
| return NextResponse.json({ error: "Failed to reset codex settings" }, { status: 500 }); |
| } |
| } |
|
|