Files
metona-ai-desktop/electron/harness/adapters/deepseek.adapter.ts
T
thzxx 6f08759f63 v0.1.2: 全项目代码审查修复 + 子代理编排器 + 上下文压缩 + 并行工具执行
后端修复: Ollama adapter 移除死代码/修复超时硬编码/iteration硬编码/pullModel无超时/流异常断开补发DONE; SSE解析器支持非字符串arguments; Sandbox fail-closed安全加固; IPC移除未使用变量

新增功能: TaskOrchestrator子任务编排器; DelegateTaskTool委派工具; Agent Loop并行工具执行; 上下文自动压缩; 记忆检索注入System Prompt

前端修复: useAgentStream text_delta thought累积bug; 工具调用状态正确流转; 修复重复stateChange事件; TraceStep.thought正确填充
2026-07-06 22:48:33 +08:00

189 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* DeepSeek Provider Adapter
*
* 基于 OpenAI 兼容 API。支持 Tool Calling、Thinking 模式、流式输出。
* 模型: deepseek-v4-flash / deepseek-v4-pro1M 上下文,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<MetonaResponse> {
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<string, unknown>;
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<MetonaStreamEvent> {
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<string[]> {
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<string, unknown> {
const messages = buildOpenAICompatibleMessages(request);
const tools = buildOpenAICompatibleTools(request.tools);
const body: Record<string, unknown> = {
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<string, string> = {
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;
}
}