chore: add buildOutput RTK filter, drop legacy cloud sync, internal cleanup

- feat(rtk): buildOutput filter + autodetect for npm/yarn/cargo logs
- chore: remove unused cloud sync module and related routes
- ui: hide deprecated providers (qwen, iflow, antigravity)
- chore: minor tray/cli/internal adjustments
This commit is contained in:
decolua
2026-05-16 10:54:41 +07:00
parent 21ea744c72
commit 3cca2252a6
26 changed files with 871 additions and 535 deletions
@@ -3,6 +3,9 @@ import { getProviderConnectionById, getApiKeys } from "@/lib/localDb";
import { getProviderModels, PROVIDER_ID_TO_ALIAS } from "open-sse/config/providerModels.js";
import { isOpenAICompatibleProvider, isAnthropicCompatibleProvider } from "@/shared/constants/providers";
import { UPDATER_CONFIG } from "@/shared/constants/config";
import { getConsistentMachineId } from "@/shared/utils/machineId";
const CLI_TOKEN_SALT = "9r-cli-auth";
/**
* Get an active API key to pass through auth when requireApiKey is enabled.
@@ -16,11 +19,12 @@ async function getInternalApiKey() {
* Ping a single model via internal completions endpoint (OpenAI format).
* open-sse handles all provider translation automatically.
*/
async function pingModel(modelId, baseUrl, apiKey) {
async function pingModel(modelId, baseUrl, apiKey, cliToken) {
const start = Date.now();
try {
const headers = { "Content-Type": "application/json" };
if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
if (cliToken) headers["x-9r-cli-token"] = cliToken;
const res = await fetch(`${baseUrl}/api/v1/chat/completions`, {
method: "POST",
headers,
@@ -83,17 +87,19 @@ export async function POST(request, { params }) {
}
const apiKey = await getInternalApiKey();
// Bypass dashboardGuard for internal self-call via CLI token (machineId-based)
const cliToken = await getConsistentMachineId(CLI_TOKEN_SALT);
// Warm up with first model to trigger token refresh (if needed) before parallel calls.
// This prevents race condition where multiple requests concurrently refresh the same token.
const [first, ...rest] = models;
const firstResult = await pingModel(`${alias}/${first.id}`, baseUrl, apiKey);
const firstResult = await pingModel(`${alias}/${first.id}`, baseUrl, apiKey, cliToken);
const results = [{ modelId: first.id, name: first.name || first.id, ...firstResult }];
if (rest.length > 0) {
const restResults = await Promise.all(
rest.map(async (model) => {
const result = await pingModel(`${alias}/${model.id}`, baseUrl, apiKey);
const result = await pingModel(`${alias}/${model.id}`, baseUrl, apiKey, cliToken);
return { modelId: model.id, name: model.name || model.id, ...result };
})
);
+41 -9
View File
@@ -2,19 +2,51 @@ import { NextResponse } from "next/server";
import { getProviderConnections } from "@/lib/localDb";
import { backfillCodexEmails } from "@/lib/oauth/providers";
// GET /api/providers/client - List all connections for client (includes sensitive fields for sync)
// Whitelist: only safe metadata fields exposed to UI
const SAFE_FIELDS = [
"id", "provider", "authType", "name", "email", "displayName",
"priority", "globalPriority", "isActive", "defaultModel",
"testStatus", "lastError", "lastErrorAt", "errorCode",
"expiresAt", "lastUsedAt", "consecutiveUseCount",
"createdAt", "updatedAt",
];
// providerSpecificData fields safe to expose (non-secret config only)
const SAFE_PSD_FIELDS = [
"baseUrl", "azureEndpoint", "deployment", "apiVersion", "accountId",
"region", "projectId", "resourceUrl", "proxyPoolId",
"connectionProxyEnabled", "connectionProxyUrl", "connectionNoProxy",
"githubLogin", "githubName", "githubEmail", "githubUserId",
"username", "firstName", "lastName", "authMethod", "authKind",
];
function maskName(name) {
if (typeof name !== "string" || name.length <= 16) return name;
// Names like "hahask-uDUOg90..." may embed API keys — mask if looks like key
if (/[a-zA-Z0-9_-]{32,}/.test(name)) return `${name.slice(0, 8)}***`;
return name;
}
function sanitize(c) {
const safe = {};
for (const f of SAFE_FIELDS) if (c[f] !== undefined) safe[f] = c[f];
if (safe.name) safe.name = maskName(safe.name);
if (c.providerSpecificData) {
const psd = {};
for (const f of SAFE_PSD_FIELDS) {
if (c.providerSpecificData[f] !== undefined) psd[f] = c.providerSpecificData[f];
}
safe.providerSpecificData = psd;
}
return safe;
}
// GET /api/providers/client - List connections for dashboard UI (whitelist only)
export async function GET() {
try {
await backfillCodexEmails();
const connections = await getProviderConnections();
// Include sensitive fields for sync to cloud (only accessible from same origin)
const clientConnections = connections.map(c => ({
...c,
// Don't hide sensitive fields here since this is for internal sync
}));
return NextResponse.json({ connections: clientConnections });
return NextResponse.json({ connections: connections.map(sanitize) });
} catch (error) {
console.log("Error fetching providers for client:", error);
return NextResponse.json({ error: "Failed to fetch providers" }, { status: 500 });