import "server-only"; import { getAgentAiModelWithProvider } from "@/lib/db/queries"; import { resolveModelRuntimeId } from "@/lib/ai/model-runtime"; import { selectVip98kChatInvocation } from "@/lib/ai/vip98k-chat-contract"; import { normalizeVip98kBaseUrl } from "@/lib/ai/vip98k-models"; type ConfiguredModel = NonNullable>>; const OPENAI_COMPATIBLE = new Set(["openai", "openai-compatible", "alibaba", "qwen", "local-qwen", "local", "zhipu", "moonshot", "deepseek", "xai", "siliconflow", "custom"]); const catalogCache = new Map(); export async function createZcodeModelGateway(configured: ConfiguredModel, onError?: (message: string) => void) { const { provider, model } = configured; const modelId = resolveModelRuntimeId(model, { providerType: provider.type }); let base = String(provider.baseUrl || "").replace(/\/+$/, ""); let path = "/v1/chat/completions"; let apiType = "openai-chat-completions"; let contractHeaders: Record = {}; if (provider.type === "vip98k") { base = normalizeVip98kBaseUrl(base); const capabilities = model.capabilities as Record | null; const imageConfig = model.imageGenConfig as Record | null; let contract = selectVip98kChatInvocation(capabilities?.vip98k || imageConfig?.vip98k); if (!contract?.headers["X-Relay-Route-Option"]) { let catalog = catalogCache.get(base); if (!catalog || catalog.expires < Date.now()) { const response = await fetch(`${base}/api/v2/catalog/models`, { redirect: "error", signal: AbortSignal.timeout(12000), headers: { Accept: "application/json" }, }); if (!response.ok) throw new Error("Agent 模型调用规范暂不可用,请稍后重试"); const payload = await response.json(); const entries = (payload?.data?.sections || []).flatMap((section: { models?: unknown[] }) => section.models || []); catalog = { expires: Date.now() + 5 * 60_000, value: entries }; if (catalogCache.size >= 16) catalogCache.delete(catalogCache.keys().next().value!); catalogCache.set(base, catalog); } const entry = catalog.value.find(entry => (entry as { id?: string }).id === modelId); contract = selectVip98kChatInvocation(entry) || contract; } if (!contract) throw new Error("当前 Agent 模型没有可用的对话调用规范"); path = contract.path; contractHeaders = contract.headers; apiType = contract.protocol === "openai-responses" ? "openai-responses" : "openai-chat-completions"; } else { if (!OPENAI_COMPATIBLE.has(provider.type)) throw new Error("当前 Agent 服务商尚未提供原生核心可用的对话协议"); base ||= provider.type === "openai" ? "https://api.openai.com" : ""; if (!base) throw new Error("Agent 服务商缺少接口地址"); base = base.replace(/\/v1$/, ""); } const endpoint = new URL(base + path); if (!["https:", "http:"].includes(endpoint.protocol)) throw new Error("Agent 服务商地址无效"); return { modelId, apiType, forward: async (body: Record, requestedPath: string, signal: AbortSignal) => { if (requestedPath !== path) return Response.json({ error: { message: "此任务的模型协议不匹配" } }, { status: 400 }); const headers: Record = { "content-type": "application/json", ...contractHeaders }; if (apiType === "anthropic-messages") { headers["x-api-key"] = provider.apiKey; headers["anthropic-version"] = "2023-06-01"; } else headers.Authorization = `Bearer ${provider.apiKey}`; try { const response = await fetch(endpoint, { method: "POST", headers, redirect: "error", body: JSON.stringify({ ...body, model: modelId }), signal: AbortSignal.any([signal, AbortSignal.timeout(120_000)]) }); if (response.ok) return response; const message = response.status === 401 ? "当前 Agent 模型服务商鉴权失败,请检查该服务商密钥" : response.status === 429 ? "当前 Agent 模型通道额度不足或请求过多,请稍后重试" : `当前 Agent 模型通道暂不可用(${response.status})`; onError?.(message); await response.body?.cancel(); return Response.json({ error: { message, type: "canvas_model_gateway_error" } }, { status: response.status }); } catch { const message = "Agent 模型连接中断,已保留任务记录"; onError?.(message); return Response.json({ error: { message, type: "canvas_model_gateway_error" } }, { status: 502 }); } }, }; }