Files
metona-ai-desktop/electron/harness/adapters/mimo.adapter.ts
T
thzxx 656c6b7af1 feat: 升级至 v0.3.4 — 接入 Xiaomi MiMo Provider + 文档全量校准
新增 MiMo (小米) LLM Provider 适配器,支持 mimo-v2.5-pro 和 mimo-v2.5 两个文本模型,复用 OpenAI 兼容 SSE 流式解析,支持 Thinking 模式和 Function Calling。同步校准全量 docs 文档与 README 使其与实际代码一致。

主要变更:
- 新增 mimo.adapter.ts 适配器(SSE + thinking.type + max_completion_tokens)
- 修复 thinking 逻辑 bug:禁用思考时未传 temperature/top_p
- 补全 sse-stream.ts 的 MiMo 缓存字段映射(prompt_tokens_details.cached_tokens)
- 补全 sse-stream.ts 的 finish_reason 映射(repetition_truncation)
- 注册 MiMo 适配器到 adapters/index.ts、main.ts 工厂
- handlers.ts 添加 mimo.contextWindow 热重载触发
- database.service.ts seed 添加 mimo 默认配置
- SettingsModal/OnboardingWizard/Header 添加 MiMo Provider UI
- constants.ts PROVIDER_LABELS 添加 mimo
- .env.example 添加 MIMO_API_KEY/MIMO_BASE_URL
- 反向修改 4 个 docs HTML 设计文档(工具数量/版本日期/适配器列表/数据库表)
- 反向修改 Agent网络工具通用设计-v2.md 附录 B 文件索引
- 完全重写 README.md(v0.3.4、27 工具、4 适配器、9 表)
2026-07-15 22:28:09 +08:00

205 lines
7.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.
/**
* MiMo (Xiaomi) Provider Adapter
*
* 基于 OpenAI 兼容 API。支持 Tool Calling、Thinking 模式、流式输出。
* 模型: mimo-v2.5-pro131072 max_tokens/ mimo-v2.532768 max_tokens
*
* 独立继承 BaseAdapter,通过 shared/openai-format 和 shared/sse-stream 复用
* OpenAI 兼容格式构建和 SSE 流式解析逻辑。不与其他 Provider Adapter 耦合。
*
* 与 DeepSeek 适配器的关键差异:
* - 使用 max_completion_tokens(非 max_tokens
* - thinking 参数结构与 DeepSeek 一致(thinking.type: "enabled"/"disabled"
* - 不提供 /models 端点(listModels 回退到本地元数据)
* - 不提供 /user/balance 端点
* - tool_choice 仅支持 "auto"
*
* @see apis/mimo-api-docs-20260715.html
*/
import { BaseAdapter } from './base-adapter';
import type { MetonaRequest, MetonaResponse, MetonaStreamEvent } from '../types';
import { MetonaFinishReason } 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 MimoAdapter extends BaseAdapter {
override readonly providerId: string = 'mimo';
readonly supportedModels = ['mimo-v2.5-pro', 'mimo-v2.5'];
readonly supportsToolCalling = true;
readonly supportsThinking = true;
// MiMo 模型元信息
// mimo-v2.5-pro: 131072 max_completion_tokensmimo-v2.5: 32768
// 官方未公布上下文窗口大小,保守设为 131072(与 pro 的 max_output 一致)
private static readonly MODEL_INFO: Record<string, MetonaModelInfo> = {
'mimo-v2.5-pro': {
id: 'mimo-v2.5-pro',
name: 'MiMo V2.5 Pro',
contextWindow: 131_072,
maxOutputTokens: 131_072,
supportsToolCalling: true,
supportsThinking: true,
description: '小米 MiMo 旗舰模型,支持深度思考与工具调用',
},
'mimo-v2.5': {
id: 'mimo-v2.5',
name: 'MiMo V2.5',
contextWindow: 131_072,
maxOutputTokens: 32_768,
supportsToolCalling: true,
supportsThinking: true,
description: '小米 MiMo 标准模型,低延迟推理',
},
};
// ===== POST /chat/completions (非流式) =====
async send(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: this.getFetchSignal(this.config.timeoutMs ?? 120_000),
});
if (!response.ok) {
const errorBody = await response.text().catch(() => '');
throw new Error(`MiMo API error: ${response.status} ${response.statusText} - ${errorBody}`);
}
const data = await response.json() as Record<string, unknown>;
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 (流式) =====
async *sendStream(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: this.getFetchSignal(this.config.timeoutMs ?? 300_000),
});
if (!response.ok || !response.body) {
throw new Error(`MiMo stream error: ${response.status}`);
}
yield* parseSSEStream(
response.body,
request.meta.requestId,
request.meta.sessionId,
request.meta.iteration,
);
}
// ===== 模型列表 =====
/**
* MiMo 官方未提供 /models 端点,直接返回本地元数据。
*/
override async listModels(): Promise<MetonaModelInfo[]> {
return this.supportedModels.map((id) => MimoAdapter.MODEL_INFO[id] ?? { id });
}
/**
* 获取上下文窗口大小
*
* v0.3.1: 优先使用配置注入的 contextWindow,回退到 MODEL_INFO 默认值。
* MiMo OpenAI 兼容 API 不支持 context_window 参数,此值仅用于
* Engine 压缩判断和前端 UI 显示。
*/
override getContextWindow(): number {
if (typeof this.config.contextWindow === 'number' && this.config.contextWindow > 0) {
return this.config.contextWindow;
}
const modelInfo = MimoAdapter.MODEL_INFO[this.config.defaultModel];
return modelInfo?.contextWindow ?? 131_072;
}
// ========== 私有方法 ==========
/**
* 构建 MiMo 原生请求体
*
* MiMo 特有参数:
* - thinking: { type: "enabled" / "disabled" } — 与 DeepSeek 一致
* - max_completion_tokens — 非 max_tokensMiMo 使用新字段名)
* - stream_options: { include_usage: true } — 流式返回 usage
* - tool_choice: "auto" — MiMo 仅支持 auto
*
* 思考模式下 temperature/top_p 会被 API 强制覆盖,因此不传这两个参数。
*/
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,
// MiMo 使用 max_completion_tokens(非 max_tokens
max_completion_tokens: request.params.maxTokens,
stream,
};
if (stream) {
body.stream_options = { include_usage: true };
}
if (tools) {
body.tools = tools;
// MiMo 仅支持 tool_choice: "auto"
body.tool_choice = 'auto';
}
// Thinking 模式(与 DeepSeek 参数结构一致)
// MiMo API 默认 thinking.type = "enabled",必须显式发送 disabled 才能关闭
if (request.params.thinkingEnabled === false) {
// 显式禁用思考:传 disabled + temperature/top_p(非思考模式下这两个参数有效)
body.thinking = { type: 'disabled' };
body.temperature = request.params.temperature;
body.top_p = request.params.topP;
} else {
// 启用思考(包括 undefined,因为 MiMo 默认 enabled
// 思考模式下 temperature/top_p 被 API 强制覆盖为 1.0/0.95,不传
body.thinking = { type: 'enabled' };
}
// 停止序列
if (request.params.stopSequences?.length) {
body.stop = request.params.stopSequences;
}
return body;
}
}