feat(ai-agents): add anthropic-messages support for custom account apiType (#12013)

This commit is contained in:
CityFun
2026-03-02 13:43:06 +08:00
committed by GitHub
parent 625cd9f32f
commit 8cead06d7b
15 changed files with 38 additions and 35 deletions
+1 -1
View File
@@ -137,7 +137,7 @@ var catalog = map[string]Meta{
Key: "kimi-coding",
DisplayName: "Kimi Coding",
Sort: 100,
DefaultBaseURL: "https://api.moonshot.cn/anthropic/v1",
DefaultBaseURL: "https://kimi-code.moonshot.cn/api",
EnvKey: "KIMI_API_KEY",
Enabled: true,
Models: []Model{
+32 -14
View File
@@ -94,6 +94,14 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
return nil, buserr.New("ErrAgentApiKeyRequired")
}
apiType, maxTokens, contextWindow := resolveRuntimeParams(provider, account.APIType, account.MaxTokens, account.ContextWindow)
runtimeModel := strings.TrimSpace(req.Model)
if provider == "custom" {
primaryID := customPrimaryModelID(req.Model)
if primaryID == "" {
primaryID = normalizeCustomModel(req.Model)
}
runtimeModel = "custom/" + primaryID
}
if err := checkPortExist(req.WebUIPort); err != nil {
return nil, err
}
@@ -121,7 +129,7 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
}
params := map[string]interface{}{
"PROVIDER": provider,
"MODEL": req.Model,
"MODEL": runtimeModel,
"API_TYPE": apiType,
"MAX_TOKENS": maxTokens,
"CONTEXT_WINDOW": contextWindow,
@@ -291,7 +299,7 @@ func (a AgentService) UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error
if modelName == "" {
return buserr.New("ErrAgentProviderMismatch")
}
if !strings.HasPrefix(modelName, provider+"/") {
if provider != "custom" && !strings.HasPrefix(modelName, provider+"/") {
return buserr.New("ErrAgentProviderMismatch")
}
baseURL := strings.TrimSpace(account.BaseURL)
@@ -526,7 +534,7 @@ func (a AgentService) VerifyAccount(req dto.AgentAccountVerifyReq) error {
if provider == "ollama" {
return nil
}
if provider == "custom" {
if provider == "custom" || provider == "kimi-coding" {
return nil
}
return verifyProvider(provider, baseURL, apiKey)
@@ -1412,20 +1420,15 @@ func writeOpenclawConfig(confDir, provider, modelName, apiType string, maxTokens
},
}
} else if provider == "custom" {
primary := modelName
if !strings.Contains(primary, "/") {
primary = "custom/" + strings.TrimSpace(primary)
customModelID := normalizeCustomModel(modelName)
primaryID := customPrimaryModelID(customModelID)
if primaryID == "" {
primaryID = customModelID
}
primary := "custom/" + primaryID
cfg.Agents.Defaults.Model.Primary = primary
base := strings.TrimSpace(baseURL)
plainKey := strings.TrimSpace(apiKey)
if !strings.Contains(modelName, "/") {
modelName = primary
}
customModelID := modelID
if parts := strings.SplitN(modelName, "/", 2); len(parts) == 2 {
customModelID = parts[1]
}
useAPIType, useMaxTokens, useContextWindow := resolveRuntimeParams(provider, apiType, maxTokens, contextWindow)
cfg.Models = &modelsConfig{
Mode: "merge",
@@ -1702,6 +1705,21 @@ func normalizeCustomModel(modelName string) string {
return trim
}
func customPrimaryModelID(modelName string) string {
trim := normalizeCustomModel(modelName)
if trim == "" {
return ""
}
parts := strings.Split(trim, "/")
for i := len(parts) - 1; i >= 0; i-- {
part := strings.TrimSpace(parts[i])
if part != "" {
return part
}
}
return ""
}
func normalizeAPIType(apiType string) string {
trim := strings.ToLower(strings.TrimSpace(apiType))
if trim == "" {
@@ -1712,7 +1730,7 @@ func normalizeAPIType(apiType string) string {
func isSupportedAPIType(apiType string) bool {
switch normalizeAPIType(apiType) {
case "openai-completions", "openai-responses":
case "openai-completions", "openai-responses", "anthropic-messages":
return true
default:
return false
-1
View File
@@ -720,7 +720,6 @@ const message = {
saveSuccess: 'Saved successfully',
pairingCodeRequired: 'Enter pairing code',
pairingApproveSuccess: 'Pairing approved successfully',
customModelHelper: 'For custom model accounts, model names must start with custom/.',
customProviderHelper: 'Custom model providers do not validate whether the account is available.',
},
model: {
-1
View File
@@ -716,7 +716,6 @@ const message = {
saveSuccess: 'Saved successfully',
pairingCodeRequired: 'Please enter pairing code',
pairingApproveSuccess: 'Pairing approved successfully',
customModelHelper: 'En la cuenta de modelo personalizada, el nombre del modelo debe empezar por custom/',
customProviderHelper: 'En el proveedor de modelo personalizado no se valida si la cuenta está disponible',
},
model: {
-1
View File
@@ -708,7 +708,6 @@ const message = {
saveSuccess: 'Saved successfully',
pairingCodeRequired: 'Please enter pairing code',
pairingApproveSuccess: 'Pairing approved successfully',
customModelHelper: 'カスタムモデルアカウントではモデル名は必ず custom/ で始めてください',
customProviderHelper: 'カスタムモデルプロバイダーではアカウントの有効性を検証しません',
},
model: {
-1
View File
@@ -703,7 +703,6 @@ const message = {
saveSuccess: 'Saved successfully',
pairingCodeRequired: 'Please enter pairing code',
pairingApproveSuccess: 'Pairing approved successfully',
customModelHelper: '사용자 정의 모델 계정의 모델명은 반드시 custom/ 로 시작해야 합니다',
customProviderHelper: '사용자 정의 모델 공급자는 계정 사용 가능 여부를 검증하지 않습니다',
},
model: {
-1
View File
@@ -719,7 +719,6 @@ const message = {
saveSuccess: 'Saved successfully',
pairingCodeRequired: 'Please enter pairing code',
pairingApproveSuccess: 'Pairing approved successfully',
customModelHelper: 'Akaun model tersuai, nama model mesti bermula dengan custom/',
customProviderHelper: 'Penyedia model tersuai tidak mengesahkan sama ada akaun boleh digunakan',
},
model: {
-1
View File
@@ -716,7 +716,6 @@ const message = {
saveSuccess: 'Saved successfully',
pairingCodeRequired: 'Please enter pairing code',
pairingApproveSuccess: 'Pairing approved successfully',
customModelHelper: 'Conta de modelo personalizada: o nome do modelo deve começar com custom/',
customProviderHelper: 'Provedores de modelo personalizados não validam se a conta está disponível',
},
model: {
-1
View File
@@ -711,7 +711,6 @@ const message = {
saveSuccess: 'Saved successfully',
pairingCodeRequired: 'Please enter pairing code',
pairingApproveSuccess: 'Pairing approved successfully',
customModelHelper: 'Для пользовательской учетной записи модели имя модели должно начинаться с custom/',
customProviderHelper: 'Для пользовательского провайдера модели доступность учетной записи не проверяется',
},
model: {
-1
View File
@@ -725,7 +725,6 @@ const message = {
saveSuccess: 'Saved successfully',
pairingCodeRequired: 'Please enter pairing code',
pairingApproveSuccess: 'Pairing approved successfully',
customModelHelper: 'Özel model hesabında model adı custom/ ile başlamalıdır',
customProviderHelper: 'Özel model sağlayıcısında hesabın kullanılabilirliği doğrulanmaz',
},
model: {
-1
View File
@@ -694,7 +694,6 @@ const message = {
saveSuccess: '保存成功',
pairingCodeRequired: '請輸入配對碼',
pairingApproveSuccess: '配對成功',
customModelHelper: '自訂模型帳號模型需固定以 custom/ 開頭',
customProviderHelper: '自訂模型供應商不驗證帳號是否可用',
},
model: {
-1
View File
@@ -678,7 +678,6 @@ const message = {
feishuSaveSuccess: '保存成功',
pairingCodeRequired: '请输入配对码',
pairingApproveSuccess: '配对成功',
customModelHelper: '自定义模型账号模型固定以 custom/ 开头',
customProviderHelper: '自定义模型供应商不验证账号是否可用',
},
model: {
@@ -60,9 +60,6 @@
<el-select v-else v-model="form.model" filterable @change="handleModelChange">
<el-option v-for="item in filteredModels" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<span class="input-help" v-if="form.provider == 'custom'">
{{ $t('aiTools.agents.customModelHelper') }}
</span>
</el-form-item>
<el-form-item :label="$t('aiTools.agents.baseUrl')" v-if="form.accountId" prop="baseURL">
<el-input v-model="form.baseURL" disabled />
@@ -256,7 +253,7 @@ const handleAccountChange = () => {
form.maxTokens = selected.maxTokens || 8192;
form.contextWindow = selected.contextWindow || 128000;
if (selected.provider === 'custom' && selected.model && !manualModel.value) {
form.model = `custom/${selected.model}`;
form.model = selected.model;
}
}
setDefaultModel();
@@ -274,7 +271,7 @@ const setDefaultModel = () => {
if (form.provider === 'custom') {
const selected = accountOptions.value.find((item) => item.id === form.accountId);
if (selected?.model && !form.model) {
form.model = `custom/${selected.model}`;
form.model = selected.model;
}
}
};
@@ -15,9 +15,6 @@
<el-select v-else v-model="form.model" filterable>
<el-option v-for="item in modelOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<span class="input-help" v-if="provdier == 'custom'">
{{ $t('aiTools.agents.customModelHelper') }}
</span>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="saving" @click="saveModel">
@@ -96,7 +93,7 @@ const handleAccountChange = () => {
provdier.value = selected.provider;
if (selected.provider === 'custom') {
form.manualModel = true;
form.model = selected.model ? `custom/${selected.model}` : form.model;
form.model = selected.model || form.model;
modelOptions.value = [];
return;
}
@@ -148,7 +145,7 @@ const load = async (agent: AI.AgentItem) => {
form.model = modelOptions.value.length > 0 ? modelOptions.value[0].id : '';
}
if (currentAccount.provider === 'custom' && currentAccount.model && !form.model) {
form.model = `custom/${currentAccount.model}`;
form.model = currentAccount.model;
}
} finally {
loading.value = false;
@@ -31,6 +31,7 @@
<el-select v-model="form.apiType">
<el-option label="openai-completions" value="openai-completions" />
<el-option label="openai-responses" value="openai-responses" />
<el-option label="anthropic-messages" value="anthropic-messages" />
</el-select>
</el-form-item>
<el-form-item label="Max Tokens" prop="maxTokens" v-if="form.provider === 'custom'">