import type { Extension, ExtensionConfig } from "../../../kernel"; import { ExtensionId, Hook } from "../../catalog"; import type { ModelProvider } from "../agent"; export interface DeepSeekOptions { apiKey?: string; baseUrl?: string; model?: string; request?: (url: string, init: RequestInit) => Promise; } export function createDeepSeekExtension(options: ExtensionConfig = {}): Extension { if (options.apiKey !== undefined && typeof options.apiKey !== "string") { throw new Error("deepseek.apiKey must be a string."); } if (options.baseUrl !== undefined && typeof options.baseUrl !== "string") { throw new Error("deepseek.baseUrl must be a string."); } if (options.model !== undefined && typeof options.model !== "string") { throw new Error("deepseek.model must be a string."); } if (options.request !== undefined && typeof options.request !== "function") { throw new Error("deepseek.request must be a function."); } const config = options as DeepSeekOptions; const apiKey = process.env.DEEPSEEK_API_KEY ?? config.apiKey; const baseUrl = ( process.env.DEEPSEEK_BASE_URL ?? config.baseUrl ?? "https://api.deepseek.com" ).replace(/\/+$/, ""); const model = process.env.DEEPSEEK_MODEL ?? config.model ?? "deepseek-v4-flash"; const request = config.request ?? fetch; const provider: ModelProvider = { id: "deepseek", async *chat(messages, signal) { const response = await request(`${baseUrl}/chat/completions`, { method: "POST", headers: { authorization: `Bearer ${apiKey}`, "content-type": "application/json", }, body: JSON.stringify({ model, messages: messages.map(({ role, content }) => ({ role, content })), thinking: { type: "disabled" }, stream: true, }), signal, }); if (!response.ok) { throw new Error( `DeepSeek request failed (${response.status}): ${await response.text()}`, ); } if (!response.body) { throw new Error("DeepSeek returned an empty response body."); } const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ""; try { while (true) { const { done, value } = await reader.read(); buffer += decoder.decode(value, { stream: !done }); const events = buffer.split(/\r?\n\r?\n/); buffer = events.pop() ?? ""; for (const entry of events) { for (const line of entry.split(/\r?\n/)) { if (!line.startsWith("data:")) continue; const data = line.slice(5).trim(); if (data === "[DONE]") return; if (!data) continue; const chunk = JSON.parse(data); const content = chunk.choices?.[0]?.delta?.content; if (content) yield content; } } if (done) break; } } finally { reader.releaseLock(); } }, }; return { setup(context) { if (!apiKey) { throw new Error("DEEPSEEK_API_KEY is required."); } context.add(Hook.ModelProviders, provider); }, }; } createDeepSeekExtension.id = ExtensionId.DeepSeek;