import "server-only"; import type { WorldLabsProviderConfig } from "./types"; type WorldLabsRequestOptions = { method?: "GET" | "POST"; path: string; json?: Record; headers?: Record; timeoutMs?: number; }; export const DEFAULT_WORLDLABS_BASE_URL = "https://api.vip98k.com"; export const WORLDLABS_EXTERNAL_TASK_MODEL = "world-generation"; const DEFAULT_WORLDLABS_REQUEST_TIMEOUT_MS = 180_000; function toIntMs(value: string | undefined) { const parsed = Number.parseInt(String(value || "").trim(), 10); if (!Number.isFinite(parsed) || parsed <= 0) { return null; } return parsed; } function getDefaultWorldLabsTimeoutMs() { return toIntMs(process.env.WORLDLABS_API_TIMEOUT_MS) || DEFAULT_WORLDLABS_REQUEST_TIMEOUT_MS; } function createRequestSignal(timeoutMs: number) { const timeoutSignal = (globalThis as Record).AbortSignal; if (timeoutSignal && typeof (timeoutSignal as typeof AbortSignal).timeout === "function") { return { signal: (timeoutSignal as typeof AbortSignal).timeout(timeoutMs), cancel: () => undefined, }; } const controller = new AbortController(); const timer = setTimeout(() => { controller.abort(`World Labs 请求超时 (${timeoutMs}ms)`); }, timeoutMs); return { signal: controller.signal, cancel: () => clearTimeout(timer), }; } export class WorldLabsApiError extends Error { httpStatus: number; payload: unknown; constructor(params: { message: string; httpStatus: number; payload?: unknown; }) { super(params.message); this.name = "WorldLabsApiError"; this.httpStatus = params.httpStatus; this.payload = params.payload; } } function normalizeBaseUrl(baseUrl?: string) { const resolved = String(baseUrl || "").trim() || DEFAULT_WORLDLABS_BASE_URL; return resolved.replace(/\/+$/, ""); } export function isWorldLabsExternalTaskApi(baseUrl?: string | null) { const normalized = normalizeBaseUrl(baseUrl || ""); try { const parsed = new URL(normalized); const hostname = parsed.hostname.toLowerCase(); return hostname === "api.vip98k.com" || normalized.includes("/api/external/task"); } catch { return normalized.includes("api.vip98k.com") || normalized.includes("/api/external/task"); } } function sanitizeApiKey(value: string): string { return String(value || "") .replace(/[\u2028\u2029]/g, "") .replace(/[\u0000-\u001F\u007F]/g, "") .trim(); } async function parseResponseBody(response: Response): Promise { const contentType = response.headers.get("content-type") || ""; if (contentType.includes("application/json")) { return response.json().catch(() => null); } const text = await response.text().catch(() => ""); return text || null; } function extractErrorMessage(payload: unknown, fallback: string) { if (!payload) return fallback; if (typeof payload === "string") return payload || fallback; if (typeof payload !== "object") return fallback; const record = payload as Record; if (typeof record.message === "string" && record.message.trim()) { return record.message.trim(); } if (typeof record.detail === "string" && record.detail.trim()) { return record.detail.trim(); } if (Array.isArray(record.detail) && record.detail.length > 0) { const firstDetail = record.detail[0]; if (firstDetail && typeof firstDetail === "object") { const msg = (firstDetail as Record).msg; if (typeof msg === "string" && msg.trim()) { return msg.trim(); } } } return fallback; } export async function worldLabsRequest( config: WorldLabsProviderConfig, options: WorldLabsRequestOptions ): Promise { const path = options.path.startsWith("/") ? options.path : `/${options.path}`; const url = `${normalizeBaseUrl(config.baseUrl)}${path}`; const headers = new Headers(options.headers || {}); const normalizedApiKey = sanitizeApiKey(String(config.apiKey || "")).replace(/\s+/g, ""); if (!normalizedApiKey) { throw new WorldLabsApiError({ message: "World Labs API Key 为空,请在本地AI设置中重新保存", httpStatus: 400, }); } if (isWorldLabsExternalTaskApi(config.baseUrl)) { headers.set("Authorization", `Bearer ${normalizedApiKey}`); } else { headers.set("WLT-Api-Key", normalizedApiKey); } headers.set("Accept", "application/json"); let body: BodyInit | undefined; if (options.json) { headers.set("Content-Type", "application/json"); body = JSON.stringify(options.json); } const timeoutMs = options.timeoutMs || getDefaultWorldLabsTimeoutMs(); const { signal, cancel } = createRequestSignal(timeoutMs); try { const response = await fetch(url, { method: options.method || "GET", headers, body, cache: "no-store", signal, }); const parsed = await parseResponseBody(response); if (!response.ok) { throw new WorldLabsApiError({ message: extractErrorMessage(parsed, `World Labs 请求失败(HTTP ${response.status})`), httpStatus: response.status, payload: parsed, }); } return parsed as T; } catch (error) { if (error instanceof Error && error.name === "AbortError") { throw new WorldLabsApiError({ message: `World Labs 请求超时(${timeoutMs}ms)`, httpStatus: 504, }); } throw error; } finally { cancel(); } }