Your Name 078a115cfd
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
feat: add worldlabs key pool import api
2026-04-05 23:08:04 +08:00

238 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { z } from "zod";
import {
createAiProvider,
getAiProvidersByType,
} from "@/lib/db/queries";
import { ChatSDKError } from "@/lib/errors";
import {
sanitizeWorldLabsApiKey,
sanitizeWorldLabsBaseUrl,
} from "@/lib/worldlabs/server";
export const runtime = "nodejs";
export const maxDuration = 60;
const DEFAULT_WORLDLABS_BASE_URL = "https://api.worldlabs.ai";
const importPayloadSchema = z.object({
keys: z
.array(
z.union([
z.string().min(1),
z.object({
apiKey: z.string().min(1),
name: z.string().min(1).max(128).optional(),
baseUrl: z.string().min(1).optional(),
isEnabled: z.boolean().optional(),
}),
])
)
.max(5000)
.optional(),
apiKeysText: z.string().optional(),
namePrefix: z.string().min(1).max(96).optional(),
baseUrl: z.string().min(1).optional(),
isEnabled: z.boolean().optional(),
});
function readImportToken(request: Request) {
const authHeader = request.headers.get("authorization") || "";
const bearerToken = authHeader.match(/^Bearer\s+(.+)$/i)?.[1]?.trim();
const customToken =
request.headers.get("x-worldlabs-import-token")?.trim() || "";
return bearerToken || customToken || "";
}
function getConfiguredImportToken() {
return String(
process.env.WORLDLABS_KEY_POOL_IMPORT_TOKEN ||
process.env.WORLDLABS_IMPORT_TOKEN ||
""
).trim();
}
function normalizeImportItems(payload: z.infer<typeof importPayloadSchema>) {
const items: Array<{
apiKey: string;
name?: string;
baseUrl?: string;
isEnabled?: boolean;
}> = [];
for (const item of payload.keys || []) {
if (typeof item === "string") {
items.push({ apiKey: item });
} else {
items.push(item);
}
}
const lines = String(payload.apiKeysText || "")
.split(/\r?\n/g)
.map((line) => line.trim())
.filter(Boolean);
for (const line of lines) {
items.push({ apiKey: line });
}
return items;
}
function buildProviderName(params: {
namePrefix: string;
index: number;
overrideName?: string;
}) {
const customName = String(params.overrideName || "").trim();
if (customName) return customName.slice(0, 128);
const datePart = new Date()
.toISOString()
.replace(/[-:TZ.]/g, "")
.slice(0, 14);
return `${params.namePrefix}-${datePart}-${String(params.index + 1).padStart(4, "0")}`.slice(
0,
128
);
}
function maskKey(value: string) {
if (value.length <= 10) {
return `${value.slice(0, 2)}****`;
}
return `${value.slice(0, 4)}****${value.slice(-4)}`;
}
export async function POST(request: Request) {
try {
const configuredToken = getConfiguredImportToken();
if (!configuredToken) {
return Response.json(
{
success: false,
error:
"服务端未配置 WORLDLABS_KEY_POOL_IMPORT_TOKEN,无法使用专用导入接口",
},
{ status: 503 }
);
}
const requestToken = readImportToken(request);
if (!requestToken || requestToken !== configuredToken) {
return Response.json(
{ success: false, error: "导入令牌无效" },
{ status: 401 }
);
}
const rawBody = await request.json().catch(() => ({}));
const payload = importPayloadSchema.parse(rawBody);
const inputItems = normalizeImportItems(payload);
if (inputItems.length === 0) {
return Response.json(
{ success: false, error: "请至少提供一个 World Labs API Key" },
{ status: 400 }
);
}
const existingProviders = await getAiProvidersByType({
type: "worldlabs-3d",
enabledOnly: false,
});
const existingKeySet = new Set(
existingProviders
.map((provider) => sanitizeWorldLabsApiKey(String(provider.apiKey || "")))
.filter(Boolean)
);
const defaultBaseUrl =
sanitizeWorldLabsBaseUrl(payload.baseUrl || "") ||
DEFAULT_WORLDLABS_BASE_URL;
const defaultIsEnabled = payload.isEnabled !== false;
const namePrefix = String(payload.namePrefix || "WorldLabsKey").trim();
const dedupedImports: Array<{
apiKey: string;
name?: string;
baseUrl: string;
isEnabled: boolean;
}> = [];
const requestKeySet = new Set<string>();
const skipped: Array<{ apiKey: string; reason: string }> = [];
for (const item of inputItems) {
const apiKey = sanitizeWorldLabsApiKey(item.apiKey);
if (!apiKey) {
skipped.push({ apiKey: "", reason: "empty_key" });
continue;
}
if (existingKeySet.has(apiKey)) {
skipped.push({ apiKey: maskKey(apiKey), reason: "duplicate_existing" });
continue;
}
if (requestKeySet.has(apiKey)) {
skipped.push({ apiKey: maskKey(apiKey), reason: "duplicate_in_request" });
continue;
}
requestKeySet.add(apiKey);
dedupedImports.push({
apiKey,
name: item.name,
baseUrl:
sanitizeWorldLabsBaseUrl(item.baseUrl || "") || defaultBaseUrl,
isEnabled: item.isEnabled ?? defaultIsEnabled,
});
}
const imported = [];
for (const [index, item] of dedupedImports.entries()) {
const created = await createAiProvider({
name: buildProviderName({
namePrefix,
index,
overrideName: item.name,
}),
type: "worldlabs-3d",
baseUrl: item.baseUrl,
apiKey: item.apiKey,
isEnabled: item.isEnabled,
});
imported.push({
id: created[0]?.id,
name: created[0]?.name,
isEnabled: created[0]?.isEnabled === true,
});
}
return Response.json({
success: true,
data: {
importedCount: imported.length,
skippedCount: skipped.length,
imported,
skipped,
},
});
} catch (error) {
if (error instanceof ChatSDKError) {
return error.toResponse();
}
if (error instanceof z.ZodError) {
return Response.json(
{ success: false, error: error.flatten() },
{ status: 400 }
);
}
return Response.json(
{
success: false,
error:
error instanceof Error ? error.message : "导入 World Labs key 失败",
},
{ status: 500 }
);
}
}