import type { Extension } 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: DeepSeekOptions = {}): Extension { const apiKey = options.apiKey ?? process.env.DEEPSEEK_API_KEY; const baseUrl = ( options.baseUrl ?? process.env.DEEPSEEK_BASE_URL ?? "https://api.deepseek.com" ).replace(/\/+$/, ""); const model = options.model ?? process.env.DEEPSEEK_MODEL ?? "deepseek-v4-flash"; const request = options.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 { id: ExtensionId.DeepSeek, setup(context) { if (!apiKey) { throw new Error("DEEPSEEK_API_KEY is required."); } context.add(Hook.ModelProviders, provider); }, }; }