/** * DeepSeek Provider Adapter * * 基于 OpenAI 兼容 API。支持 Tool Calling、Thinking 模式、流式输出。 * 模型: deepseek-v4-flash / deepseek-v4-pro(1M 上下文,384K 最大输出) * * 独立继承 BaseAdapter,通过 shared/openai-format 和 shared/sse-stream 复用 * OpenAI 兼容格式构建和 SSE 流式解析逻辑。不与其他 Provider Adapter 耦合。 * * @see apis/deepseek-api-docs-20260518.html */ import { BaseAdapter } from './base-adapter'; import type { MetonaRequest, MetonaResponse, MetonaStreamEvent } from '../types'; import { MetonaFinishReason, MetonaErrorCode } from '../types'; import type { MetonaModelInfo } from '../types/metona-adapter'; import { buildOpenAICompatibleMessages, buildOpenAICompatibleTools } from './shared/openai-format'; import { parseSSEStream, parseOpenAICompatibleResponse } from './shared/sse-stream'; export class DeepSeekAdapter extends BaseAdapter { // H-2 修复: provider → providerId(规范要求) override readonly providerId: string = 'deepseek'; readonly supportedModels = ['deepseek-v4-pro', 'deepseek-v4-flash']; readonly supportsToolCalling = true; readonly supportsThinking = true; // H-2 修复: DeepSeek 模型元信息(1M 上下文,384K 最大输出) private static readonly MODEL_INFO: Record = { 'deepseek-v4-pro': { id: 'deepseek-v4-pro', name: 'DeepSeek V4 Pro', contextWindow: 1_000_000, maxOutputTokens: 384_000, supportsToolCalling: true, supportsThinking: true, description: 'DeepSeek 旗舰模型,1M 上下文,支持深度推理与工具调用', }, 'deepseek-v4-flash': { id: 'deepseek-v4-flash', name: 'DeepSeek V4 Flash', contextWindow: 1_000_000, maxOutputTokens: 384_000, supportsToolCalling: true, supportsThinking: true, description: 'DeepSeek 快速版,1M 上下文,低延迟推理', }, }; // ===== POST /chat/completions (非流式) ===== // H-2 修复: chat → send(规范要求) async send(request: MetonaRequest): Promise { const body = this.toNativeRequest(request, false); const response = await fetch(`${this.config.baseURL}/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.config.apiKey}`, ...this.config.headers, }, body: JSON.stringify(body), // C-2 修复: 使用合并后的 signal(外部 abort + timeout) signal: this.getFetchSignal(this.config.timeoutMs ?? 120_000), }); if (!response.ok) { const errorBody = await response.text().catch(() => ''); throw new Error(`DeepSeek API error: ${response.status} ${response.statusText} - ${errorBody}`); } const data = await response.json() as Record; const parsed = parseOpenAICompatibleResponse(data, request.meta.requestId, this.providerId, this.config.defaultModel); return { meta: { requestId: request.meta.requestId, provider: this.providerId, model: (data.model as string) ?? this.config.defaultModel, latencyMs: 0, timestamp: Date.now(), }, content: parsed.content, reasoningContent: parsed.reasoningContent, toolCalls: parsed.toolCalls, usage: parsed.usage, finishReason: parsed.finishReason as MetonaFinishReason, }; } // ===== POST /chat/completions (流式) ===== // H-2 修复: chatStream → sendStream(规范要求) async *sendStream(request: MetonaRequest): AsyncIterable { const body = this.toNativeRequest(request, true); const response = await fetch(`${this.config.baseURL}/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.config.apiKey}`, ...this.config.headers, }, body: JSON.stringify(body), // C-2 修复: 使用合并后的 signal(外部 abort + timeout) signal: this.getFetchSignal(this.config.timeoutMs ?? 300_000), }); if (!response.ok || !response.body) { throw new Error(`DeepSeek stream error: ${response.status}`); } yield* parseSSEStream( response.body, request.meta.requestId, request.meta.sessionId, request.meta.iteration, ); } // ===== GET /models ===== /** * H-2 修复: 返回 MetonaModelInfo[](规范要求) * * 优先尝试从 API 获取实时模型列表,并合并本地 MODEL_INFO 元数据。 * API 不可用时回退到 supportedModels。 */ async listModels(): Promise { try { const response = await fetch(`${this.config.baseURL}/models`, { headers: { Authorization: `Bearer ${this.config.apiKey}` }, signal: AbortSignal.timeout(10_000), }); if (response.ok) { const data = await response.json() as { data?: Array<{ id: string }> }; if (data.data?.length) { // 合并 API 返回的模型 ID 与本地元数据 return data.data.map((m) => DeepSeekAdapter.MODEL_INFO[m.id] ?? { id: m.id }); } } } catch { // API 不可用时降级 } // 回退到 supportedModels(带本地元数据) return this.supportedModels.map((id) => DeepSeekAdapter.MODEL_INFO[id] ?? { id }); } /** * H-2 修复: 获取上下文窗口大小(规范要求) * * v0.3.1: 优先使用配置注入的 contextWindow,回退到 MODEL_INFO 默认值。 * DeepSeek OpenAI 兼容 API 不支持 context_window 参数,此值仅用于 * Engine 压缩判断和前端 UI 显示。 */ override getContextWindow(): number { // v0.3.1: 优先使用配置注入的 contextWindow if (typeof this.config.contextWindow === 'number' && this.config.contextWindow > 0) { return this.config.contextWindow; } // 回退到 MODEL_INFO const modelInfo = DeepSeekAdapter.MODEL_INFO[this.config.defaultModel]; return modelInfo?.contextWindow ?? 1_000_000; } // ===== GET /user/balance ===== async getBalance(): Promise<{ currency: string; totalBalance: string; grantedBalance: string; toppedUpBalance: string; } | null> { try { const response = await fetch(`${this.config.baseURL}/user/balance`, { headers: { Authorization: `Bearer ${this.config.apiKey}` }, signal: AbortSignal.timeout(10_000), }); if (!response.ok) return null; const data = await response.json() as { currency?: string; total_balance?: string; granted_balance?: string; topped_up_balance?: string; }; return { currency: data.currency ?? 'CNY', totalBalance: data.total_balance ?? '0', grantedBalance: data.granted_balance ?? '0', toppedUpBalance: data.topped_up_balance ?? '0', }; } catch { return null; } } // ========== 私有方法 ========== /** * 构建 DeepSeek 原生请求体 * * DeepSeek 特有参数: * - thinking: { type: "enabled" } — 启用思考模式 * - reasoning_effort — 思考强度映射 * - stream_options: { include_usage: true } — 流式返回 usage */ private toNativeRequest(request: MetonaRequest, stream: boolean): Record { const messages = buildOpenAICompatibleMessages(request); const tools = buildOpenAICompatibleTools(request.tools); const body: Record = { model: this.config.defaultModel, messages, temperature: request.params.temperature, max_tokens: request.params.maxTokens, stream, }; if (stream) { body.stream_options = { include_usage: true }; } if (tools) { body.tools = tools; } // Thinking 模式 // API 默认 thinking.type = "enabled",必须显式发送 disabled 才能关闭 if (request.params.thinkingEnabled === false) { body.thinking = { type: 'disabled' }; } else if (request.params.thinkingEnabled) { body.thinking = { type: 'enabled' }; const effortMap: Record = { low: 'high', medium: 'high', high: 'high', max: 'max', }; // DeepSeek API 仅支持 high / max 两档,low/medium 映射为 high body.reasoning_effort = effortMap[request.params.thinkingEffort ?? 'high'] ?? 'high'; } // 停止序列 if (request.params.stopSequences?.length) { body.stop = request.params.stopSequences; } return body; } }