Fix MITM on window

This commit is contained in:
decolua
2026-02-28 10:04:57 +07:00
parent 49a56612bf
commit 833069caac
22 changed files with 650 additions and 199 deletions

View File

@@ -21,7 +21,7 @@ const checkClaudeInstalled = async () => {
try {
const isWindows = os.platform() === "win32";
const command = isWindows ? "where claude" : "command -v claude";
await execAsync(command);
await execAsync(command, { windowsHide: true });
return true;
} catch {
return false;

View File

@@ -76,7 +76,7 @@ const checkCodexInstalled = async () => {
try {
const isWindows = os.platform() === "win32";
const command = isWindows ? "where codex" : "command -v codex";
await execAsync(command);
await execAsync(command, { windowsHide: true });
return true;
} catch {
return false;

View File

@@ -17,7 +17,7 @@ const checkDroidInstalled = async () => {
try {
const isWindows = os.platform() === "win32";
const command = isWindows ? "where droid" : "command -v droid";
await execAsync(command);
await execAsync(command, { windowsHide: true });
return true;
} catch {
return false;

View File

@@ -17,7 +17,7 @@ const checkOpenClawInstalled = async () => {
try {
const isWindows = os.platform() === "win32";
const command = isWindows ? "where openclaw" : "command -v openclaw";
await execAsync(command);
await execAsync(command, { windowsHide: true });
return true;
} catch {
return false;

View File

@@ -0,0 +1,49 @@
import { NextResponse } from "next/server";
import { getApiKeys } from "@/lib/localDb";
// POST /api/models/test - Ping a single model via internal completions
export async function POST(request) {
try {
const { model } = await request.json();
if (!model) return NextResponse.json({ error: "Model required" }, { status: 400 });
const url = new URL(request.url);
const baseUrl = `${url.protocol}//${url.host}`;
// Get an active internal API key for auth (if requireApiKey is enabled)
let apiKey = null;
try {
const keys = await getApiKeys();
apiKey = keys.find((k) => k.isActive !== false)?.key || null;
} catch {}
const headers = { "Content-Type": "application/json" };
if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
const start = Date.now();
const res = await fetch(`${baseUrl}/api/v1/chat/completions`, {
method: "POST",
headers,
body: JSON.stringify({
model,
max_tokens: 1,
stream: false,
messages: [{ role: "user", content: "hi" }],
}),
signal: AbortSignal.timeout(15000),
});
const latencyMs = Date.now() - start;
// 200 = ok; 400 = bad request but auth passed (model reachable)
const ok = res.status === 200 || res.status === 400;
let error = null;
if (!ok) {
const text = await res.text().catch(() => "");
error = `HTTP ${res.status}${text ? `: ${text.slice(0, 120)}` : ""}`;
}
return NextResponse.json({ ok, latencyMs, error });
} catch (err) {
return NextResponse.json({ ok: false, error: err.message }, { status: 500 });
}
}

View File

@@ -1,5 +1,5 @@
import { NextResponse } from "next/server";
import { getProviderConnections, createProviderConnection, getProviderNodeById } from "@/models";
import { getProviderConnections, createProviderConnection, getProviderNodeById, getProviderNodes } from "@/models";
import { APIKEY_PROVIDERS } from "@/shared/constants/config";
import { isOpenAICompatibleProvider, isAnthropicCompatibleProvider } from "@/shared/constants/providers";
@@ -7,15 +7,31 @@ import { isOpenAICompatibleProvider, isAnthropicCompatibleProvider } from "@/sha
export async function GET() {
try {
const connections = await getProviderConnections();
// Build nodeNameMap for compatible providers (id → name)
let nodeNameMap = {};
try {
const nodes = await getProviderNodes();
for (const node of nodes) {
if (node.id && node.name) nodeNameMap[node.id] = node.name;
}
} catch {}
// Hide sensitive fields
const safeConnections = connections.map(c => ({
...c,
apiKey: undefined,
accessToken: undefined,
refreshToken: undefined,
idToken: undefined,
}));
// Hide sensitive fields, enrich name for compatible providers
const safeConnections = connections.map(c => {
const isCompatible = isOpenAICompatibleProvider(c.provider) || isAnthropicCompatibleProvider(c.provider);
const name = isCompatible
? (nodeNameMap[c.provider] || c.providerSpecificData?.nodeName || c.provider)
: c.name;
return {
...c,
name,
apiKey: undefined,
accessToken: undefined,
refreshToken: undefined,
idToken: undefined,
};
});
return NextResponse.json({ connections: safeConnections });
} catch (error) {