feat(#1219): support canonical model capability metadata (#1228)

This commit is contained in:
m57 authored and GitHub committed 2026-09-22 18:53:24 +03:00
1 parent 16967c7e91
commit bf53905a93
14 files changed
+451 -43

No files matched your search

@@ -27,4 +27,5 @@ data class ModelInfoResponse(
val config_context_length: Long? = null,
/** Effective context window actually in use (max of auto/config + caps). */
val effective_context_length: Long? = null,
val capabilities: ModelCapabilities? = null,
)
@@ -26,8 +26,60 @@ data class ModelCapabilities(
val fast: Boolean? = null,
val reasoning: Boolean? = null,
val can_disable_reasoning: Boolean? = null,
val supports_tools: Boolean? = null,
val supports_vision: Boolean? = null,
val supports_reasoning: Boolean? = null,
val context_window: Long? = null,
val max_output_tokens: Long? = null,
val model_family: String? = null,
)
val ModelCapabilities.reasoningSupport: Boolean?
get() = supports_reasoning ?: reasoning
fun ModelCapabilities?.mergedWithCanonical(canonical: ModelCapabilities?): ModelCapabilities? {
if (canonical == null) return this
val catalog = this
return ModelCapabilities(
fast = catalog?.fast,
reasoning = catalog?.reasoning,
can_disable_reasoning = catalog?.can_disable_reasoning,
supports_tools = canonical.supports_tools ?: catalog?.supports_tools,
supports_vision = canonical.supports_vision ?: catalog?.supports_vision,
supports_reasoning = canonical.supports_reasoning ?: catalog?.supports_reasoning,
context_window = canonical.context_window ?: catalog?.context_window,
max_output_tokens = canonical.max_output_tokens ?: catalog?.max_output_tokens,
model_family = canonical.model_family ?: catalog?.model_family,
)
}
fun List<ModelProvider>.withModelInfoCapabilities(info: ModelInfoResponse?): List<ModelProvider> {
val providerSlug = info?.provider?.takeIf { it.isNotBlank() } ?: return this
val modelName = info.model?.takeIf { it.isNotBlank() } ?: return this
val canonical = info.capabilities ?: return this
var changed = false
val enriched =
map { provider ->
if (
provider.slug.equals(providerSlug, ignoreCase = true) &&
provider.models.orEmpty().contains(modelName)
) {
changed = true
provider.copy(
capabilities =
provider.capabilities.orEmpty() +
(
modelName to
(provider.capabilities?.get(modelName).mergedWithCanonical(canonical) ?: canonical)
),
)
} else {
provider
}
}
return if (changed) enriched else this
}
@Serializable
data class PinnedModel(
val providerSlug: String,
@@ -300,7 +300,9 @@ interface HermesApiService : KanbanApiService {
): Response<SessionInfo>
@GET("api/model/info")
suspend fun getModelInfo(): Response<ModelInfoResponse>
suspend fun getModelInfo(
@Query("profile") profile: String? = null,
): Response<ModelInfoResponse>
@GET("api/system/stats")
suspend fun getSystemStats(): Response<SystemStatsResponse>