Audit fixes implemented: 1. SkillTool command filtering via SkillFilters.getSkillToolCommands() 2. getSlashCommandToolSkills() with distinct slash command filters 3. SkillChangeDetector: WatchService-based hot reload with 300ms debounce 4. Skill hooks support: hooks field in Skill record + HooksSchema validation 5. SkillTool UI rendering: renderInlineResult/ForkedResult/Rejected/Error 6. McpSkillBuilders registry: register/unregister/getAllMcpSkills pattern 7. Permission checks in SkillTool via PermissionRuleEngine integration 8. Analytics telemetry: logSkillInvocation + tengu_skill_tool_invocation 9. SkillFilters exports: findCommand/hasCommand/getCommand/groupBySource 10. isOfficialMarketplaceName: marketplace skill provenance check 11. Experimental skill search: isExperimentalSkillSearchEnabled() flag 12. Model alias resolution: ModelResolver with haiku/sonnet/opus aliases Additional: - AgentLoader wired as Spring Bean in AppConfig - PermissionRuleEngine injected into ToolContext for SkillTool - SkillChangeDetector started on app boot for skill hot reload - Skill record: 27 -> 28 fields (added hooks Map<String,Object>) - 34 new tests (138 total, all passing) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
110 lines
3.9 KiB
Java
110 lines
3.9 KiB
Java
package com.claudecode.util;
|
|
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import java.util.Map;
|
|
|
|
/**
|
|
* 模型别名解析器 —— 对应 TS resolveSkillModelOverride()。
|
|
* <p>
|
|
* 解析技能 frontmatter 中的 model 字段,支持:
|
|
* <ul>
|
|
* <li>别名解析:haiku → claude-3-haiku, sonnet → claude-sonnet-4, opus → claude-opus-4</li>
|
|
* <li>特殊值:inherit → null(继承父级模型)</li>
|
|
* <li>完整模型 ID:直接透传</li>
|
|
* </ul>
|
|
*/
|
|
public final class ModelResolver {
|
|
|
|
private static final Logger log = LoggerFactory.getLogger(ModelResolver.class);
|
|
|
|
/** Model alias mapping (short name → full model ID) */
|
|
private static final Map<String, String> MODEL_ALIASES = Map.ofEntries(
|
|
// Claude 3 family
|
|
Map.entry("haiku", "claude-3-haiku-20240307"),
|
|
Map.entry("claude-3-haiku", "claude-3-haiku-20240307"),
|
|
Map.entry("haiku-3", "claude-3-haiku-20240307"),
|
|
|
|
// Claude 3.5 family
|
|
Map.entry("sonnet-3.5", "claude-3-5-sonnet-20241022"),
|
|
Map.entry("claude-3.5-sonnet", "claude-3-5-sonnet-20241022"),
|
|
Map.entry("haiku-3.5", "claude-3-5-haiku-20241022"),
|
|
Map.entry("claude-3.5-haiku", "claude-3-5-haiku-20241022"),
|
|
|
|
// Claude 4 family (latest)
|
|
Map.entry("sonnet", "claude-sonnet-4-20250514"),
|
|
Map.entry("claude-sonnet", "claude-sonnet-4-20250514"),
|
|
Map.entry("sonnet-4", "claude-sonnet-4-20250514"),
|
|
Map.entry("claude-sonnet-4", "claude-sonnet-4-20250514"),
|
|
|
|
Map.entry("opus", "claude-opus-4-20250514"),
|
|
Map.entry("claude-opus", "claude-opus-4-20250514"),
|
|
Map.entry("opus-4", "claude-opus-4-20250514"),
|
|
Map.entry("claude-opus-4", "claude-opus-4-20250514"),
|
|
|
|
// OpenAI aliases (for OpenAI-compatible providers)
|
|
Map.entry("gpt-4", "gpt-4"),
|
|
Map.entry("gpt-4o", "gpt-4o"),
|
|
Map.entry("gpt-4o-mini", "gpt-4o-mini"),
|
|
Map.entry("o1", "o1"),
|
|
Map.entry("o1-mini", "o1-mini"),
|
|
Map.entry("o3", "o3"),
|
|
Map.entry("o3-mini", "o3-mini"),
|
|
Map.entry("o4-mini", "o4-mini")
|
|
);
|
|
|
|
private ModelResolver() {}
|
|
|
|
/**
|
|
* Resolve a model override from skill frontmatter.
|
|
* Corresponds to TS resolveSkillModelOverride().
|
|
*
|
|
* @param modelValue raw model value from frontmatter (may be alias, "inherit", or full ID)
|
|
* @return resolved model ID, or null if "inherit" or invalid
|
|
*/
|
|
public static String resolveSkillModelOverride(String modelValue) {
|
|
if (modelValue == null || modelValue.isBlank()) {
|
|
return null;
|
|
}
|
|
|
|
String normalized = modelValue.strip().toLowerCase();
|
|
|
|
// "inherit" means use parent model
|
|
if ("inherit".equals(normalized)) {
|
|
return null;
|
|
}
|
|
|
|
// Check aliases
|
|
String resolved = MODEL_ALIASES.get(normalized);
|
|
if (resolved != null) {
|
|
log.debug("Resolved model alias '{}' → '{}'", modelValue, resolved);
|
|
return resolved;
|
|
}
|
|
|
|
// If it looks like a full model ID (contains a dash and has enough chars), pass through
|
|
if (modelValue.contains("-") || modelValue.contains("/") || modelValue.length() > 10) {
|
|
return modelValue.strip();
|
|
}
|
|
|
|
// Unknown short name — log warning but still pass through
|
|
log.debug("Unknown model alias '{}', passing through as-is", modelValue);
|
|
return modelValue.strip();
|
|
}
|
|
|
|
/**
|
|
* Check if a model value is a known alias.
|
|
*/
|
|
public static boolean isKnownAlias(String modelValue) {
|
|
if (modelValue == null) return false;
|
|
return MODEL_ALIASES.containsKey(modelValue.strip().toLowerCase());
|
|
}
|
|
|
|
/**
|
|
* Get all known model aliases.
|
|
*/
|
|
public static Map<String, String> getAllAliases() {
|
|
return MODEL_ALIASES;
|
|
}
|
|
}
|