2api
feat: configurable stream stall timeout + per-provider UI
88c4c60
Raw
History Blame Contribute Delete
5.62 kB
"use server";
import { NextResponse } from "next/server";
import fs from "fs/promises";
import path from "path";
import os from "os";
import { exec } from "child_process";
import { promisify } from "util";
import { parseTOML, stringifyTOML } from "confbox";
const execAsync = promisify(exec);
const getJcodeConfigDir = () => path.join(os.homedir(), ".jcode");
const getConfigPath = () => path.join(getJcodeConfigDir(), "config.toml");
const getProviderEnvPath = () => {
const configDir = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
return path.join(configDir, "jcode", "provider-9router.env");
};
const checkJcodeInstalled = async () => {
try {
const isWindows = os.platform() === "win32";
const command = isWindows ? "where jcode" : "which jcode";
await execAsync(command, { windowsHide: true });
return true;
} catch {
try {
await fs.access(getJcodeConfigDir());
return true;
} catch {
return false;
}
}
};
const readConfig = async () => {
try {
const configPath = getConfigPath();
const content = await fs.readFile(configPath, "utf-8");
return parseTOML(content);
} catch (error) {
return { providers: {} };
}
};
const has9RouterConfig = (config) => {
if (!config || !config.providers) return false;
const providers = config.providers;
if (providers["9router"]) return true;
for (const [name, provider] of Object.entries(providers)) {
if (provider.base_url && provider.base_url.includes("localhost:20128")) {
return true;
}
}
return false;
};
const writeConfig = async (config) => {
const configPath = getConfigPath();
const content = stringifyTOML(config);
await fs.writeFile(configPath, content, "utf-8");
};
const readProviderEnv = async () => {
try {
const envPath = getProviderEnvPath();
const content = await fs.readFile(envPath, "utf-8");
const env = {};
for (const line of content.split("\n")) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith("#")) continue;
const eqIndex = trimmed.indexOf("=");
if (eqIndex > 0) {
const key = trimmed.slice(0, eqIndex).trim();
let value = trimmed.slice(eqIndex + 1).trim();
if ((value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))) {
value = value.slice(1, -1);
}
env[key] = value;
}
}
return env;
} catch {
return {};
}
};
const writeProviderEnv = async (env) => {
const envPath = getProviderEnvPath();
let content = "# jcode provider environment variables\n";
for (const [key, value] of Object.entries(env)) {
content += `${key}="${value}"\n`;
}
await fs.writeFile(envPath, content, "utf-8");
};
export async function GET() {
const isInstalled = await checkJcodeInstalled();
if (!isInstalled) {
return NextResponse.json({
installed: false,
message: "jcode not installed. Install via: curl -fsSL https://raw.githubusercontent.com/1jehuang/jcode/master/scripts/install.sh | bash",
});
}
const config = await readConfig();
const has9Router = has9RouterConfig(config);
return NextResponse.json({
installed: true,
config,
has9Router,
configPath: getConfigPath(),
});
}
export async function POST(request) {
try {
const { baseUrl, apiKey, models } = await request.json();
if (!baseUrl || !apiKey) {
return NextResponse.json(
{ error: "baseUrl and apiKey are required" },
{ status: 400 }
);
}
const normalizedBaseUrl = baseUrl.endsWith("/v1")
? baseUrl
: `${baseUrl}/v1`;
let config = await readConfig();
if (!config.providers) {
config.providers = {};
}
config.providers["9router"] = {
type: "openai-compatible",
base_url: normalizedBaseUrl,
auth: "bearer",
api_key_env: "JCODE_9ROUTER_API_KEY",
env_file: "provider-9router.env",
default_model: models && models.length > 0 ? models[0] : "cc/claude-opus-4-7",
requires_api_key: true,
};
const configDir = getJcodeConfigDir();
await fs.mkdir(configDir, { recursive: true });
await writeConfig(config);
const xdgConfigDir = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
const jcodeConfigDir = path.join(xdgConfigDir, "jcode");
await fs.mkdir(jcodeConfigDir, { recursive: true });
const env = await readProviderEnv();
env.JCODE_9ROUTER_API_KEY = apiKey;
await writeProviderEnv(env);
return NextResponse.json({
success: true,
message: "jcode configured successfully. Use: jcode --provider-profile 9router",
configPath: getConfigPath(),
});
} catch (error) {
console.error("Error configuring jcode:", error);
return NextResponse.json(
{ error: error.message },
{ status: 500 }
);
}
}
export async function DELETE() {
try {
const config = await readConfig();
if (!config.providers) {
return NextResponse.json({ success: true, message: "No configuration to remove" });
}
delete config.providers["9router"];
await writeConfig(config);
const env = await readProviderEnv();
delete env.JCODE_9ROUTER_API_KEY;
await writeProviderEnv(env);
return NextResponse.json({
success: true,
message: "9router configuration removed from jcode",
});
} catch (error) {
console.error("Error removing jcode configuration:", error);
return NextResponse.json(
{ error: error.message },
{ status: 500 }
);
}
}