From 6b20ff0b13bbf6d18a111dc887b901e8a77ed9c8 Mon Sep 17 00:00:00 2001
From: CityFun <31820853+zhengkunwang223@users.noreply.github.com>
Date: Tue, 15 Sep 2026 18:36:24 +0800
Subject: [PATCH] feat: OpenClaw supports configuring model Max Tokens (#13818)
---
agent/app/dto/agents.go | 23 ++-
agent/app/service/agents.go | 5 +-
agent/app/service/agents_channels.go | 4 +
agent/app/service/agents_utils.go | 155 +++++++++++++++++-
frontend/src/api/interface/ai.ts | 9 +
frontend/src/lang/modules/en.ts | 8 +
frontend/src/lang/modules/es-es.ts | 8 +
frontend/src/lang/modules/fa.ts | 8 +
frontend/src/lang/modules/ja.ts | 8 +
frontend/src/lang/modules/ko.ts | 8 +
frontend/src/lang/modules/lo.ts | 8 +
frontend/src/lang/modules/ms.ts | 8 +
frontend/src/lang/modules/pt-br.ts | 8 +
frontend/src/lang/modules/ru.ts | 8 +
frontend/src/lang/modules/tr.ts | 8 +
frontend/src/lang/modules/zh-Hant.ts | 7 +
frontend/src/lang/modules/zh.ts | 7 +
.../ai/agents/agent/config/tabs/model.vue | 83 ++++++++++
18 files changed, 359 insertions(+), 14 deletions(-)
diff --git a/agent/app/dto/agents.go b/agent/app/dto/agents.go
index 5a3e69709..30305882e 100644
--- a/agent/app/dto/agents.go
+++ b/agent/app/dto/agents.go
@@ -162,16 +162,25 @@ type AgentWebsiteBindReq struct {
}
type AgentModelConfigUpdateReq struct {
- AgentID uint `json:"agentId" validate:"required"`
- AccountID uint `json:"accountId" validate:"required"`
- Model string `json:"model" validate:"required"`
- Fallbacks []string `json:"fallbacks"`
+ AgentID uint `json:"agentId" validate:"required"`
+ AccountID uint `json:"accountId" validate:"required"`
+ Model string `json:"model" validate:"required"`
+ Fallbacks []string `json:"fallbacks"`
+ Metadata []AgentModelMetadata `json:"metadata" validate:"dive"`
}
type AgentModelConfig struct {
- AccountID uint `json:"accountId"`
- Model string `json:"model"`
- Fallbacks []string `json:"fallbacks"`
+ AccountID uint `json:"accountId"`
+ Model string `json:"model"`
+ Fallbacks []string `json:"fallbacks"`
+ Metadata []AgentModelMetadata `json:"metadata"`
+}
+
+type AgentModelMetadata struct {
+ Model string `json:"model" validate:"required"`
+ InputMode string `json:"inputMode" validate:"required,oneof=auto text image"`
+ ContextWindow int `json:"contextWindow" validate:"min=0"`
+ MaxTokens int `json:"maxTokens" validate:"min=0"`
}
type AgentHermesChatSessionItem struct {
diff --git a/agent/app/service/agents.go b/agent/app/service/agents.go
index e6ada78f5..f4d3d2c3e 100644
--- a/agent/app/service/agents.go
+++ b/agent/app/service/agents.go
@@ -936,6 +936,7 @@ func (a AgentService) GetModelConfig(req dto.AgentIDReq) (*dto.AgentModelConfig,
AccountID: agent.AccountID,
Model: model,
Fallbacks: extractOpenclawFallbackModelIDs(conf, account, models, model),
+ Metadata: extractOpenclawModelMetadata(conf, account, models),
}, nil
}
@@ -967,7 +968,7 @@ func (a AgentService) UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error
if agent.AgentType != constant.AppOpenclaw {
return fmt.Errorf("%s does not support", agent.AgentType)
}
- if err := writeOpenclawConfig(confDir, account, modelName, agent.Token, nil, req.Fallbacks); err != nil {
+ if err := writeOpenclawConfig(confDir, account, modelName, agent.Token, nil, req.Fallbacks, req.Metadata); err != nil {
return err
}
}
@@ -1684,7 +1685,7 @@ func (a AgentService) syncAgentsByAccount(account *model.AgentAccount) error {
return err
}
fallbacks := extractOpenclawFallbackModelIDs(conf, account, accountModels, selectedAccountModel.ID)
- if err := writeOpenclawConfig(confDir, account, modelName, agent.Token, nil, fallbacks); err != nil {
+ if err := writeOpenclawConfig(confDir, account, modelName, agent.Token, nil, fallbacks, nil); err != nil {
return err
}
case constant.AppHermesAgent:
diff --git a/agent/app/service/agents_channels.go b/agent/app/service/agents_channels.go
index a16e13dba..f898a328a 100644
--- a/agent/app/service/agents_channels.go
+++ b/agent/app/service/agents_channels.go
@@ -1354,6 +1354,10 @@ func installOpenclawPlugin(mgr *cmd.CommandHelper, containerName, spec, pluginID
} else if slices.Contains(options, "--dangerously-force-unsafe-install") {
args = append(args, "--dangerously-force-unsafe-install")
}
+ // Source confirmation does not grant the selected channel plugin's capabilities.
+ if slices.Contains(options, "--accept-capabilities") {
+ args = append(args, "--accept-capabilities")
+ }
return mgr.Run("docker", args...)
}
diff --git a/agent/app/service/agents_utils.go b/agent/app/service/agents_utils.go
index 068db9bd2..94951826f 100644
--- a/agent/app/service/agents_utils.go
+++ b/agent/app/service/agents_utils.go
@@ -10,6 +10,7 @@ import (
"net/url"
"path"
"regexp"
+ "slices"
"strconv"
"strings"
"time"
@@ -737,9 +738,11 @@ type modelProvider struct {
}
type modelEntry struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Input []string `json:"input,omitempty"`
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Input []string `json:"input,omitempty"`
+ ContextWindow int `json:"contextWindow,omitempty"`
+ MaxTokens int `json:"maxTokens,omitempty"`
}
func requiresOpenclawProviderModels(provider string) bool {
@@ -767,7 +770,7 @@ type browserConfig struct {
DefaultProfile string `json:"defaultProfile"`
}
-func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName, token string, allowedOrigins []string, fallbacks []string) error {
+func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName, token string, allowedOrigins []string, fallbacks []string, metadata []dto.AgentModelMetadata) error {
if strings.TrimSpace(confDir) == "" {
return fmt.Errorf("config dir is required")
}
@@ -852,6 +855,7 @@ func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName,
}
conf = initial
} else {
+ preserveOpenclawModelMetadata(conf, cfg.Models)
if err := applyOpenclawModelsConfig(conf, cfg.Models); err != nil {
return err
}
@@ -906,6 +910,9 @@ func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName,
if allowedOrigins != nil {
setSecurityConfig(conf, dto.AgentSecurityConfig{AllowedOrigins: allowedOrigins})
}
+ if err := applyOpenclawModelMetadata(conf, account, metadata); err != nil {
+ return err
+ }
if err := writeOpenclawConfigRaw(configPath, conf); err != nil {
return err
}
@@ -920,6 +927,144 @@ func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName,
return writeAgentEnvMap(path.Join(confDir, ".env"), envMap, order)
}
+func readOpenclawModelsConfig(conf map[string]interface{}) *modelsConfig {
+ raw, ok := conf["models"]
+ if !ok {
+ return nil
+ }
+ payload, err := json.Marshal(raw)
+ if err != nil {
+ return nil
+ }
+ var models modelsConfig
+ if err := json.Unmarshal(payload, &models); err != nil {
+ return nil
+ }
+ return &models
+}
+
+func preserveOpenclawModelMetadata(conf map[string]interface{}, next *modelsConfig) {
+ current := readOpenclawModelsConfig(conf)
+ if current == nil || next == nil {
+ return
+ }
+ for providerID, nextProvider := range next.Providers {
+ currentProvider, ok := current.Providers[providerID]
+ if !ok {
+ continue
+ }
+ byID := make(map[string]modelEntry, len(currentProvider.Models))
+ for _, entry := range currentProvider.Models {
+ byID[entry.ID] = entry
+ }
+ for index := range nextProvider.Models {
+ currentEntry, ok := byID[nextProvider.Models[index].ID]
+ if !ok {
+ continue
+ }
+ nextProvider.Models[index].Input = currentEntry.Input
+ nextProvider.Models[index].ContextWindow = currentEntry.ContextWindow
+ nextProvider.Models[index].MaxTokens = currentEntry.MaxTokens
+ }
+ next.Providers[providerID] = nextProvider
+ }
+}
+
+func extractOpenclawModelMetadata(conf map[string]interface{}, account *model.AgentAccount, accountModels []dto.AgentAccountModel) []dto.AgentModelMetadata {
+ result := make([]dto.AgentModelMetadata, 0, len(accountModels))
+ configured := readOpenclawModelsConfig(conf)
+ for _, item := range accountModels {
+ _, inferred, providerID, _, err := buildOpenclawAccountModelConfig(account, item)
+ if err != nil {
+ continue
+ }
+ metadata := dto.AgentModelMetadata{Model: item.ID, InputMode: "auto"}
+ if configured != nil {
+ for _, entry := range configured.Providers[providerID].Models {
+ if entry.ID != inferred.ID {
+ continue
+ }
+ metadata.ContextWindow = entry.ContextWindow
+ metadata.MaxTokens = entry.MaxTokens
+ if len(entry.Input) > 0 && !slices.Equal(entry.Input, inferred.Input) {
+ if slices.Contains(entry.Input, "image") {
+ metadata.InputMode = "image"
+ } else {
+ metadata.InputMode = "text"
+ }
+ }
+ break
+ }
+ }
+ result = append(result, metadata)
+ }
+ return result
+}
+
+func applyOpenclawModelMetadata(conf map[string]interface{}, account *model.AgentAccount, requested []dto.AgentModelMetadata) error {
+ if len(requested) == 0 {
+ return nil
+ }
+ configured := readOpenclawModelsConfig(conf)
+ if configured == nil {
+ return fmt.Errorf("model metadata is not supported for provider %s", account.Provider)
+ }
+ accountModels, err := loadAgentAccountModels(account)
+ if err != nil {
+ return err
+ }
+ available := make(map[string]dto.AgentAccountModel, len(accountModels))
+ for _, item := range accountModels {
+ available[item.ID] = item
+ }
+ seen := make(map[string]struct{}, len(requested))
+ for _, metadata := range requested {
+ item, ok := available[metadata.Model]
+ if !ok {
+ return buserr.New("ErrAgentModelNotInAccount")
+ }
+ if _, ok := seen[metadata.Model]; ok {
+ return fmt.Errorf("duplicate model metadata: %s", metadata.Model)
+ }
+ seen[metadata.Model] = struct{}{}
+ _, inferred, providerID, _, err := buildOpenclawAccountModelConfig(account, item)
+ if err != nil {
+ return err
+ }
+ provider := configured.Providers[providerID]
+ found := false
+ for index := range provider.Models {
+ if provider.Models[index].ID != inferred.ID {
+ continue
+ }
+ found = true
+ provider.Models[index].ContextWindow = metadata.ContextWindow
+ provider.Models[index].MaxTokens = metadata.MaxTokens
+ switch metadata.InputMode {
+ case "auto":
+ provider.Models[index].Input = inferred.Input
+ case "text":
+ provider.Models[index].Input = []string{"text"}
+ case "image":
+ provider.Models[index].Input = []string{"text", "image"}
+ default:
+ return fmt.Errorf("unsupported model input mode: %s", metadata.InputMode)
+ }
+ break
+ }
+ if !found {
+ return buserr.New("ErrAgentModelNotInAccount")
+ }
+ configured.Providers[providerID] = provider
+ }
+ modelsMap, err := structToMap(configured)
+ if err != nil {
+ return err
+ }
+ conf["models"] = modelsMap
+ return nil
+}
+
func resolveOpenclawFallbackModels(account *model.AgentAccount, primaryModel string, fallbackIDs []string) ([]string, error) {
accountModels, err := loadAgentAccountModels(account)
if err != nil {
@@ -1041,7 +1186,7 @@ func prepareOpenclawInstallFiles(appInstall *model.AppInstall, account *model.Ag
return fmt.Errorf("app install is required")
}
confDir := path.Join(appInstall.GetPath(), "data", "conf")
- if err := writeOpenclawConfig(confDir, account, modelName, token, allowedOrigins, nil); err != nil {
+ if err := writeOpenclawConfig(confDir, account, modelName, token, allowedOrigins, nil, nil); err != nil {
return err
}
dataDir := path.Join(appInstall.GetPath(), "data")
diff --git a/frontend/src/api/interface/ai.ts b/frontend/src/api/interface/ai.ts
index 54809915c..74d7449e6 100644
--- a/frontend/src/api/interface/ai.ts
+++ b/frontend/src/api/interface/ai.ts
@@ -426,12 +426,21 @@ export namespace AI {
accountId: number;
model: string;
fallbacks: string[];
+ metadata: AgentModelMetadata[];
}
export interface AgentModelConfig {
accountId: number;
model: string;
fallbacks: string[];
+ metadata: AgentModelMetadata[];
+ }
+
+ export interface AgentModelMetadata {
+ model: string;
+ inputMode: 'auto' | 'text' | 'image';
+ contextWindow: number;
+ maxTokens: number;
}
export interface AgentHermesChatSessionItem {
diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts
index db08922d5..cbe701598 100644
--- a/frontend/src/lang/modules/en.ts
+++ b/frontend/src/lang/modules/en.ts
@@ -758,6 +758,14 @@ const message = {
addFallbackModel: 'Add Fallback Model',
fallbackModelsEmpty: 'No fallback models configured',
modelInputTypes: 'Input Types',
+ modelMetadata: 'Model Capabilities',
+ modelMetadataHelper:
+ 'These settings apply only to this OpenClaw instance. Leave values empty to use OpenClaw or provider defaults.',
+ inputModeAuto: 'Auto detect',
+ inputModeText: 'Text only',
+ inputModeImage: 'Text and images',
+ contextWindow: 'Context Window',
+ useModelDefault: 'Use model default',
reasoning: 'Reasoning Model',
verified: 'Verified',
verifySkipped: 'No verification',
diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts
index d7f18eb58..efa27abbe 100644
--- a/frontend/src/lang/modules/es-es.ts
+++ b/frontend/src/lang/modules/es-es.ts
@@ -764,6 +764,14 @@ const message = {
addFallbackModel: 'Agregar modelo de respaldo',
fallbackModelsEmpty: 'No hay modelos de respaldo configurados',
modelInputTypes: 'Tipos de entrada',
+ modelMetadata: 'Capacidades del modelo',
+ modelMetadataHelper:
+ 'Estos ajustes solo se aplican a esta instancia de OpenClaw. Déjelos vacíos para usar los valores predeterminados de OpenClaw o del proveedor.',
+ inputModeAuto: 'Detectar automáticamente',
+ inputModeText: 'Solo texto',
+ inputModeImage: 'Texto e imágenes',
+ contextWindow: 'Ventana de contexto',
+ useModelDefault: 'Usar valor predeterminado del modelo',
reasoning: 'Modelo de razonamiento',
verified: 'Verificado',
verifySkipped: 'Sin verificación',
diff --git a/frontend/src/lang/modules/fa.ts b/frontend/src/lang/modules/fa.ts
index 8f4afbcb4..1b13fbb74 100644
--- a/frontend/src/lang/modules/fa.ts
+++ b/frontend/src/lang/modules/fa.ts
@@ -755,6 +755,14 @@ const message = {
addFallbackModel: 'افزودن مدل جایگزین',
fallbackModelsEmpty: 'هیچ مدل جایگزینی پیکربندی نشده است',
modelInputTypes: 'انواع ورودی',
+ modelMetadata: 'قابلیتهای مدل',
+ modelMetadataHelper:
+ 'این تنظیمات فقط برای نمونه فعلی OpenClaw اعمال میشوند. برای استفاده از پیشفرض OpenClaw یا ارائهدهنده، فیلدها را خالی بگذارید.',
+ inputModeAuto: 'تشخیص خودکار',
+ inputModeText: 'فقط متن',
+ inputModeImage: 'متن و تصویر',
+ contextWindow: 'پنجره زمینه',
+ useModelDefault: 'استفاده از پیشفرض مدل',
reasoning: 'مدل استدلالی',
verified: 'تأیید شده',
verifySkipped: 'بدون تأیید',
diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts
index cb16eda14..58bb66b15 100644
--- a/frontend/src/lang/modules/ja.ts
+++ b/frontend/src/lang/modules/ja.ts
@@ -759,6 +759,14 @@ const message = {
addFallbackModel: 'フォールバックモデルを追加',
fallbackModelsEmpty: 'フォールバックモデルは未設定です',
modelInputTypes: '入力タイプ',
+ modelMetadata: 'モデル機能設定',
+ modelMetadataHelper:
+ 'この設定は現在の OpenClaw にのみ適用されます。空欄の場合は OpenClaw またはプロバイダーの既定値を使用します。',
+ inputModeAuto: '自動判定',
+ inputModeText: 'テキストのみ',
+ inputModeImage: 'テキストと画像',
+ contextWindow: 'コンテキストウィンドウ',
+ useModelDefault: 'モデルの既定値を使用',
reasoning: '推論モデル',
verified: '検証済み',
verifySkipped: '検証なし',
diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts
index b56625f19..f5da2e7c3 100644
--- a/frontend/src/lang/modules/ko.ts
+++ b/frontend/src/lang/modules/ko.ts
@@ -749,6 +749,14 @@ const message = {
addFallbackModel: '폴백 모델 추가',
fallbackModelsEmpty: '구성된 폴백 모델이 없습니다',
modelInputTypes: '입력 유형',
+ modelMetadata: '모델 기능 설정',
+ modelMetadataHelper:
+ '이 설정은 현재 OpenClaw에만 적용됩니다. 비워 두면 OpenClaw 또는 공급자의 기본값을 사용합니다.',
+ inputModeAuto: '자동 감지',
+ inputModeText: '텍스트만',
+ inputModeImage: '텍스트 및 이미지',
+ contextWindow: '컨텍스트 창',
+ useModelDefault: '모델 기본값 사용',
reasoning: '추론 모델',
verified: '검증됨',
verifySkipped: '검증 안 함',
diff --git a/frontend/src/lang/modules/lo.ts b/frontend/src/lang/modules/lo.ts
index 34585bd6b..ee1ba5243 100644
--- a/frontend/src/lang/modules/lo.ts
+++ b/frontend/src/lang/modules/lo.ts
@@ -750,6 +750,14 @@ const message = {
addFallbackModel: 'ເພີ່ມໂມເດວສຳຮອງ',
fallbackModelsEmpty: 'ຍັງບໍ່ໄດ້ຕັ້ງຄ່າໂມເດວສຳຮອງ',
modelInputTypes: 'ປະເພດຂໍ້ມູນຂາເຂົ້າ',
+ modelMetadata: 'ຄວາມສາມາດຂອງໂມເດວ',
+ modelMetadataHelper:
+ 'ການຕັ້ງຄ່ານີ້ໃຊ້ກັບ OpenClaw ປັດຈຸບັນເທົ່ານັ້ນ. ປ່ອຍຫວ່າງເພື່ອໃຊ້ຄ່າເລີ່ມຕົ້ນຂອງ OpenClaw ຫຼືຜູ້ໃຫ້ບໍລິການ.',
+ inputModeAuto: 'ກວດຫາອັດຕະໂນມັດ',
+ inputModeText: 'ຂໍ້ຄວາມເທົ່ານັ້ນ',
+ inputModeImage: 'ຂໍ້ຄວາມແລະຮູບພາບ',
+ contextWindow: 'ຂອບເຂດບໍລິບົດ',
+ useModelDefault: 'ໃຊ້ຄ່າເລີ່ມຕົ້ນຂອງໂມເດວ',
reasoning: 'ໂມເດວການໃຊ້ເຫດຜົນ',
verified: 'ກວດສອບແລ້ວ',
verifySkipped: 'ບໍ່ມີການກວດສອບ',
diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts
index 6df7d9a5d..958fa1f40 100644
--- a/frontend/src/lang/modules/ms.ts
+++ b/frontend/src/lang/modules/ms.ts
@@ -767,6 +767,14 @@ const message = {
addFallbackModel: 'Tambah model fallback',
fallbackModelsEmpty: 'Tiada model fallback dikonfigurasikan',
modelInputTypes: 'Jenis input',
+ modelMetadata: 'Keupayaan model',
+ modelMetadataHelper:
+ 'Tetapan ini hanya digunakan pada tika OpenClaw semasa. Biarkan kosong untuk menggunakan nilai lalai OpenClaw atau penyedia.',
+ inputModeAuto: 'Kesan secara automatik',
+ inputModeText: 'Teks sahaja',
+ inputModeImage: 'Teks dan imej',
+ contextWindow: 'Tetingkap konteks',
+ useModelDefault: 'Gunakan lalai model',
reasoning: 'Model penaakulan',
verified: 'Disahkan',
verifySkipped: 'Tanpa pengesahan',
diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts
index 1f607c590..777174d8b 100644
--- a/frontend/src/lang/modules/pt-br.ts
+++ b/frontend/src/lang/modules/pt-br.ts
@@ -761,6 +761,14 @@ const message = {
addFallbackModel: 'Adicionar modelo de fallback',
fallbackModelsEmpty: 'Nenhum modelo de fallback configurado',
modelInputTypes: 'Tipos de entrada',
+ modelMetadata: 'Recursos do modelo',
+ modelMetadataHelper:
+ 'Estas configurações se aplicam somente a esta instância do OpenClaw. Deixe em branco para usar os padrões do OpenClaw ou do provedor.',
+ inputModeAuto: 'Detectar automaticamente',
+ inputModeText: 'Somente texto',
+ inputModeImage: 'Texto e imagens',
+ contextWindow: 'Janela de contexto',
+ useModelDefault: 'Usar padrão do modelo',
reasoning: 'Modelo de raciocínio',
verified: 'Verificado',
verifySkipped: 'Sem verificação',
diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts
index 370c2e4ab..84c70e683 100644
--- a/frontend/src/lang/modules/ru.ts
+++ b/frontend/src/lang/modules/ru.ts
@@ -756,6 +756,14 @@ const message = {
addFallbackModel: 'Добавить резервную модель',
fallbackModelsEmpty: 'Резервные модели не настроены',
modelInputTypes: 'Типы ввода',
+ modelMetadata: 'Возможности модели',
+ modelMetadataHelper:
+ 'Эти настройки применяются только к текущему экземпляру OpenClaw. Оставьте поля пустыми, чтобы использовать значения OpenClaw или поставщика.',
+ inputModeAuto: 'Определять автоматически',
+ inputModeText: 'Только текст',
+ inputModeImage: 'Текст и изображения',
+ contextWindow: 'Контекстное окно',
+ useModelDefault: 'Использовать значение модели',
reasoning: 'Модель рассуждений',
verified: 'Проверено',
verifySkipped: 'Без проверки',
diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts
index fad6f2642..d2af72788 100644
--- a/frontend/src/lang/modules/tr.ts
+++ b/frontend/src/lang/modules/tr.ts
@@ -762,6 +762,14 @@ const message = {
addFallbackModel: 'Yedek model ekle',
fallbackModelsEmpty: 'Yapılandırılmış yedek model yok',
modelInputTypes: 'Girdi türleri',
+ modelMetadata: 'Model yetenekleri',
+ modelMetadataHelper:
+ 'Bu ayarlar yalnızca mevcut OpenClaw örneğine uygulanır. OpenClaw veya sağlayıcı varsayılanlarını kullanmak için boş bırakın.',
+ inputModeAuto: 'Otomatik algıla',
+ inputModeText: 'Yalnızca metin',
+ inputModeImage: 'Metin ve görseller',
+ contextWindow: 'Bağlam penceresi',
+ useModelDefault: 'Model varsayılanını kullan',
reasoning: 'Akıl yürütme modeli',
verified: 'Doğrulandı',
verifySkipped: 'Doğrulama yok',
diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts
index 0c613406a..bb1d60f47 100644
--- a/frontend/src/lang/modules/zh-Hant.ts
+++ b/frontend/src/lang/modules/zh-Hant.ts
@@ -729,6 +729,13 @@ const message = {
addFallbackModel: '新增備用模型',
fallbackModelsEmpty: '尚未設定備用模型',
modelInputTypes: '輸入類型',
+ modelMetadata: '模型能力設定',
+ modelMetadataHelper: '設定僅套用於目前的 OpenClaw。留空時使用 OpenClaw 或模型供應商的預設值。',
+ inputModeAuto: '自動識別',
+ inputModeText: '僅文字',
+ inputModeImage: '文字和圖片',
+ contextWindow: '上下文視窗',
+ useModelDefault: '使用模型預設值',
reasoning: '推理模型',
verified: '驗證狀態',
verifySkipped: '略過驗證',
diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts
index fed9b3d61..e81316518 100644
--- a/frontend/src/lang/modules/zh.ts
+++ b/frontend/src/lang/modules/zh.ts
@@ -746,6 +746,13 @@ const message = {
addFallbackModel: '新增备用模型',
fallbackModelsEmpty: '尚未配置备用模型',
modelInputTypes: '输入类型',
+ modelMetadata: '模型能力配置',
+ modelMetadataHelper: '配置仅作用于当前 OpenClaw。留空时使用 OpenClaw 或模型供应商的默认值。',
+ inputModeAuto: '自动识别',
+ inputModeText: '仅文本',
+ inputModeImage: '文本和图片',
+ contextWindow: '上下文窗口',
+ useModelDefault: '使用模型默认值',
reasoning: '推理模型',
verified: '验证状态',
verifySkipped: '不验证',
diff --git a/frontend/src/views/ai/agents/agent/config/tabs/model.vue b/frontend/src/views/ai/agents/agent/config/tabs/model.vue
index 32f0cac81..45c0b39fe 100644
--- a/frontend/src/views/ai/agents/agent/config/tabs/model.vue
+++ b/frontend/src/views/ai/agents/agent/config/tabs/model.vue
@@ -55,6 +55,46 @@
+
+ {{ t('aiTools.agents.modelMetadata') }}
+ {{ t('aiTools.agents.modelMetadataHelper') }}
+
+
+
+ {{ modelOptions.find((item) => item.id === row.model)?.name || row.model }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ t('commons.button.save') }}
@@ -88,6 +128,16 @@ const agentType = ref('openclaw');
const accountOptions = ref([]);
const modelOptions = ref([]);
const showFallbacks = computed(() => agentType.value === 'openclaw');
+const metadataRows = ref<
+ Array<
+ Omit & {
+ contextWindow?: number;
+ maxTokens?: number;
+ }
+ >
+>([]);
+const selectedAccount = computed(() => accountOptions.value.find((item) => item.id === form.accountId));
+const showMetadata = computed(() => agentType.value === 'openclaw' && selectedAccount.value?.provider !== 'gemini');
interface AgentModelLoadParams {
agentId: number;
@@ -145,11 +195,25 @@ const syncFallbacks = (fallbacks?: string[]) => {
}
};
+const syncMetadata = (metadata: AI.AgentModelMetadata[] = []) => {
+ const configured = new Map(metadata.map((item) => [item.model, item]));
+ metadataRows.value = modelOptions.value.map((model) => {
+ const item = configured.get(model.id);
+ return {
+ model: model.id,
+ inputMode: item?.inputMode || 'auto',
+ contextWindow: item?.contextWindow || undefined,
+ maxTokens: item?.maxTokens || undefined,
+ };
+ });
+};
+
const handleAccountChange = () => {
setModelOptionsByAccount(form.accountId);
ensureSelectedModel();
form.fallbacks = [];
fallbackCandidate.value = '';
+ syncMetadata();
};
const handleModelChange = () => {
@@ -199,6 +263,7 @@ const load = async (params: AgentModelLoadParams) => {
form.model = '';
form.fallbacks = [];
modelOptions.value = [];
+ metadataRows.value = [];
return;
}
const config = configRes.data;
@@ -208,6 +273,7 @@ const load = async (params: AgentModelLoadParams) => {
setModelOptionsByAccount(currentAccount.id);
ensureSelectedModel(config.model);
syncFallbacks(showFallbacks.value ? config.fallbacks || [] : []);
+ syncMetadata(showMetadata.value ? config.metadata || [] : []);
} finally {
loading.value = false;
}
@@ -225,6 +291,14 @@ const saveModel = async () => {
accountId: form.accountId,
model: form.model,
fallbacks: showFallbacks.value ? form.fallbacks : [],
+ metadata: showMetadata.value
+ ? metadataRows.value.map((item) => ({
+ model: item.model,
+ inputMode: item.inputMode,
+ contextWindow: item.contextWindow || 0,
+ maxTokens: item.maxTokens || 0,
+ }))
+ : [],
});
MsgSuccess(t('aiTools.agents.switchModelSuccess'));
emit('updated');
@@ -257,4 +331,13 @@ defineExpose({
padding: 12px 0;
color: var(--el-text-color-secondary);
}
+
+.metadata-help {
+ display: block;
+ margin: -8px 0 12px;
+}
+
+:deep(.el-input-number) {
+ width: 100%;
+}