feat: agent model account support xiaomi (#12242)

https://github.com/1Panel-dev/1Panel/issues/12220
This commit is contained in:
CityFun
2026-03-20 07:27:55 +00:00
committed by GitHub
parent ff8f79ed52
commit 59c657e403
16 changed files with 58 additions and 88 deletions
+13
View File
@@ -114,6 +114,19 @@ var catalog = map[string]Meta{
{ID: "minimax/MiniMax-M2.5-highspeed", Name: "MiniMax M2.5 highspeed"},
},
},
"xiaomi": {
Key: "xiaomi",
DisplayName: "Xiaomi",
Sort: 46,
DefaultBaseURL: "https://api.xiaomimimo.com/anthropic",
EnvKey: "XIAOMI_API_KEY",
Models: []Model{
{ID: "xiaomi/mimo-v2-pro", Name: "Xiaomi MiMo V2 Pro", ContextWindow: 262144, MaxTokens: 8192, Reasoning: false, Input: []string{"text"}},
{ID: "xiaomi/mimo-v2-omni", Name: "Xiaomi MiMo V2 Omni", ContextWindow: 262144, MaxTokens: 8192, Reasoning: false, Input: []string{"text"}},
{ID: "xiaomi/mimo-v2-tts", Name: "Xiaomi MiMo V2 TTS", ContextWindow: 262144, MaxTokens: 8192, Reasoning: false, Input: []string{"text"}},
{ID: "xiaomi/mimo-v2-flash", Name: "Xiaomi MiMo V2 Flash", ContextWindow: 262144, MaxTokens: 8192, Reasoning: false, Input: []string{"text"}},
},
},
"kimi": {
Key: "kimi",
DisplayName: "Kimi (CN)",
+18
View File
@@ -34,6 +34,8 @@ func BuildOpenClawPatch(provider, modelName, apiType string, maxTokens, contextW
return buildArkPatch(modelID, maxTokens, contextWindow, baseURL, apiKey), nil
case "minimax":
return buildMiniMaxPatch(modelID, baseURL, apiKey), nil
case "xiaomi":
return buildXiaomiPatch(modelID, baseURL, apiKey), nil
case "custom", "vllm":
return buildCustomPatch(provider, modelName, apiType, maxTokens, contextWindow, baseURL, apiKey), nil
case "ollama":
@@ -142,6 +144,22 @@ func buildMiniMaxPatch(modelID, baseURL, apiKey string) *OpenClawPatch {
}
}
func buildXiaomiPatch(modelID, baseURL, apiKey string) *OpenClawPatch {
normalizedID := strings.TrimSpace(modelID)
return &OpenClawPatch{
PrimaryModel: "xiaomi/" + normalizedID,
Models: providerModels("xiaomi", strings.TrimSpace(apiKey), firstNonEmpty(strings.TrimSpace(baseURL), "https://api.xiaomimimo.com/anthropic"), "anthropic-messages", map[string]interface{}{
"id": normalizedID,
"name": normalizedID,
"reasoning": false,
"input": []string{"text"},
"contextWindow": 262144,
"maxTokens": 8192,
"cost": map[string]interface{}{},
}),
}
}
func buildCustomPatch(provider, modelName, apiType string, maxTokens, contextWindow int, baseURL, apiKey string) *OpenClawPatch {
customModelID := normalizeCustomModel(modelName)
return &OpenClawPatch{
+21
View File
@@ -142,6 +142,27 @@ func BuildVerifyRequest(provider, baseURL, apiKey string) VerifyRequest {
}},
}},
})
case "xiaomi":
request.Method = http.MethodPost
headers["x-api-key"] = apiKey
headers["anthropic-version"] = "2023-06-01"
headers["Content-Type"] = "application/json"
if strings.Contains(base, "/v1") {
request.URL = base + "/messages"
} else {
request.URL = base + "/v1/messages"
}
request.Body = mustJSON(map[string]interface{}{
"model": "mimo-v2-flash",
"max_tokens": 1,
"messages": []map[string]interface{}{{
"role": "user",
"content": []map[string]string{{
"type": "text",
"text": "test",
}},
}},
})
default:
headers["Authorization"] = fmt.Sprintf("Bearer %s", apiKey)
if strings.Contains(base, "/v1") {
+3 -1
View File
@@ -81,7 +81,7 @@ func verifyResolvedAgentAccount(input resolvedAgentAccountVerification) error {
}
func resolveAgentAccountAPIType(provider, apiType, fallbackAPIType string) (string, error) {
if provider == "minimax" {
if provider == "minimax" || provider == "xiaomi" {
return "anthropic-messages", nil
}
resolvedAPIType := normalizeAPIType(apiType)
@@ -1919,6 +1919,8 @@ func fixedProviderBaseURL(provider string) (string, bool) {
return providerDefaultBaseURL(provider)
case "minimax":
return providerDefaultBaseURL(provider)
case "xiaomi":
return providerDefaultBaseURL(provider)
default:
return "", false
}
-2
View File
@@ -694,8 +694,6 @@ const message = {
accountModelsRequired: 'Configure at least one model',
accountModelsDuplicate: 'Duplicate models exist in the catalog',
modelPool: 'Model Pool',
modelPoolHelper:
'Manage the models exposed by this account here. Agent creation and OpenClaw model switching both use this pool.',
modelInputTypes: 'Input Types',
reasoning: 'Reasoning Model',
token: 'Token',
-2
View File
@@ -702,8 +702,6 @@ const message = {
accountModelsRequired: 'Configure at least one model',
accountModelsDuplicate: 'Duplicate models exist in the catalog',
modelPool: 'Model Pool',
modelPoolHelper:
'Manage the models exposed by this account here. Agent creation and OpenClaw model switching both use this pool.',
modelInputTypes: 'Input Types',
reasoning: 'Reasoning Model',
token: 'Token',
-2
View File
@@ -695,8 +695,6 @@ const message = {
accountModelsRequired: 'Configure at least one model',
accountModelsDuplicate: 'Duplicate models exist in the catalog',
modelPool: 'Model Pool',
modelPoolHelper:
'Manage the models exposed by this account here. Agent creation and OpenClaw model switching both use this pool.',
modelInputTypes: 'Input Types',
reasoning: 'Reasoning Model',
token: 'トークン',
-2
View File
@@ -687,8 +687,6 @@ const message = {
accountModelsRequired: 'Configure at least one model',
accountModelsDuplicate: 'Duplicate models exist in the catalog',
modelPool: 'Model Pool',
modelPoolHelper:
'Manage the models exposed by this account here. Agent creation and OpenClaw model switching both use this pool.',
modelInputTypes: 'Input Types',
reasoning: 'Reasoning Model',
token: '토큰',
-2
View File
@@ -702,8 +702,6 @@ const message = {
accountModelsRequired: 'Configure at least one model',
accountModelsDuplicate: 'Duplicate models exist in the catalog',
modelPool: 'Model Pool',
modelPoolHelper:
'Manage the models exposed by this account here. Agent creation and OpenClaw model switching both use this pool.',
modelInputTypes: 'Input Types',
reasoning: 'Reasoning Model',
token: 'Token',
-2
View File
@@ -697,8 +697,6 @@ const message = {
accountModelsRequired: 'Configure at least one model',
accountModelsDuplicate: 'Duplicate models exist in the catalog',
modelPool: 'Model Pool',
modelPoolHelper:
'Manage the models exposed by this account here. Agent creation and OpenClaw model switching both use this pool.',
modelInputTypes: 'Input Types',
reasoning: 'Reasoning Model',
token: 'Token',
-2
View File
@@ -694,8 +694,6 @@ const message = {
accountModelsRequired: 'Configure at least one model',
accountModelsDuplicate: 'Duplicate models exist in the catalog',
modelPool: 'Model Pool',
modelPoolHelper:
'Manage the models exposed by this account here. Agent creation and OpenClaw model switching both use this pool.',
modelInputTypes: 'Input Types',
reasoning: 'Reasoning Model',
token: 'Токен',
-2
View File
@@ -698,8 +698,6 @@ const message = {
accountModelsRequired: 'Configure at least one model',
accountModelsDuplicate: 'Duplicate models exist in the catalog',
modelPool: 'Model Pool',
modelPoolHelper:
'Manage the models exposed by this account here. Agent creation and OpenClaw model switching both use this pool.',
modelInputTypes: 'Input Types',
reasoning: 'Reasoning Model',
token: 'Token',
-1
View File
@@ -662,7 +662,6 @@ const message = {
accountModelsRequired: '請至少配置一個模型',
accountModelsDuplicate: '模型池中存在重複模型請檢查後重試',
modelPool: '模型池',
modelPoolHelper: '在此維護該模型帳號的模型池建立智能體與 OpenClaw 模型切換都會使用這些模型',
modelInputTypes: '輸入類型',
reasoning: '推理模型',
token: 'Token',
-1
View File
@@ -661,7 +661,6 @@ const message = {
accountModelsRequired: '请至少配置一个模型',
accountModelsDuplicate: '模型池中存在重复模型请检查后重试',
modelPool: '模型池',
modelPoolHelper: '在这里维护该模型账号的模型池智能体创建和 OpenClaw 模型切换都会使用这些模型',
modelInputTypes: '输入类型',
reasoning: '推理模型',
token: 'Token',
@@ -115,7 +115,7 @@ const headerTitle = computed(() =>
const showInitialModel = computed(() => !form.id && initialModelProviders.includes(form.provider));
const apiTypeOptions = computed(() => {
if (form.provider === 'minimax') {
if (form.provider === 'minimax' || form.provider === 'xiaomi') {
return ['anthropic-messages'];
}
if (form.provider === 'custom' || form.provider === 'vllm') {
@@ -176,7 +176,7 @@ const resetInitialModel = () => {
};
const normalizeProviderAPIType = (provider: string, apiType?: string) => {
if (provider === 'minimax') {
if (provider === 'minimax' || provider === 'xiaomi') {
return 'anthropic-messages';
}
if (provider === 'ollama') {
@@ -2,13 +2,6 @@
<DrawerPro v-model="open" :header="headerTitle" size="large" @close="handleClose">
<template #content>
<div v-loading="loading">
<el-alert
:title="$t('aiTools.agents.modelPoolHelper')"
type="info"
:closable="false"
show-icon
class="mb-4"
/>
<div class="toolbar">
<el-button type="primary" @click="openCreate">
{{ $t('commons.button.add') }}
@@ -45,21 +38,7 @@
<DrawerPro v-model="editorOpen" :header="editorTitle" size="normal" @close="handleEditorClose">
<el-form ref="formRef" :model="form" label-position="top" :rules="rules" v-loading="saving">
<el-form-item :label="$t('aiTools.model.model')" prop="id">
<el-select
v-model="form.id"
filterable
allow-create
default-first-option
:reserve-keyword="false"
@change="handleModelIDChange"
>
<el-option
v-for="model in providerModelOptions"
:key="model.id"
:label="model.name"
:value="model.id"
/>
</el-select>
<el-input v-model="form.id" clearable />
</el-form-item>
<el-form-item :label="$t('commons.table.name')" prop="name">
<el-input v-model="form.name" />
@@ -99,14 +78,12 @@ import {
createAgentAccountModel,
deleteAgentAccountModel,
getAgentAccountModels,
getAgentProviders,
updateAgentAccountModel,
} from '@/api/modules/ai';
import { AI } from '@/api/interface/ai';
import i18n from '@/lang';
import { MsgError } from '@/utils/message';
import { getAgentProviderDisplayName } from '@/utils/agent';
import { useGlobalStore } from '@/composables/useGlobalStore';
import { Rules } from '@/global/form-rules';
const emit = defineEmits(['updated']);
@@ -116,9 +93,7 @@ const editorOpen = ref(false);
const loading = ref(false);
const saving = ref(false);
const formRef = ref<FormInstance>();
const providerModels = ref<Record<string, AI.ProviderModelInfo[]>>({});
const items = ref<AI.AgentAccountModel[]>([]);
const { isIntl } = useGlobalStore();
const account = reactive({
id: 0,
@@ -145,8 +120,6 @@ const editorTitle = computed(() =>
form.recordId ? i18n.global.t('commons.button.edit') : i18n.global.t('commons.button.add'),
);
const providerModelOptions = computed(() => providerModels.value[account.provider] || []);
const rules = reactive({
id: [Rules.requiredInput],
name: [Rules.requiredInput],
@@ -173,20 +146,6 @@ const buildModelItem = (model?: Partial<AI.AgentAccountModel>): AI.AgentAccountM
input: Array.from(new Set((model?.input || []).filter((value) => value === 'text' || value === 'image'))),
});
const loadProviders = async () => {
if (Object.keys(providerModels.value).length > 0) {
return;
}
const res = await getAgentProviders();
const data = res.data || [];
const blockedProviders = new Set(['ark-coding-plan', 'bailian-coding-plan']);
const filteredData = isIntl.value ? data.filter((item) => !blockedProviders.has(item.provider)) : data;
providerModels.value = filteredData.reduce((acc, item) => {
acc[item.provider] = item.models || [];
return acc;
}, {} as Record<string, AI.ProviderModelInfo[]>);
};
const search = async () => {
if (!account.id) {
items.value = [];
@@ -213,15 +172,6 @@ const resetForm = () => {
const openCreate = () => {
resetForm();
const firstModel = providerModelOptions.value[0];
if (firstModel) {
form.id = firstModel.id;
form.name = firstModel.name || firstModel.id;
form.contextWindow = firstModel.contextWindow || 128000;
form.maxTokens = firstModel.maxTokens || 8192;
form.reasoning = firstModel.reasoning;
form.input = firstModel.input?.length ? [...firstModel.input] : ['text'];
}
editorOpen.value = true;
};
@@ -230,21 +180,6 @@ const openEdit = (row: AI.AgentAccountModel) => {
editorOpen.value = true;
};
const handleModelIDChange = (value: string) => {
form.id = String(value || '').trim();
const matched = providerModelOptions.value.find((item) => item.id === form.id);
if (!matched) {
return;
}
if (!form.name) {
form.name = matched.name || matched.id;
}
form.contextWindow = matched.contextWindow || form.contextWindow || 128000;
form.maxTokens = matched.maxTokens || form.maxTokens || 8192;
form.reasoning = matched.reasoning;
form.input = matched.input?.length ? [...matched.input] : ['text'];
};
const submit = async () => {
if (!account.id || !formRef.value) {
return;
@@ -319,7 +254,6 @@ const openDrawer = async (row: AI.AgentAccountItem) => {
account.name = row.name;
account.provider = row.provider;
open.value = true;
await loadProviders();
await search();
};