import type OpenAI from "openai"; import { type PolyModel, modelKey } from "@/lib/ai/core/types"; import { createOpenAICompatAdapter, listOpenAIModels } from "../shared/openai-compat/factory"; import { DEEPSEEK_CATALOG } from "./catalog"; /** * DeepSeek — OpenAI-compatible at https://api.deepseek.com. `GET /models` exposes ids only, so the * catalog (docs/provider-research/deepseek.md) provides limits, pricing and thinking controls. * Reasoning arrives as `delta.reasoning_content`; cache hits as `prompt_cache_hit_tokens`. */ export function normalizeDeepSeekModel(m: { id: string }): PolyModel | null { if (!m.id.startsWith("deepseek")) return null; const cat = DEEPSEEK_CATALOG.get(m.id); const vision = m.id.includes("vision"); return { key: modelKey("deepseek", m.id), id: m.id, provider: "deepseek", displayName: cat?.displayName ?? prettify(m.id), family: cat?.family ?? "DeepSeek V4", capabilities: cat?.capabilities ?? { text: true, vision, audioInput: false, audioOutput: false, imageGeneration: false, video: false, reasoning: true, tools: true, structuredOutput: true, streaming: true, files: false, webSearch: false }, limits: cat?.limits ?? {}, parameters: cat?.parameters ?? { temperature: true, topP: true, maxTokens: true, stop: true, frequencyPenalty: true, presencePenalty: true, seed: false, topK: false, reasoningEffort: true, reasoningEffortLevels: ["none", "medium"], temperatureRange: { min: 0, max: 2 } }, status: cat?.status ?? (m.id.includes("exp") ? "preview" : "active"), pricing: cat?.pricing ?? null, metadata: { ...(cat?.metadata ?? {}), sortWeight: cat?.sortWeight ?? (m.id.includes("pro") ? 100 : m.id.includes("vision") ? 80 : 90) }, }; } function prettify(id: string) { return id.replace(/^deepseek-/, "DeepSeek ").replace(/-/g, " ").replace(/\bv(\d)/i, "V$1").replace(/\b(\w)/g, (c) => c.toUpperCase()).replace(/Exp$/, "(experimental)"); } async function list(client: OpenAI, _k: string, signal?: AbortSignal): Promise { return (await listOpenAIModels(client, signal)).map(normalizeDeepSeekModel).filter((m): m is PolyModel => Boolean(m)); } export const deepseekAdapter = createOpenAICompatAdapter({ id: "deepseek", name: "DeepSeek", baseURL: "https://api.deepseek.com/v1", keyDocsUrl: "https://platform.deepseek.com/api_keys", keyPrefixHint: "sk-", listModels: list, useMaxTokens: true, messageOptions: { inlineFiles: true, replayReasoningContent: true }, tweakParams: (params, settings, req) => { const p = params as unknown as Record; const meta = (req.modelInfo?.metadata ?? {}) as Record; if (req.modelInfo?.capabilities.reasoning) { const effort = settings.reasoningEffort; if (effort === "none") p.thinking = { type: "disabled" }; else if (effort) { p.thinking = { type: "enabled" }; // DeepSeek accepts low | high | max (medium/xhigh are treated as high). p.reasoning_effort = effort === "minimal" || effort === "low" ? "low" : effort === "max" ? "max" : "high"; } // Reasoning consumes `max_tokens`; a tiny cap yields an empty answer with finish_reason "length". if (effort !== "none" && typeof params.max_tokens === "number" && params.max_tokens < Number(meta.minOutputForReasoning ?? 2000)) params.max_tokens = Number(meta.minOutputForReasoning ?? 2000); } delete p.seed; // silently ignored — never pretend determinism }, refineError: (status, code, message) => { if (status === 401) return "INVALID_API_KEY"; if (status === 402) return "INSUFFICIENT_CREDITS"; if (status === 422) return "INVALID_PARAMETER"; if (status === 503) return "PROVIDER_UNAVAILABLE"; if (status === 400 && /supported API model names|model/i.test(message) && /model/i.test(message)) return "MODEL_NOT_FOUND"; void code; return undefined; }, });