Spaces:
Runtime error
Runtime error
| import { NextResponse } from "next/server"; | |
| import fs from "fs/promises"; | |
| import path from "path"; | |
| import { z } from "zod"; | |
| import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth"; | |
| import { getCliPrimaryConfigPath } from "@/shared/services/cliRuntime"; | |
| import { validateBaseUrl } from "@/lib/cli-helper/config-generator"; | |
| import { | |
| generateHermesAgentConfig, | |
| getCurrentHermesAgentRoles, | |
| } from "@/lib/cli-helper/config-generator/hermes-agent"; | |
| import { getHermesConfigPath } from "@/lib/cli-helper/config-generator/hermesHome"; | |
| import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts"; | |
| const hermesAgentSettingsSchema = z.object({ | |
| baseUrl: z.string().min(1, "baseUrl is required"), | |
| keyId: z.string().optional().nullable(), | |
| apiKey: z.string().optional().nullable(), | |
| selections: z | |
| .array( | |
| z.object({ | |
| role: z.string(), | |
| model: z.string(), | |
| }) | |
| ) | |
| .min(1, "selections must be a non-empty array of { role, model }"), | |
| preview: z.boolean().optional(), | |
| }); | |
| /** | |
| * Dedicated endpoint for Hermes Agent (the advanced Nous Research terminal agent). | |
| * This is separate from the original simple "Hermes" guided tool. | |
| * | |
| * GET -> returns current per-role configuration (default, delegation, auxiliary.*) | |
| * POST -> accepts { baseUrl, keyId?, apiKey?, selections: [{role, model}, ...] } | |
| */ | |
| // Resolved lazily so HERMES_HOME is always honoured (#3628). | |
| const getConfigPath = () => getHermesConfigPath(); | |
| function getMetadataPath(configPath: string) { | |
| return path.join(path.dirname(configPath), ".first-setup.json"); | |
| } | |
| export async function GET(request: Request) { | |
| // cli-tools routes touch host config files — guard every handler with the shared auth. | |
| const authError = await requireCliToolsAuth(request); | |
| if (authError) return authError; | |
| try { | |
| const roles = await getCurrentHermesAgentRoles(); | |
| const configPath = getCliPrimaryConfigPath("hermes-agent") || getConfigPath(); | |
| let firstSetupAt: string | null = null; | |
| try { | |
| const metaRaw = await fs.readFile(getMetadataPath(configPath), "utf8"); | |
| const meta = JSON.parse(metaRaw); | |
| firstSetupAt = meta.firstSetupAt || null; | |
| } catch { | |
| // no metadata yet | |
| } | |
| return NextResponse.json({ success: true, roles, firstSetupAt }); | |
| } catch (error) { | |
| return NextResponse.json( | |
| { success: false, error: sanitizeErrorMessage(error) }, | |
| { status: 500 } | |
| ); | |
| } | |
| } | |
| 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: "Invalid JSON" }, { status: 400 }); | |
| } | |
| const parsed = hermesAgentSettingsSchema.safeParse(rawBody); | |
| if (!parsed.success) { | |
| return NextResponse.json( | |
| { error: parsed.error.issues[0]?.message ?? "Invalid request" }, | |
| { status: 400 } | |
| ); | |
| } | |
| const { baseUrl, keyId, apiKey, selections, preview } = parsed.data; | |
| if (!validateBaseUrl(baseUrl)) { | |
| return NextResponse.json({ error: "baseUrl must be a valid http(s) URL" }, { status: 400 }); | |
| } | |
| const configPath = getCliPrimaryConfigPath("hermes-agent") || getConfigPath(); | |
| const configDir = path.dirname(configPath); | |
| await fs.mkdir(configDir, { recursive: true }); | |
| const payload = { | |
| baseUrl, | |
| keyId, | |
| apiKey, | |
| selections, | |
| }; | |
| const result = await generateHermesAgentConfig(payload); | |
| if (result.error) { | |
| return NextResponse.json({ error: result.error }, { status: 400 }); | |
| } | |
| // Preview mode: return the would-be YAML without writing it (Phase 5 polish) | |
| if (preview === true) { | |
| return NextResponse.json({ | |
| success: true, | |
| preview: true, | |
| yaml: result.yaml, | |
| configPath, | |
| }); | |
| } | |
| await fs.writeFile(configPath, result.yaml, "utf-8"); | |
| // Record first setup time if this is the first save via OmniRoute | |
| const metaPath = getMetadataPath(configPath); | |
| try { | |
| await fs.access(metaPath); | |
| } catch { | |
| await fs.writeFile( | |
| metaPath, | |
| JSON.stringify({ firstSetupAt: new Date().toISOString() }), | |
| "utf8" | |
| ); | |
| } | |
| return NextResponse.json({ | |
| success: true, | |
| message: `Hermes Agent config saved to ${configPath}`, | |
| configPath, | |
| }); | |
| } | |