TypeScript 97.4%
SQL 1%
JavaScript 0.9%
CSS 0.6%
1import type OpenAI from "openai";2import { type PolyModel, modelKey } from "@/lib/ai/core/types";3import { createOpenAICompatAdapter, listOpenAIModels } from "../shared/openai-compat/factory";4import { DEEPSEEK_CATALOG } from "./catalog";56/**7 * DeepSeek — OpenAI-compatible at https://api.deepseek.com. `GET /models` exposes ids only, so the8 * catalog (docs/provider-research/deepseek.md) provides limits, pricing and thinking controls.9 * Reasoning arrives as `delta.reasoning_content`; cache hits as `prompt_cache_hit_tokens`.10 */11export function normalizeDeepSeekModel(m: { id: string }): PolyModel | null {12 if (!m.id.startsWith("deepseek")) return null;13 const cat = DEEPSEEK_CATALOG.get(m.id);14 const vision = m.id.includes("vision");15 return {16 key: modelKey("deepseek", m.id),17 id: m.id,18 provider: "deepseek",19 displayName: cat?.displayName ?? prettify(m.id),20 family: cat?.family ?? "DeepSeek V4",21 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 },22 limits: cat?.limits ?? {},23 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 } },24 status: cat?.status ?? (m.id.includes("exp") ? "preview" : "active"),25 pricing: cat?.pricing ?? null,26 metadata: { ...(cat?.metadata ?? {}), sortWeight: cat?.sortWeight ?? (m.id.includes("pro") ? 100 : m.id.includes("vision") ? 80 : 90) },27 };28}2930function prettify(id: string) {31 return id.replace(/^deepseek-/, "DeepSeek ").replace(/-/g, " ").replace(/\bv(\d)/i, "V$1").replace(/\b(\w)/g, (c) => c.toUpperCase()).replace(/Exp$/, "(experimental)");32}3334async function list(client: OpenAI, _k: string, signal?: AbortSignal): Promise<PolyModel[]> {35 return (await listOpenAIModels(client, signal)).map(normalizeDeepSeekModel).filter((m): m is PolyModel => Boolean(m));36}3738export const deepseekAdapter = createOpenAICompatAdapter({39 id: "deepseek",40 name: "DeepSeek",41 baseURL: "https://api.deepseek.com/v1",42 keyDocsUrl: "https://platform.deepseek.com/api_keys",43 keyPrefixHint: "sk-",44 listModels: list,45 useMaxTokens: true,46 messageOptions: { inlineFiles: true, replayReasoningContent: true },47 tweakParams: (params, settings, req) => {48 const p = params as unknown as Record<string, unknown>;49 const meta = (req.modelInfo?.metadata ?? {}) as Record<string, unknown>;50 if (req.modelInfo?.capabilities.reasoning) {51 const effort = settings.reasoningEffort;52 if (effort === "none") p.thinking = { type: "disabled" };53 else if (effort) {54 p.thinking = { type: "enabled" };55 // DeepSeek accepts low | high | max (medium/xhigh are treated as high).56 p.reasoning_effort = effort === "minimal" || effort === "low" ? "low" : effort === "max" ? "max" : "high";57 }58 // Reasoning consumes `max_tokens`; a tiny cap yields an empty answer with finish_reason "length".59 if (effort !== "none" && typeof params.max_tokens === "number" && params.max_tokens < Number(meta.minOutputForReasoning ?? 2000)) params.max_tokens = Number(meta.minOutputForReasoning ?? 2000);60 }61 delete p.seed; // silently ignored — never pretend determinism62 },63 refineError: (status, code, message) => {64 if (status === 401) return "INVALID_API_KEY";65 if (status === 402) return "INSUFFICIENT_CREDITS";66 if (status === 422) return "INVALID_PARAMETER";67 if (status === 503) return "PROVIDER_UNAVAILABLE";68 if (status === 400 && /supported API model names|model/i.test(message) && /model/i.test(message)) return "MODEL_NOT_FOUND";69 void code;70 return undefined;71 },72});73