/** * 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 { buildOpenAICompatibleMessages, buildOpenAICompatibleTools } from './shared/openai-format'; import { parseSSEStream, parseOpenAICompatibleResponse } from './shared/sse-stream'; export class DeepSeekAdapter extends BaseAdapter { override readonly provider: string = 'deepseek'; readonly supportedModels = ['deepseek-v4-pro', 'deepseek-v4-flash']; readonly supportsToolCalling = true; readonly supportsThinking = true; // ===== POST /chat/completions (非流式) ===== async chat(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), signal: AbortSignal.timeout(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.provider, this.config.defaultModel); return { meta: { requestId: request.meta.requestId, provider: this.provider, 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 (流式) ===== async *chatStream(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), signal: AbortSignal.timeout(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 ===== 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) return this.supportedModels; const data = await response.json() as { data?: Array<{ id: string }> }; return data.data?.map((m) => m.id) ?? this.supportedModels; } catch { return this.supportedModels; } } // ===== 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', xhigh: 'max', max: 'max', }; body.reasoning_effort = effortMap[request.params.thinkingEffort ?? 'high'] ?? 'high'; } // 停止序列 if (request.params.stopSequences?.length) { body.stop = request.params.stopSequences; } return body; } }