feat(agents): map Claude Desktop context capabilities from model API
This commit is contained in:
1 parent
fb41286e97
commit
1edf647581
9 files changed
+210
-21
No files matched your search
@@ -388,6 +388,8 @@ fn prepare_catalog_with_sources(
|
||||
alias: optional_map_string(&entry.value, "display_name").filter(|display| {
|
||||
!display.eq_ignore_ascii_case(&string_value(&entry.value, "slug"))
|
||||
}),
|
||||
is_alias: false,
|
||||
context_window: positive_u64_value(entry.value.get("context_window")),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let values = entries
|
||||
|
||||
+107
-16
@@ -88,6 +88,7 @@ const CLAUDE_DESKTOP_PROFILE_ID: &str = "00000000-0000-4000-8000-000000831700";
|
||||
const CLAUDE_DESKTOP_OPUS_MODEL_ID: &str = "claude-opus-5";
|
||||
const CLAUDE_DESKTOP_SONNET_MODEL_ID: &str = "claude-sonnet-4-6";
|
||||
const CLAUDE_DESKTOP_HAIKU_MODEL_ID: &str = "claude-haiku-4-5";
|
||||
const CLAUDE_DESKTOP_EXTENDED_CONTEXT_WINDOW: u64 = 1_000_000;
|
||||
const MODEL_ALIAS_CONFIG_SECTIONS: &[&str] = &[
|
||||
"codex-api-key",
|
||||
"openai-compatibility",
|
||||
@@ -651,6 +652,10 @@ struct CodexModelCatalogUpdateResult {
|
||||
struct AgentModelOption {
|
||||
name: String,
|
||||
alias: Option<String>,
|
||||
#[serde(default)]
|
||||
is_alias: bool,
|
||||
#[serde(default)]
|
||||
context_window: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
@@ -2544,14 +2549,23 @@ fn parse_agent_model_options(payload: &serde_json::Value) -> Result<Vec<AgentMod
|
||||
.get("fork")
|
||||
.and_then(serde_json::Value::as_bool)
|
||||
.unwrap_or(false);
|
||||
let context_window = [
|
||||
"context_length",
|
||||
"contextLength",
|
||||
"ContextLength",
|
||||
"context_window",
|
||||
"contextWindow",
|
||||
]
|
||||
.into_iter()
|
||||
.find_map(|key| item.get(key).and_then(json_positive_u64));
|
||||
|
||||
if let Some(model_alias) = model_alias {
|
||||
if keep_original {
|
||||
append_agent_model_option(&mut models, &name, display_name);
|
||||
append_agent_model_option(&mut models, &name, display_name, false, context_window);
|
||||
}
|
||||
append_agent_model_option(&mut models, &model_alias, Some(name));
|
||||
append_agent_model_option(&mut models, &model_alias, Some(name), true, context_window);
|
||||
} else {
|
||||
append_agent_model_option(&mut models, &name, display_name);
|
||||
append_agent_model_option(&mut models, &name, display_name, false, context_window);
|
||||
}
|
||||
}
|
||||
Ok(models)
|
||||
@@ -2561,6 +2575,8 @@ fn append_agent_model_option(
|
||||
models: &mut Vec<AgentModelOption>,
|
||||
name: &str,
|
||||
alias: Option<String>,
|
||||
is_alias: bool,
|
||||
context_window: Option<u64>,
|
||||
) {
|
||||
let name = name.trim();
|
||||
if name.is_empty()
|
||||
@@ -2578,6 +2594,8 @@ fn append_agent_model_option(
|
||||
models.push(AgentModelOption {
|
||||
name: name.to_string(),
|
||||
alias,
|
||||
is_alias,
|
||||
context_window,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4452,6 +4470,7 @@ fn build_agent_updates_with_oauth(
|
||||
&root_base,
|
||||
api_key,
|
||||
model,
|
||||
models,
|
||||
claude_desktop_model_mappings,
|
||||
)
|
||||
.or_else(|_| {
|
||||
@@ -4460,6 +4479,7 @@ fn build_agent_updates_with_oauth(
|
||||
&root_base,
|
||||
api_key,
|
||||
model,
|
||||
models,
|
||||
claude_desktop_model_mappings,
|
||||
)
|
||||
})?,
|
||||
@@ -4658,6 +4678,8 @@ fn ordered_agent_models(
|
||||
ordered.push(AgentModelOption {
|
||||
name: selected_model.to_string(),
|
||||
alias: None,
|
||||
is_alias: false,
|
||||
context_window: None,
|
||||
});
|
||||
}
|
||||
for model in models {
|
||||
@@ -4683,6 +4705,7 @@ fn build_claude_desktop_profile(
|
||||
base_url: &str,
|
||||
api_key: &str,
|
||||
model: &str,
|
||||
models: &[AgentModelOption],
|
||||
mappings: Option<&ClaudeDesktopModelMappings>,
|
||||
) -> Result<String, String> {
|
||||
let mut root = parse_agent_json_object(existing, "Claude Desktop 网关配置")?;
|
||||
@@ -4710,13 +4733,13 @@ fn build_claude_desktop_profile(
|
||||
"inferenceProvider".to_string(),
|
||||
serde_json::json!("gateway"),
|
||||
);
|
||||
let _mappings = mappings
|
||||
let mappings = mappings
|
||||
.cloned()
|
||||
.unwrap_or_else(|| ClaudeDesktopModelMappings::all(model));
|
||||
let inference_models = vec![
|
||||
serde_json::json!(CLAUDE_DESKTOP_OPUS_MODEL_ID),
|
||||
serde_json::json!(CLAUDE_DESKTOP_SONNET_MODEL_ID),
|
||||
serde_json::json!(CLAUDE_DESKTOP_HAIKU_MODEL_ID),
|
||||
claude_desktop_inference_model(CLAUDE_DESKTOP_OPUS_MODEL_ID, &mappings.opus, models),
|
||||
claude_desktop_inference_model(CLAUDE_DESKTOP_SONNET_MODEL_ID, &mappings.sonnet, models),
|
||||
claude_desktop_inference_model(CLAUDE_DESKTOP_HAIKU_MODEL_ID, &mappings.haiku, models),
|
||||
];
|
||||
root.insert(
|
||||
"inferenceModels".to_string(),
|
||||
@@ -4725,6 +4748,24 @@ fn build_claude_desktop_profile(
|
||||
render_agent_json(root, "Claude Desktop 网关配置")
|
||||
}
|
||||
|
||||
fn claude_desktop_inference_model(
|
||||
route_model: &str,
|
||||
source_model: &str,
|
||||
models: &[AgentModelOption],
|
||||
) -> serde_json::Value {
|
||||
let context_window = models
|
||||
.iter()
|
||||
.find(|model| model.name.eq_ignore_ascii_case(source_model))
|
||||
.and_then(|model| model.context_window);
|
||||
let mut entry = serde_json::Map::new();
|
||||
entry.insert("name".to_string(), serde_json::json!(route_model));
|
||||
if context_window.is_some_and(|window| window >= CLAUDE_DESKTOP_EXTENDED_CONTEXT_WINDOW) {
|
||||
entry.insert("supports1m".to_string(), serde_json::json!(true));
|
||||
entry.insert("prefer1m".to_string(), serde_json::json!(true));
|
||||
}
|
||||
serde_json::Value::Object(entry)
|
||||
}
|
||||
|
||||
fn build_claude_desktop_meta(existing: Option<&str>) -> Result<String, String> {
|
||||
let mut root = parse_agent_json_object(existing, "Claude Desktop 配置索引")?;
|
||||
let entries = ensure_json_array_entry(&mut root, "entries");
|
||||
@@ -6890,6 +6931,8 @@ fn fresh_agent_contents(
|
||||
let models = [AgentModelOption {
|
||||
name: model.to_string(),
|
||||
alias: None,
|
||||
is_alias: false,
|
||||
context_window: None,
|
||||
}];
|
||||
fresh_agent_contents_with_oauth(client, port, api_key, model, &models, false, None)
|
||||
}
|
||||
@@ -6917,6 +6960,7 @@ fn fresh_agent_contents_with_oauth(
|
||||
&root_base,
|
||||
api_key,
|
||||
model,
|
||||
models,
|
||||
claude_desktop_model_mappings,
|
||||
)?,
|
||||
build_claude_desktop_meta(None)?,
|
||||
@@ -7727,6 +7771,8 @@ fn reset_agent_configuration_to_default(
|
||||
models: &[AgentModelOption {
|
||||
name: model.to_string(),
|
||||
alias: None,
|
||||
is_alias: false,
|
||||
context_window: None,
|
||||
}],
|
||||
codex_catalog,
|
||||
oauth_configuration: false,
|
||||
@@ -16715,6 +16761,8 @@ mod tests {
|
||||
.map(|name| AgentModelOption {
|
||||
name: (*name).to_string(),
|
||||
alias: None,
|
||||
is_alias: false,
|
||||
context_window: None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -16883,6 +16931,7 @@ mod tests {
|
||||
"http://127.0.0.1:8317",
|
||||
DEFAULT_API_KEY,
|
||||
"gpt-test",
|
||||
&[],
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -18195,6 +18244,7 @@ model:
|
||||
"http://127.0.0.1:8317",
|
||||
DEFAULT_API_KEY,
|
||||
"claude-sonnet-test",
|
||||
&[],
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -18213,9 +18263,9 @@ model:
|
||||
assert_eq!(
|
||||
profile["inferenceModels"],
|
||||
serde_json::json!([
|
||||
CLAUDE_DESKTOP_OPUS_MODEL_ID,
|
||||
CLAUDE_DESKTOP_SONNET_MODEL_ID,
|
||||
CLAUDE_DESKTOP_HAIKU_MODEL_ID
|
||||
{ "name": CLAUDE_DESKTOP_OPUS_MODEL_ID },
|
||||
{ "name": CLAUDE_DESKTOP_SONNET_MODEL_ID },
|
||||
{ "name": CLAUDE_DESKTOP_HAIKU_MODEL_ID }
|
||||
])
|
||||
);
|
||||
assert_eq!(meta["appliedId"], CLAUDE_DESKTOP_PROFILE_ID);
|
||||
@@ -18236,11 +18286,32 @@ model:
|
||||
sonnet: "gpt-5.6".to_string(),
|
||||
haiku: "gpt-5.6-mini".to_string(),
|
||||
};
|
||||
let models = vec![
|
||||
AgentModelOption {
|
||||
name: mappings.opus.clone(),
|
||||
alias: None,
|
||||
is_alias: false,
|
||||
context_window: Some(1_000_000),
|
||||
},
|
||||
AgentModelOption {
|
||||
name: mappings.sonnet.clone(),
|
||||
alias: None,
|
||||
is_alias: false,
|
||||
context_window: Some(272_000),
|
||||
},
|
||||
AgentModelOption {
|
||||
name: mappings.haiku.clone(),
|
||||
alias: None,
|
||||
is_alias: false,
|
||||
context_window: Some(1_000_000),
|
||||
},
|
||||
];
|
||||
let profile = build_claude_desktop_profile(
|
||||
None,
|
||||
"http://127.0.0.1:8317",
|
||||
DEFAULT_API_KEY,
|
||||
"gpt-5.6-sol",
|
||||
&models,
|
||||
Some(&mappings),
|
||||
)
|
||||
.unwrap();
|
||||
@@ -18249,9 +18320,17 @@ model:
|
||||
assert_eq!(
|
||||
profile["inferenceModels"],
|
||||
serde_json::json!([
|
||||
CLAUDE_DESKTOP_OPUS_MODEL_ID,
|
||||
CLAUDE_DESKTOP_SONNET_MODEL_ID,
|
||||
CLAUDE_DESKTOP_HAIKU_MODEL_ID
|
||||
{
|
||||
"name": CLAUDE_DESKTOP_OPUS_MODEL_ID,
|
||||
"supports1m": true,
|
||||
"prefer1m": true
|
||||
},
|
||||
{ "name": CLAUDE_DESKTOP_SONNET_MODEL_ID },
|
||||
{
|
||||
"name": CLAUDE_DESKTOP_HAIKU_MODEL_ID,
|
||||
"supports1m": true,
|
||||
"prefer1m": true
|
||||
}
|
||||
])
|
||||
);
|
||||
assert!(!profile.to_string().contains("gpt-"));
|
||||
@@ -18429,9 +18508,9 @@ model:
|
||||
let models = parse_agent_model_options(&serde_json::json!({
|
||||
"object": "list",
|
||||
"data": [
|
||||
{"id": "gpt-5", "display_name": "GPT 5"},
|
||||
{"name": "claude-sonnet", "alias": "claude-sonnet-xhigh", "fork": true},
|
||||
{"name": "hidden-original", "alias": "visible-alias"},
|
||||
{"id": "gpt-5", "display_name": "GPT 5", "context_length": 272000},
|
||||
{"name": "claude-sonnet", "alias": "claude-sonnet-xhigh", "fork": true, "contextLength": "1000000"},
|
||||
{"name": "hidden-original", "alias": "visible-alias", "context_window": 128000},
|
||||
"deepseek-chat",
|
||||
{"id": "GPT-5"},
|
||||
{"id": ""}
|
||||
@@ -18445,22 +18524,32 @@ model:
|
||||
AgentModelOption {
|
||||
name: "gpt-5".to_string(),
|
||||
alias: Some("GPT 5".to_string()),
|
||||
is_alias: false,
|
||||
context_window: Some(272_000),
|
||||
},
|
||||
AgentModelOption {
|
||||
name: "claude-sonnet".to_string(),
|
||||
alias: None,
|
||||
is_alias: false,
|
||||
context_window: Some(1_000_000),
|
||||
},
|
||||
AgentModelOption {
|
||||
name: "claude-sonnet-xhigh".to_string(),
|
||||
alias: Some("claude-sonnet".to_string()),
|
||||
is_alias: true,
|
||||
context_window: Some(1_000_000),
|
||||
},
|
||||
AgentModelOption {
|
||||
name: "visible-alias".to_string(),
|
||||
alias: Some("hidden-original".to_string()),
|
||||
is_alias: true,
|
||||
context_window: Some(128_000),
|
||||
},
|
||||
AgentModelOption {
|
||||
name: "deepseek-chat".to_string(),
|
||||
alias: None,
|
||||
is_alias: false,
|
||||
context_window: None,
|
||||
},
|
||||
]
|
||||
);
|
||||
@@ -18566,6 +18655,8 @@ model:
|
||||
let models = vec![AgentModelOption {
|
||||
name: "gpt-5.4".to_string(),
|
||||
alias: Some("GPT 5.4".to_string()),
|
||||
is_alias: false,
|
||||
context_window: None,
|
||||
}];
|
||||
|
||||
assert_eq!(
|
||||
|
||||
@@ -550,6 +550,8 @@ export const jaOverrides = {
|
||||
'agents.modify.oauthConfiguration': 'OAuth 設定方式',
|
||||
'agents.claudeDesktopMapping.title': 'Claude Desktop モデル → 上流モデル',
|
||||
'agents.claudeDesktopMapping.description': '各 Claude Desktop モデルが実際に呼び出す上流モデルを選択します。',
|
||||
'agents.claudeDesktopMapping.customMapping': 'カスタムマッピング',
|
||||
'agents.claudeDesktopMapping.customMappingHint': '有効にするとエイリアスモデルのみ、無効にするとエイリアス未設定の元モデルのみ表示します。',
|
||||
'agents.claudeDesktopMapping.opus': 'Claude Opus 5 のマッピング先',
|
||||
'agents.claudeDesktopMapping.sonnet': 'Claude Sonnet 4.6 のマッピング先',
|
||||
'agents.claudeDesktopMapping.haiku': 'Claude Haiku 4.5 のマッピング先',
|
||||
|
||||
@@ -548,6 +548,8 @@ export const zhCN = {
|
||||
'agents.pending': '尚未应用',
|
||||
'agents.claudeDesktopMapping.title': 'Claude Desktop 模型 → 上游模型',
|
||||
'agents.claudeDesktopMapping.description': '选择每个 Claude Desktop 模型实际调用的上游模型。',
|
||||
'agents.claudeDesktopMapping.customMapping': '自定义映射',
|
||||
'agents.claudeDesktopMapping.customMappingHint': '开启后只显示别名模型;关闭后只显示未设置别名的原始模型。',
|
||||
'agents.claudeDesktopMapping.opus': 'Claude Opus 5 映射到',
|
||||
'agents.claudeDesktopMapping.sonnet': 'Claude Sonnet 4.6 映射到',
|
||||
'agents.claudeDesktopMapping.haiku': 'Claude Haiku 4.5 映射到',
|
||||
@@ -1438,6 +1440,8 @@ export const en: Record<MessageKey, string> = {
|
||||
'agents.pending': 'Not Applied',
|
||||
'agents.claudeDesktopMapping.title': 'Claude Desktop Model → Upstream Model',
|
||||
'agents.claudeDesktopMapping.description': 'Choose the upstream model each Claude Desktop model actually calls.',
|
||||
'agents.claudeDesktopMapping.customMapping': 'Custom Mapping',
|
||||
'agents.claudeDesktopMapping.customMappingHint': 'When enabled, only aliased models are shown; when disabled, only original models without aliases are shown.',
|
||||
'agents.claudeDesktopMapping.opus': 'Map Claude Opus 5 to',
|
||||
'agents.claudeDesktopMapping.sonnet': 'Map Claude Sonnet 4.6 to',
|
||||
'agents.claudeDesktopMapping.haiku': 'Map Claude Haiku 4.5 to',
|
||||
|
||||
@@ -34,6 +34,7 @@ import opencodeIcon from '../assets/icons/opencode.svg';
|
||||
import {
|
||||
agentModelAlias,
|
||||
filterAgentModels,
|
||||
filterAgentModelsByAlias,
|
||||
findAgentModel,
|
||||
resolveAgentModelSelection,
|
||||
} from '../services/agentModelPicker';
|
||||
@@ -298,7 +299,9 @@ function AgentModelPicker({
|
||||
() => visibleModels.map((model) => ({ name: model.name, alias: model.alias ?? '' })),
|
||||
[visibleModels],
|
||||
);
|
||||
const selectedAlias = agentModelAlias(models, value);
|
||||
const selectedModel = findAgentModel(models, value);
|
||||
const selectedName = selectedModel?.name ?? '';
|
||||
const selectedAlias = selectedName ? agentModelAlias(models, selectedName) : '';
|
||||
|
||||
const updateDropdownLayout = useCallback(() => {
|
||||
const root = rootRef.current;
|
||||
@@ -411,8 +414,8 @@ function AgentModelPicker({
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<strong title={value || undefined}>
|
||||
{value || (loading ? t('agents.model.loading') : error ? t('agents.model.loadFailed') : models.length ? t('agents.model.select') : t('agents.model.none'))}
|
||||
<strong title={selectedName || undefined}>
|
||||
{selectedName || (loading ? t('agents.model.loading') : error ? t('agents.model.loadFailed') : models.length ? t('agents.model.select') : t('agents.model.none'))}
|
||||
</strong>
|
||||
{selectedAlias ? <small title={selectedAlias}>{selectedAlias}</small> : null}
|
||||
</span>
|
||||
@@ -513,6 +516,7 @@ export function AgentsPage() {
|
||||
const [claudeDesktopModelMappingsDraft, setClaudeDesktopModelMappingsDraft] = useState<ClaudeDesktopModelMappings>(
|
||||
createClaudeDesktopModelMappings(''),
|
||||
);
|
||||
const [claudeDesktopCustomMapping, setClaudeDesktopCustomMapping] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [modelLoading, setModelLoading] = useState(false);
|
||||
const [busyAction, setBusyAction] = useState<
|
||||
@@ -643,6 +647,10 @@ export function AgentsPage() {
|
||||
const savedSelectedModel = modelByClient[selected] ?? '';
|
||||
const selectedModelOption = findAgentModel(models, savedSelectedModel);
|
||||
const selectedModel = selectedModelOption?.name ?? '';
|
||||
const claudeDesktopModels = useMemo(
|
||||
() => filterAgentModelsByAlias(models, claudeDesktopCustomMapping),
|
||||
[claudeDesktopCustomMapping, models],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (selected !== 'claude-desktop' || !selectedModel) return;
|
||||
@@ -1105,14 +1113,31 @@ export function AgentsPage() {
|
||||
<strong>{t('agents.claudeDesktopMapping.title')}</strong>
|
||||
<span>{t('agents.claudeDesktopMapping.description')}</span>
|
||||
</div>
|
||||
{claudeDesktopMappingDraftChanged ? <span className="agent-pending-badge">{t('agents.pending')}</span> : null}
|
||||
<div className="agent-section-heading-actions">
|
||||
<label
|
||||
className="agent-claude-desktop-mapping-filter"
|
||||
title={t('agents.claudeDesktopMapping.customMappingHint')}
|
||||
>
|
||||
<span>{t('agents.claudeDesktopMapping.customMapping')}</span>
|
||||
<span className="switch-control">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={claudeDesktopCustomMapping}
|
||||
onChange={(event) => setClaudeDesktopCustomMapping(event.currentTarget.checked)}
|
||||
disabled={busy || modelLoading}
|
||||
/>
|
||||
<span className="switch-track" />
|
||||
</span>
|
||||
</label>
|
||||
{claudeDesktopMappingDraftChanged ? <span className="agent-pending-badge">{t('agents.pending')}</span> : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="agent-claude-desktop-mapping-grid">
|
||||
{claudeDesktopMappingRoles.map((role) => (
|
||||
<div className="agent-claude-desktop-mapping-row" key={role.key}>
|
||||
<strong>{t(role.labelKey)}</strong>
|
||||
<AgentModelPicker
|
||||
models={models}
|
||||
models={claudeDesktopModels}
|
||||
value={claudeDesktopModelMappingsDraft[role.key]}
|
||||
loading={modelLoading}
|
||||
error={modelError}
|
||||
|
||||
@@ -28,6 +28,13 @@ export function filterAgentModels(models: ModelOption[], search: string): ModelO
|
||||
.map((item) => item.model);
|
||||
}
|
||||
|
||||
export function filterAgentModelsByAlias(
|
||||
models: ModelOption[],
|
||||
aliasesOnly: boolean,
|
||||
): ModelOption[] {
|
||||
return models.filter((model) => Boolean(model.isAlias) === aliasesOnly);
|
||||
}
|
||||
|
||||
export function hasExactAgentModel(models: ModelOption[], value: string): boolean {
|
||||
const query = normalized(value);
|
||||
if (!query) return false;
|
||||
|
||||
@@ -6,6 +6,8 @@ const modelText = (key: Parameters<typeof translate>[1]) => translate(getCurrent
|
||||
export type ModelOption = {
|
||||
name: string;
|
||||
alias?: string;
|
||||
isAlias?: boolean;
|
||||
contextWindow?: number;
|
||||
thinking?: Record<string, unknown>;
|
||||
};
|
||||
export type ModelProvider = 'gemini' | 'codex' | 'claude' | 'openai';
|
||||
|
||||
@@ -5166,6 +5166,39 @@ textarea:focus-visible {
|
||||
font-size: var(--font-size-label);
|
||||
}
|
||||
|
||||
.agent-section-heading-actions {
|
||||
display: flex !important;
|
||||
flex: none;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 8px !important;
|
||||
}
|
||||
|
||||
.agent-claude-desktop-mapping-filter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid var(--theme-e3e1db);
|
||||
border-radius: 7px;
|
||||
background: var(--theme-fff);
|
||||
color: var(--theme-5f5a53);
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-label);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.agent-claude-desktop-mapping-filter:has(input:checked) {
|
||||
border-color: var(--theme-8069ad);
|
||||
background: var(--theme-f1ecfb);
|
||||
color: var(--theme-554075);
|
||||
}
|
||||
|
||||
.agent-claude-desktop-mapping-filter:has(input:disabled) {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.62;
|
||||
}
|
||||
|
||||
.agent-section-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -6950,6 +6983,15 @@ textarea:focus-visible {
|
||||
}
|
||||
|
||||
@container agent-panel (max-width: 640px) {
|
||||
.agent-claude-desktop-mapping > .agent-section-heading {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.agent-claude-desktop-mapping .agent-section-heading-actions {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.agent-claude-desktop-mapping-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { describe, expect, test } from 'bun:test';
|
||||
import {
|
||||
agentModelAlias,
|
||||
filterAgentModels,
|
||||
filterAgentModelsByAlias,
|
||||
findAgentModel,
|
||||
hasExactAgentModel,
|
||||
resolveAgentModelSelection,
|
||||
@@ -49,4 +50,17 @@ describe('智能体模型选择器', () => {
|
||||
expect(findAgentModel(models, 'codex')?.name).toBeUndefined();
|
||||
expect(findAgentModel(models, 'gpt-5.2-codex')?.name).toBe('gpt-5.2-codex');
|
||||
});
|
||||
|
||||
test('Claude Desktop 自定义映射只显示对应类型的模型', () => {
|
||||
const mixedModels = [
|
||||
{ name: 'gpt-original', alias: 'GPT Original', isAlias: false },
|
||||
{ name: 'gpt-high', alias: 'gpt-original', isAlias: true },
|
||||
{ name: 'claude-original' },
|
||||
];
|
||||
|
||||
expect(filterAgentModelsByAlias(mixedModels, false).map((model) => model.name))
|
||||
.toEqual(['gpt-original', 'claude-original']);
|
||||
expect(filterAgentModelsByAlias(mixedModels, true).map((model) => model.name))
|
||||
.toEqual(['gpt-high']);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user