diff --git a/README.md b/README.md
index d6db59484..758b57693 100644
--- a/README.md
+++ b/README.md
@@ -53,17 +53,18 @@
-
-
-
-
-
📥 Download •
📖 Documentation •
💬 Discussions
+
+
+
+
+
+
---
## Why Askimo?
diff --git a/desktop-shared/src/main/kotlin/io/askimo/ui/chat/ChatInputField.kt b/desktop-shared/src/main/kotlin/io/askimo/ui/chat/ChatInputField.kt
index 61375869a..ecd2ff66e 100644
--- a/desktop-shared/src/main/kotlin/io/askimo/ui/chat/ChatInputField.kt
+++ b/desktop-shared/src/main/kotlin/io/askimo/ui/chat/ChatInputField.kt
@@ -94,6 +94,7 @@ import io.askimo.core.event.error.AppErrorEvent
import io.askimo.core.event.internal.ImageCapabilityDetectedEvent
import io.askimo.core.event.internal.ReasoningEffortChangedEvent
import io.askimo.core.event.internal.ThinkingSupportDetectedEvent
+import io.askimo.core.event.internal.ToolSupportDetectedEvent
import io.askimo.core.i18n.LocalizationManager
import io.askimo.core.intent.ToolConfig
import io.askimo.core.intent.ToolRegistry
@@ -189,6 +190,20 @@ fun chatInputField(
)
}
+ // Whether the current model supports tool calling.
+ // Only disabled when the probe has run AND returned false — unknown (null) stays enabled.
+ var modelSupportsTools by remember(resolvedProvider, currentModel) {
+ mutableStateOf(
+ if (resolvedProvider != null && currentModel.isNotBlank() &&
+ ModelCapabilitiesCache.hasTestedToolSupport(resolvedProvider, currentModel)
+ ) {
+ ModelCapabilitiesCache.supportsTools(resolvedProvider, currentModel)
+ } else {
+ true // Not yet probed — optimistically keep enabled
+ },
+ )
+ }
+
// Listen for the probe result and update when it arrives for the active model.
LaunchedEffect(resolvedProvider, currentModel) {
EventBus.internalEvents.collect { event ->
@@ -201,6 +216,18 @@ fun chatInputField(
}
}
+ // Listen for tool support probe result and update reactively.
+ LaunchedEffect(resolvedProvider, currentModel) {
+ EventBus.internalEvents.collect { event ->
+ if (event is ToolSupportDetectedEvent &&
+ event.provider == resolvedProvider &&
+ event.model == currentModel
+ ) {
+ modelSupportsTools = event.supportsTools
+ }
+ }
+ }
+
// Whether the current model supports native image generation (in-chat).
var supportsNativeImageGeneration by remember(resolvedProvider, currentModel) {
mutableStateOf(
@@ -713,6 +740,7 @@ fun chatInputField(
},
onNavigateToMcpSettings = onNavigateToMcpSettings,
iconSize = 28.dp,
+ modelSupportsTools = modelSupportsTools,
)
// Image mode chip — only show when user explicitly toggles to Image mode
@@ -915,6 +943,7 @@ private fun toolsIndicatorButton(
onEnabledServerIdsChange: (Set) -> Unit,
onNavigateToMcpSettings: (() -> Unit)? = null,
iconSize: Dp = 36.dp,
+ modelSupportsTools: Boolean = false,
) {
var showToolsPopup by remember { mutableStateOf(false) }
var mcpServers by remember { mutableStateOf>(emptyList()) }
@@ -985,31 +1014,32 @@ private fun toolsIndicatorButton(
Box {
themedTooltip(
- text = if (hasDisabled) {
- stringResource("chat.tools.button.disabled", (totalServers - enabledServers).toString())
- } else {
- stringResource("chat.tools.button")
+ text = when {
+ !modelSupportsTools -> stringResource("chat.tools.button.model.no.support")
+ hasDisabled -> stringResource("chat.tools.button.disabled", (totalServers - enabledServers).toString())
+ else -> stringResource("chat.tools.button")
},
) {
// Inline chip: [🔧 3] or [🔧 3/5] — avoids all badge clipping issues
Surface(
shape = RoundedCornerShape(8.dp),
color = when {
+ !modelSupportsTools -> MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f)
totalServers == 0 -> Color.Transparent
hasDisabled -> MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.5f)
else -> MaterialTheme.colorScheme.secondaryContainer
},
- tonalElevation = if (totalServers > 0) 2.dp else 0.dp,
+ tonalElevation = if (totalServers > 0 && modelSupportsTools) 2.dp else 0.dp,
modifier = Modifier
.height(iconSize)
.clip(RoundedCornerShape(8.dp))
.clickable(
- enabled = !isLoading,
+ enabled = !isLoading && modelSupportsTools,
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = { showToolsPopup = true },
)
- .pointerHoverIcon(PointerIcon.Hand),
+ .pointerHoverIcon(if (modelSupportsTools) PointerIcon.Hand else PointerIcon.Default),
) {
Row(
verticalAlignment = Alignment.CenterVertically,
@@ -1020,13 +1050,14 @@ private fun toolsIndicatorButton(
Icons.Default.Build,
contentDescription = stringResource("chat.tools.button"),
tint = when {
+ !modelSupportsTools -> MaterialTheme.colorScheme.onSurface.copy(alpha = 0.3f)
hasDisabled -> MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f)
totalServers > 0 -> MaterialTheme.colorScheme.onSecondaryContainer
else -> MaterialTheme.colorScheme.onSurface
},
modifier = Modifier.size(16.dp),
)
- if (totalServers > 0) {
+ if (totalServers > 0 && modelSupportsTools) {
Text(
text = if (hasDisabled) "$enabledServers/$totalServers" else "$enabledServers",
style = MaterialTheme.typography.labelSmall,
diff --git a/desktop-shared/src/main/kotlin/io/askimo/ui/session/SessionManager.kt b/desktop-shared/src/main/kotlin/io/askimo/ui/session/SessionManager.kt
index 4e81bad7c..f3114c338 100644
--- a/desktop-shared/src/main/kotlin/io/askimo/ui/session/SessionManager.kt
+++ b/desktop-shared/src/main/kotlin/io/askimo/ui/session/SessionManager.kt
@@ -17,9 +17,11 @@ import io.askimo.core.chat.service.ChatSessionService
import io.askimo.core.context.AppContext
import io.askimo.core.event.EventBus
import io.askimo.core.event.internal.SessionCreatedEvent
+import io.askimo.core.exception.ContextLengthException
import io.askimo.core.exception.ExceptionHandler
import io.askimo.core.logging.logger
import io.askimo.core.providers.ConfigurationErrorException
+import io.askimo.core.providers.isContextLengthError
import io.askimo.core.providers.sendStreamingMessageWithCallback
import io.askimo.core.vision.ImageProcessor
import io.askimo.ui.chat.ChatViewModel
@@ -366,6 +368,12 @@ class SessionManager(
val partialResponse = thread.getCurrentContent()
val failedResponse = if (e is ConfigurationErrorException) {
e.displayMessage
+ } else if (e.isContextLengthError()) {
+ ExceptionHandler.handleWithPartialContent(
+ throwable = ContextLengthException(cause = e),
+ partialContent = partialResponse,
+ contextId = sessionId,
+ )
} else {
ExceptionHandler.handleWithPartialContent(
throwable = e,
diff --git a/desktop-shared/src/main/resources/i18n/messages.properties b/desktop-shared/src/main/resources/i18n/messages.properties
index 9584f37cb..e1651ba96 100644
--- a/desktop-shared/src/main/resources/i18n/messages.properties
+++ b/desktop-shared/src/main/resources/i18n/messages.properties
@@ -651,6 +651,8 @@ chat.create.image.mode.cancel=Cancel image creation mode
chat.image.model.missing.title=Image model not configured
chat.image.model.missing.message=No image model is set for {0}. Open Settings > AI Provider > Image Generation Models and set a model name first.
chat.tools.button=Available Tools
+chat.tools.button.disabled=Available Tools ({0} disabled)
+chat.tools.button.model.no.support=Tools are not supported by this model
chat.tools.popup.title=Available Tools
chat.tools.popup.loading=Loading servers...
chat.tools.popup.no.servers.global=No global MCP servers configured.
@@ -659,7 +661,6 @@ chat.tools.server.scope.global=Global
chat.tools.server.scope.project=Project
chat.tools.server.scope.builtin=Built-in
chat.tools.tool.no.description=No description available
-chat.tools.button.disabled=Available Tools ({0} disabled)
chat.reasoning.effort.label=Thinking
chat.reasoning.effort.tooltip=Reasoning effort — controls how deeply the model thinks before responding
chat.reasoning.effort.off=Off
@@ -1010,6 +1011,7 @@ error.provider_not_configured=⚠️ No AI provider configured.\n\nTo start chat
error.rate_limit=⚠️ Rate limit exceeded\n{retryAfter}\n\nYou've reached your API usage limit. Please wait before sending more messages.
error.insufficient_credits=⚠️ Insufficient API credits\n\nYour credit balance has been exhausted.\n\nPlease go to Plans & Billing to top up your credits.\n\nerror.timeout=⚠️ Request timed out after {timeout} seconds\n\nThis might be due to:\n• High server load - try again in a moment\n• Complex query - try simplifying\n• Network latency
error.timeout=⚠️ Request timed out after {timeout} seconds\n\nPossible causes:\n• High server load – please try again later\n• Complex request – simplify your input\n• Network delays
+error.context_length=⚠️ Context window exceeded\n\nThe total input (conversation history + your message + attachments) is too large for this model to process.\n\nTo fix this:\n• Start a new chat session to clear the conversation history\n• Shorten your message or remove large attachments\n• If using a local model (Ollama / LM Studio / Docker), increase the context size in its settings\n• Switch to a model with a larger context window
error.invalid_request=⚠️ Invalid request: {details}\n\nPlease check your input and try again.
error.system=⚠️ System Error\n\nAn unexpected error occurred: {errorCode}\n\nPlease contact Askimo support with:\n• What you were doing\n• The error code above\n• The timestamp
diff --git a/desktop-shared/src/main/resources/i18n/messages_de.properties b/desktop-shared/src/main/resources/i18n/messages_de.properties
index b98c61e88..6c89ada64 100644
--- a/desktop-shared/src/main/resources/i18n/messages_de.properties
+++ b/desktop-shared/src/main/resources/i18n/messages_de.properties
@@ -638,6 +638,7 @@ chat.tools.server.scope.project=Projekt
chat.tools.server.scope.builtin=Integriert
chat.tools.tool.no.description=Keine Beschreibung verfügbar
chat.tools.button.disabled=Verfügbare Tools ({0} deaktiviert)
+chat.tools.button.model.no.support=Tools werden von diesem Modell nicht unterstützt
chat.reasoning.effort.label=Denken
chat.reasoning.effort.tooltip=Denkaufwand — steuert, wie tief das Modell nachdenkt, bevor es antwortet
chat.reasoning.effort.off=Aus
@@ -988,6 +989,7 @@ error.provider_not_configured=⚠️ Kein KI-Anbieter konfiguriert.\n\nUm zu cha
error.rate_limit=⚠️ Ratenlimit überschritten\n{retryAfter}\n\nSie haben Ihr API-Nutzungslimit erreicht. Bitte warten Sie, bevor Sie weitere Nachrichten senden.
error.insufficient_credits=⚠️ Unzureichendes Guthaben\n\nIhr Guthaben ist zu niedrig, um diesen KI-Anbieter zu nutzen.\n\nBitte gehen Sie zur Abrechnungsseite Ihres Anbieters.
error.timeout=⚠️ Anfrage nach {timeout} Sekunden abgelaufen\n\nMögliche Ursachen:\n• Hohe Serverlast – versuchen Sie es später erneut\n• Komplexe Anfrage – vereinfachen Sie Ihre Eingabe\n• Netzwerkverzögerungen
+error.context_length=⚠️ Kontextfenster überschritten\n\nDie gesamte Eingabe (Konversationsverlauf + Ihre Nachricht + Anhänge) ist zu groß, als dass dieses Modell sie verarbeiten könnte.\n\nSo beheben Sie das Problem:\n• Starten Sie eine neue Chat-Sitzung, um den Konversationsverlauf zu löschen\n• Kürzen Sie Ihre Nachricht oder entfernen Sie große Anhänge\n• Wenn Sie ein lokales Modell verwenden (Ollama / LM Studio / Docker), erhöhen Sie die Kontextgröße in den Einstellungen\n• Wechseln Sie zu einem Modell mit einem größeren Kontextfenster
error.invalid_request=⚠️ Ungültige Anfrage: {details}\n\nBitte überprüfen Sie Ihre Eingabe und versuchen Sie es erneut.
error.system=⚠️ Systemfehler\n\nEin unerwarteter Fehler ist aufgetreten: {errorCode}\n\nBitte wenden Sie sich an den Askimo-Support mit:\n• Ihrer letzten Aktion\n• Dem obigen Fehlercode\n• Dem Zeitstempel
diff --git a/desktop-shared/src/main/resources/i18n/messages_es.properties b/desktop-shared/src/main/resources/i18n/messages_es.properties
index 48612b57d..e964354e7 100644
--- a/desktop-shared/src/main/resources/i18n/messages_es.properties
+++ b/desktop-shared/src/main/resources/i18n/messages_es.properties
@@ -637,6 +637,7 @@ chat.tools.server.scope.project=Proyecto
chat.tools.server.scope.builtin=Integrado
chat.tools.tool.no.description=No hay descripción disponible
chat.tools.button.disabled=Herramientas disponibles ({0} deshabilitadas)
+chat.tools.button.model.no.support=Las herramientas no son compatibles con este modelo
chat.reasoning.effort.label=Pensando
chat.reasoning.effort.tooltip=Esfuerzo de razonamiento — controla qué tan profundo piensa el modelo antes de responder
chat.reasoning.effort.off=Desactivado
@@ -986,6 +987,7 @@ error.provider_not_configured=⚠️ No hay ningún proveedor de IA configurado.
error.rate_limit=⚠️ Límite de uso excedido\n{retryAfter}\n\nHa alcanzado su límite de uso de la API. Espere antes de enviar más mensajes.
error.insufficient_credits=⚠️ Créditos insuficientes\n\nSu saldo de créditos es demasiado bajo.\n\nVaya a la página de facturación para añadir créditos.
error.timeout=⚠️ La solicitud expiró después de {timeout} segundos\n\nPosibles causas:\n• Alta carga del servidor – intente nuevamente más tarde\n• Consulta compleja – intente simplificarla\n• Latencia de red
+error.context_length=⚠️ Ventana de contexto excedida\n\nLa entrada total (historial de la conversación + su mensaje + archivos adjuntos) es demasiado grande para que este modelo la procese.\n\nPara solucionar esto:\n• Inicie una nueva sesión de chat para borrar el historial de la conversación\n• Acorte su mensaje o elimine archivos adjuntos grandes\n• Si utiliza un modelo local (Ollama / LM Studio / Docker), aumente el tamaño del contexto en su configuración\n• Cambie a un modelo con una ventana de contexto más grande
error.invalid_request=⚠️ Solicitud no válida: {details}\n\nRevise su entrada e inténtelo de nuevo.
error.system=⚠️ Error del sistema\n\nOcurrió un error inesperado: {errorCode}\n\nContacte al soporte de Askimo con:\n• Lo que estaba haciendo\n• El código de error anterior\n• La marca de tiempo
diff --git a/desktop-shared/src/main/resources/i18n/messages_fr.properties b/desktop-shared/src/main/resources/i18n/messages_fr.properties
index c9998d841..9a8d527bb 100644
--- a/desktop-shared/src/main/resources/i18n/messages_fr.properties
+++ b/desktop-shared/src/main/resources/i18n/messages_fr.properties
@@ -637,6 +637,7 @@ chat.tools.server.scope.project=Projet
chat.tools.server.scope.builtin=Intégré
chat.tools.tool.no.description=Aucune description disponible
chat.tools.button.disabled=Outils disponibles ({0} désactivés)
+chat.tools.button.model.no.support=Les outils ne sont pas pris en charge par ce modèle
chat.reasoning.effort.label=Réflexion
chat.reasoning.effort.tooltip=Effort de raisonnement — contrôle la profondeur de réflexion du modèle avant de répondre
chat.reasoning.effort.off=Désactivé
@@ -986,6 +987,7 @@ error.provider_not_configured=⚠️ Aucun fournisseur IA configuré.\n\nPour co
error.rate_limit=⚠️ Limite d’utilisation atteinte\n{retryAfter}\n\nVous avez atteint votre limite d’utilisation de l’API. Veuillez patienter avant d’envoyer d’autres messages.
error.insufficient_credits=⚠️ Crédits insuffisants\n\nVotre solde est trop faible pour utiliser ce fournisseur.\n\nVisitez la page de facturation pour ajouter des crédits.
error.timeout=⚠️ La requête a expiré après {timeout} secondes\n\nCauses possibles :\n• Charge serveur élevée – réessayez plus tard\n• Requête complexe – simplifiez-la\n• Latence réseau
+error.context_length=⚠️ Fenêtre de contexte dépassée\n\nL'entrée totale (historique de la conversation + votre message + pièces jointes) est trop volumineuse pour être traitée par ce modèle.\n\nPour corriger cela :\n• Démarrez une nouvelle session de chat pour effacer l'historique de la conversation\n• Raccourcissez votre message ou supprimez les pièces jointes volumineuses\n• Si vous utilisez un modèle local (Ollama / LM Studio / Docker), augmentez la taille du contexte dans ses paramètres\n• Passez à un modèle avec une fenêtre de contexte plus grande
error.invalid_request=⚠️ Requête invalide : {details}\n\nVeuillez vérifier votre saisie et réessayer.
error.system=⚠️ Erreur système\n\nUne erreur inattendue est survenue : {errorCode}\n\nVeuillez contacter le support Askimo avec :\n• Ce que vous faisiez\n• Le code d’erreur ci-dessus\n• L’horodatage
diff --git a/desktop-shared/src/main/resources/i18n/messages_ja_JP.properties b/desktop-shared/src/main/resources/i18n/messages_ja_JP.properties
index 4b79a0080..82dd111e9 100644
--- a/desktop-shared/src/main/resources/i18n/messages_ja_JP.properties
+++ b/desktop-shared/src/main/resources/i18n/messages_ja_JP.properties
@@ -636,6 +636,7 @@ chat.tools.server.scope.project=プロジェクト
chat.tools.server.scope.builtin=組み込み
chat.tools.tool.no.description=説明はありません
chat.tools.button.disabled=利用可能なツール({0} 無効)
+chat.tools.button.model.no.support=ツールはこのモデルではサポートされていません
chat.reasoning.effort.label=思考
chat.reasoning.effort.tooltip=推論の労力 — 応答する前にモデルがどの程度深く思考するかを制御します
chat.reasoning.effort.off=オフ
@@ -986,6 +987,7 @@ error.provider_not_configured=⚠️ AI プロバイダーが設定されてい
error.rate_limit=⚠️ レート制限を超えました\n{retryAfter}\n\nAPI の利用上限に達しました。しばらく待ってから、もう一度送信してください。
error.insufficient_credits=⚠️ クレジット残高不足\n\nクレジット残高が不足しています。\n\n請求ページでクレジットを追加してください。
error.timeout=⚠️ {timeout} 秒後にリクエストがタイムアウトしました\n\n原因として考えられること:\n• サーバー負荷が高い—少し待って再試行してください\n• クエリが複雑—内容を簡略化してみてください\n• ネットワーク遅延
+error.context_length=⚠️ コンテキストウィンドウの制限を超えました\n\n入力の合計(会話履歴 + あなたのメッセージ + 添付ファイル)が大きすぎて、このモデルでは処理できません。\n\n解決するには:\n• 新しいチャットセッションを開始して会話履歴をクリアしてください\n• メッセージを短くするか、大きな添付ファイルを削除してください\n• ローカルモデル(Ollama / LM Studio / Docker)を使用している場合は、設定でコンテキストサイズを増やしてください\n• より大きなコンテキストウィンドウを持つモデルに切り替えてください
error.invalid_request=⚠️ 無効なリクエスト:{details}\n\n入力内容を確認して、もう一度お試しください。
error.system=⚠️ システムエラー\n\n予期しないエラーが発生しました:{errorCode}\n\nAskimo サポートへ次を添えてご連絡ください:\n• 実行していた操作\n• 上記のエラーコード\n• タイムスタンプ
diff --git a/desktop-shared/src/main/resources/i18n/messages_ko_KR.properties b/desktop-shared/src/main/resources/i18n/messages_ko_KR.properties
index 9001d8c98..aea4cc0fe 100644
--- a/desktop-shared/src/main/resources/i18n/messages_ko_KR.properties
+++ b/desktop-shared/src/main/resources/i18n/messages_ko_KR.properties
@@ -637,6 +637,7 @@ chat.tools.server.scope.project=프로젝트
chat.tools.server.scope.builtin=내장
chat.tools.tool.no.description=설명이 없습니다
chat.tools.button.disabled=사용 가능한 도구 ({0} 비활성화됨)
+chat.tools.button.model.no.support=이 모델은 도구를 지원하지 않습니다
chat.reasoning.effort.label=생각 중
chat.reasoning.effort.tooltip=추론 노력 — 모델이 응답하기 전에 얼마나 깊이 생각할지 제어합니다
chat.reasoning.effort.off=끄기
@@ -987,6 +988,7 @@ error.provider_not_configured=⚠️ AI 제공자가 설정되지 않았습니
error.rate_limit=⚠️ 사용량 제한을 초과했습니다\n{retryAfter}\n\nAPI 사용 한도에 도달했습니다. 잠시 기다린 후 다시 메시지를 보내세요.
error.insufficient_credits=⚠️ 크레딧 부족\n\n크레딧 잔액이 너무 낮습니다.\n\n결제 페이지를 방문하여 크레딧을 추가하세요.
error.timeout=⚠️ {timeout}초 후 요청 시간이 초과되었습니다\n\n가능한 원인:\n• 서버 부하가 높음 - 잠시 후 다시 시도\n• 쿼리가 복잡함 - 더 간단하게 작성\n• 네트워크 지연
+error.context_length=⚠️ 컨텍스트 창 초과\n\n전체 입력(대화 기록 + 메시지 + 첨부 파일)이 너무 커서 이 모델이 처리할 수 없습니다.\n\n해결 방법:\n• 새로운 채팅 세션을 시작하여 대화 기록을 지우세요\n• 메시지를 줄이거나 큰 첨부 파일을 제거하세요\n• 로컬 모델(Ollama / LM Studio / Docker)을 사용하는 경우, 설정에서 컨텍스트 크기를 늘리세요\n• 더 큰 컨텍스트 창을 지원하는 모델로 전환하세요
error.invalid_request=⚠️ 잘못된 요청: {details}\n\n입력 내용을 확인한 뒤 다시 시도하세요.
error.system=⚠️ 시스템 오류\n\n예기치 않은 오류가 발생했습니다: {errorCode}\n\nAskimo 지원팀에 다음 정보를 함께 전달해 주세요:\n• 수행 중이던 작업\n• 위의 오류 코드\n• 타임스탬프
diff --git a/desktop-shared/src/main/resources/i18n/messages_pt_BR.properties b/desktop-shared/src/main/resources/i18n/messages_pt_BR.properties
index a6aeea888..fa2beda1a 100644
--- a/desktop-shared/src/main/resources/i18n/messages_pt_BR.properties
+++ b/desktop-shared/src/main/resources/i18n/messages_pt_BR.properties
@@ -639,6 +639,7 @@ chat.tools.server.scope.project=Projeto
chat.tools.server.scope.builtin=Integrado
chat.tools.tool.no.description=Nenhuma descrição disponível
chat.tools.button.disabled=Ferramentas disponíveis ({0} desativadas)
+chat.tools.button.model.no.support=As ferramentas não são suportadas por este modelo
chat.reasoning.effort.label=Pensando
chat.reasoning.effort.tooltip=Esforço de raciocínio — controla a profundidade com que o modelo pensa antes de responder
chat.reasoning.effort.off=Desligado
@@ -989,6 +990,7 @@ error.provider_not_configured=⚠️ Nenhum provedor de IA configurado.\n\nPara
error.rate_limit=⚠️ Limite de uso excedido\n{retryAfter}\n\nVocê atingiu o limite de uso da API. Aguarde um pouco antes de enviar mais mensagens.
error.insufficient_credits=⚠️ Créditos insuficientes\n\nSeu saldo está muito baixo.\n\nAcesse a página de cobrança para adicionar créditos.
error.timeout=⚠️ A solicitação expirou após {timeout} segundos\n\nIsso pode acontecer por:\n• Alta carga no servidor - tente novamente em instantes\n• Consulta complexa - tente simplificar\n• Latência de rede
+error.context_length=⚠️ Janela de contexto excedida\n\nA entrada total (histórico da conversa + sua mensagem + anexos) é muito grande para este modelo processar.\n\nPara corrigir isso:\n• Inicie uma nova sessão de chat para limpar o histórico da conversa\n• Encurte sua mensagem ou remova anexos grandes\n• Se estiver usando um modelo local (Ollama / LM Studio / Docker), aumente o tamanho do contexto nas configurações dele\n• Mude para um modelo com uma janela de contexto maior
error.invalid_request=⚠️ Solicitação inválida: {details}\n\nVerifique sua entrada e tente novamente.
error.system=⚠️ Erro do sistema\n\nOcorreu um erro inesperado: {errorCode}\n\nEntre em contato com o suporte do Askimo e informe:\n• O que você estava fazendo\n• O código de erro acima\n• O horário (timestamp)
diff --git a/desktop-shared/src/main/resources/i18n/messages_vi_VN.properties b/desktop-shared/src/main/resources/i18n/messages_vi_VN.properties
index e5df301f8..e01f0d943 100644
--- a/desktop-shared/src/main/resources/i18n/messages_vi_VN.properties
+++ b/desktop-shared/src/main/resources/i18n/messages_vi_VN.properties
@@ -644,6 +644,7 @@ chat.tools.server.scope.project=Dự án
chat.tools.server.scope.builtin=Tích hợp sẵn
chat.tools.tool.no.description=Không có mô tả
chat.tools.button.disabled=Công cụ khả dụng ({0} bị vô hiệu hóa)
+chat.tools.button.model.no.support=Các công cụ không được hỗ trợ bởi mô hình này
chat.input.placeholder=Nhập tin nhắn... (Enter để gửi, Shift+Enter để xuống dòng)
chat.select.file=Chọn tệp (nhiều tệp)
chat.attachment.remove=Xóa tệp đính kèm
@@ -984,6 +985,7 @@ error.provider_not_configured=⚠️ Chưa cấu hình nhà cung cấp AI.\n\nĐ
error.rate_limit=⚠️ Đã vượt giới hạn tốc độ\n{retryAfter}\n\nBạn đã đạt giới hạn sử dụng API. Vui lòng chờ một chút trước khi gửi thêm tin nhắn.
error.insufficient_credits=⚠️ Không đủ tín dụng\n\nSố dư tín dụng quá thấp.\n\nVui lòng truy cập trang thanh toán để thêm tín dụng.
error.timeout=⚠️ Yêu cầu đã hết thời gian chờ sau {timeout} giây\n\nNguyên nhân có thể do:\n• Máy chủ đang quá tải - hãy thử lại sau ít phút\n• Truy vấn quá phức tạp - hãy thử đơn giản hóa\n• Độ trễ mạng
+error.context_length=⚠️ Đã vượt quá cửa sổ ngữ cảnh (context window)\n\nTổng dữ liệu đầu vào (lịch sử trò chuyện + tin nhắn của bạn + tệp đính kèm) quá lớn để mô hình này có thể xử lý.\n\nĐể khắc phục:\n• Bắt đầu một phiên trò chuyện mới để xóa lịch sử trò chuyện\n• Rút ngắn tin nhắn hoặc xóa các tệp đính kèm lớn\n• Nếu bạn đang sử dụng mô hình cục bộ (Ollama / LM Studio / Docker), hãy tăng kích thước ngữ cảnh trong phần cài đặt của mô hình đó\n• Chuyển sang mô hình có cửa sổ ngữ cảnh lớn hơn
error.invalid_request=⚠️ Yêu cầu không hợp lệ: {details}\n\nVui lòng kiểm tra nội dung nhập và thử lại.
error.system=⚠️ Lỗi hệ thống\n\nĐã xảy ra lỗi không mong muốn: {errorCode}\n\nVui lòng liên hệ hỗ trợ Askimo và cung cấp:\n• Bạn đang làm gì\n• Mã lỗi ở trên\n• Thời điểm xảy ra lỗi
diff --git a/desktop-shared/src/main/resources/i18n/messages_zh_CN.properties b/desktop-shared/src/main/resources/i18n/messages_zh_CN.properties
index e199022c7..3b88d19aa 100644
--- a/desktop-shared/src/main/resources/i18n/messages_zh_CN.properties
+++ b/desktop-shared/src/main/resources/i18n/messages_zh_CN.properties
@@ -637,6 +637,7 @@ chat.tools.server.scope.project=项目
chat.tools.server.scope.builtin=内置
chat.tools.tool.no.description=暂无描述
chat.tools.button.disabled=可用工具({0} 已禁用)
+chat.tools.button.model.no.support=此模型不支持工具
chat.reasoning.effort.label=思考
chat.reasoning.effort.tooltip=推理力度 — 控制模型在响应前思考的深度
chat.reasoning.effort.off=关闭
@@ -986,6 +987,7 @@ error.provider_not_configured=⚠️ 尚未配置 AI 提供商。\n\n开始聊
error.rate_limit=⚠️ 已超过速率限制\n{retryAfter}\n\n您已达到 API 使用上限,请稍后再发送消息。
error.insufficient_credits=⚠️ 积分不足\n\n您的积分余额过低。\n\n请前往提供商的账单页面充值。
error.timeout=⚠️ 请求在 {timeout} 秒后超时\n\n可能原因:\n• 服务器负载过高,请稍后重试\n• 请求过于复杂,请尝试简化\n• 网络延迟
+error.context_length=⚠️ 上下文窗口已超出限制\n\n总输入内容(对话历史记录 + 您的消息 + 附件)太大,此模型无法处理。\n\n解决方法:\n• 开始新的聊天会话以清除对话历史记录\n• 缩短您的消息或删除较大的附件\n• 如果使用本地模型(Ollama / LM Studio / Docker),请在设置中增加上下文大小\n• 切换到具有更大上下文窗口的模型
error.invalid_request=⚠️ 无效请求:{details}\n\n请检查输入内容后重试。
error.system=⚠️ 系统错误\n\n发生了意外错误:{errorCode}\n\n请联系 Askimo 支持,并提供:\n• 您的操作步骤\n• 上述错误代码\n• 发生时间
diff --git a/desktop-shared/src/main/resources/i18n/messages_zh_TW.properties b/desktop-shared/src/main/resources/i18n/messages_zh_TW.properties
index ab4bf799a..541b9168e 100644
--- a/desktop-shared/src/main/resources/i18n/messages_zh_TW.properties
+++ b/desktop-shared/src/main/resources/i18n/messages_zh_TW.properties
@@ -636,6 +636,7 @@ chat.tools.server.scope.project=專案
chat.tools.server.scope.builtin=內建
chat.tools.tool.no.description=暫無描述
chat.tools.button.disabled=可用工具({0} 已停用)
+chat.tools.button.model.no.support=此模型不支援工具
chat.reasoning.effort.label=思考
chat.reasoning.effort.tooltip=推理力度 — 控制模型在回應前思考的深度
chat.reasoning.effort.off=關閉
@@ -987,6 +988,7 @@ error.provider_not_configured=⚠️ 尚未設定 AI 提供商。\n\n開始聊
error.rate_limit=⚠️ 已超過使用速率限制\n{retryAfter}\n\n您已達到 API 使用上限,請稍後再嘗試傳送訊息。
error.insufficient_credits=⚠️ 積分不足\n\n您的積分餘額過低。\n\n請前往提供商的帳單頁面充值。
error.timeout=⚠️ 請求於 {timeout} 秒後逾時\n\n可能原因包括:\n• 伺服器負載過高,請稍後再試\n• 查詢內容過於複雜,請嘗試簡化\n• 網路延遲
+error.context_length=⚠️ 上下文視窗已超出限制\n\n總輸入內容(對話歷史記錄 + 您的訊息 + 附件)太大,此模型無法處理。\n\n解決方法:\n• 開始新的聊天對話以清除對話歷史記錄\n• 縮短您的訊息或刪除較大的附件\n• 如果使用本地模型(Ollama / LM Studio / Docker),請在設定中增加上下文大小\n• 切換到具有更大上下文視窗的模型
error.invalid_request=⚠️ 無效的請求:{details}\n\n請檢查輸入內容後再試一次。
error.system=⚠️ 系統錯誤\n\n發生未預期的錯誤:{errorCode}\n\n請聯絡 Askimo 支援,並提供:\n• 您當時的操作\n• 上方的錯誤代碼\n• 發生的時間
diff --git a/shared/src/main/kotlin/io/askimo/core/event/internal/ToolSupportDetectedEvent.kt b/shared/src/main/kotlin/io/askimo/core/event/internal/ToolSupportDetectedEvent.kt
new file mode 100644
index 000000000..58242c489
--- /dev/null
+++ b/shared/src/main/kotlin/io/askimo/core/event/internal/ToolSupportDetectedEvent.kt
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: AGPLv3
+ *
+ * Copyright (c) 2026 Askimo
+ */
+package io.askimo.core.event.internal
+
+import io.askimo.core.event.Event
+import io.askimo.core.event.EventSource
+import io.askimo.core.event.EventType
+import io.askimo.core.providers.ModelProvider
+import java.time.Instant
+
+/**
+ * Fired by [io.askimo.core.providers.ModelCapabilitiesCache] after the tool-support probe
+ * completes for a model. UI components (e.g. ChatInputField) can listen
+ * for this to reactively show/hide the tool calling controls.
+ */
+data class ToolSupportDetectedEvent(
+ val provider: ModelProvider,
+ val model: String,
+ val supportsTools: Boolean,
+ override val timestamp: Instant = Instant.now(),
+ override val source: EventSource = EventSource.SYSTEM,
+) : Event {
+ override val type = EventType.INTERNAL
+
+ override fun getDetails() = "Tool support for $model ($provider): $supportsTools"
+}
diff --git a/shared/src/main/kotlin/io/askimo/core/exception/ExceptionMapper.kt b/shared/src/main/kotlin/io/askimo/core/exception/ExceptionMapper.kt
index 60fd73acb..57e96a2e3 100644
--- a/shared/src/main/kotlin/io/askimo/core/exception/ExceptionMapper.kt
+++ b/shared/src/main/kotlin/io/askimo/core/exception/ExceptionMapper.kt
@@ -161,6 +161,20 @@ object ExceptionMapper {
combinedMessage.contains("upgrade or purchase credits", ignoreCase = true) ->
InsufficientCreditsException(cause = rootCause)
+ // Context window exceeded (checked before generic 400 to avoid misclassification)
+ (
+ combinedMessage.contains("context", ignoreCase = true) && (
+ combinedMessage.contains("length", ignoreCase = true) ||
+ combinedMessage.contains("limit", ignoreCase = true) ||
+ combinedMessage.contains("exceeded", ignoreCase = true) ||
+ combinedMessage.contains("too long", ignoreCase = true) ||
+ combinedMessage.contains("maximum context", ignoreCase = true) ||
+ combinedMessage.contains("token limit", ignoreCase = true) ||
+ combinedMessage.contains("exceed", ignoreCase = true)
+ )
+ ) || combinedMessage.contains("413", ignoreCase = true) ->
+ ContextLengthException(cause = rootCause)
+
// Invalid request
combinedMessage.contains("400", ignoreCase = true) ||
combinedMessage.contains("bad request", ignoreCase = true) ||
diff --git a/shared/src/main/kotlin/io/askimo/core/exception/UserException.kt b/shared/src/main/kotlin/io/askimo/core/exception/UserException.kt
index 72ec35e97..e54cae1a5 100644
--- a/shared/src/main/kotlin/io/askimo/core/exception/UserException.kt
+++ b/shared/src/main/kotlin/io/askimo/core/exception/UserException.kt
@@ -128,6 +128,17 @@ class InsufficientCreditsException(
override fun getMessageArgs() = emptyMap()
}
+/**
+ * Context window exceeded after all automatic retries have been exhausted.
+ * Triggered when the total input (history + message + attachments) is too large for the model.
+ */
+class ContextLengthException(
+ cause: Throwable? = null,
+) : UserException("Context window exceeded", cause) {
+ override fun getMessageKey() = "error.context_length"
+ override fun getMessageArgs() = emptyMap()
+}
+
/**
* No AI provider has been configured yet (currentProvider == UNKNOWN).
*/
diff --git a/shared/src/main/kotlin/io/askimo/core/providers/ChatModelFactory.kt b/shared/src/main/kotlin/io/askimo/core/providers/ChatModelFactory.kt
index 8453c643c..0098a6000 100644
--- a/shared/src/main/kotlin/io/askimo/core/providers/ChatModelFactory.kt
+++ b/shared/src/main/kotlin/io/askimo/core/providers/ChatModelFactory.kt
@@ -191,8 +191,8 @@ interface ChatModelFactory {
testClientBuilder.tools(LocalFsTools)
}
- val testClient = testClientBuilder.build()
- testClient.sendStreamingMessageWithCallback(null, UserMessage("Capability probe — reply with 'ok'."))
+ val testClient = testClientBuilder.maxToolCallingRoundTrips(1).build()
+ testClient.sendStreamingMessageWithCallback(null, UserMessage("Capability tool probe — reply with 'ok'."))
true
} catch (e: Exception) {
val errorMessage = e.message?.lowercase() ?: ""
diff --git a/shared/src/main/kotlin/io/askimo/core/providers/ModelCapabilitiesCache.kt b/shared/src/main/kotlin/io/askimo/core/providers/ModelCapabilitiesCache.kt
index 8bd3127cd..b2e3f0e45 100644
--- a/shared/src/main/kotlin/io/askimo/core/providers/ModelCapabilitiesCache.kt
+++ b/shared/src/main/kotlin/io/askimo/core/providers/ModelCapabilitiesCache.kt
@@ -7,6 +7,7 @@ package io.askimo.core.providers
import io.askimo.core.event.EventBus
import io.askimo.core.event.internal.ImageCapabilityDetectedEvent
import io.askimo.core.event.internal.ThinkingSupportDetectedEvent
+import io.askimo.core.event.internal.ToolSupportDetectedEvent
import io.askimo.core.event.system.InvalidateCacheEvent
import io.askimo.core.logging.logger
import io.askimo.core.util.AskimoHome
@@ -271,6 +272,8 @@ object ModelCapabilitiesCache {
val modelKey = modelKey(provider, model)
update(modelKey) { it.copy(supportsTools = supported) }
log.debug("Updated tool support for $modelKey: $supported")
+ // Broadcast so UI components (e.g. ChatInputField) can reactively update
+ EventBus.post(ToolSupportDetectedEvent(provider = provider, model = model, supportsTools = supported))
}
/**
diff --git a/shared/src/main/kotlin/io/askimo/core/providers/OpenAiCompatibleChatModelFactory.kt b/shared/src/main/kotlin/io/askimo/core/providers/OpenAiCompatibleChatModelFactory.kt
index 06a2fecc9..b498f2d19 100644
--- a/shared/src/main/kotlin/io/askimo/core/providers/OpenAiCompatibleChatModelFactory.kt
+++ b/shared/src/main/kotlin/io/askimo/core/providers/OpenAiCompatibleChatModelFactory.kt
@@ -23,6 +23,10 @@ import io.askimo.core.context.AppContext
import io.askimo.core.context.ExecutionMode
import io.askimo.core.telemetry.TelemetryChatModelListener
import io.askimo.core.util.ProxyUtil
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.launch
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.net.http.HttpClient
@@ -189,18 +193,34 @@ abstract class OpenAiCompatibleChatModelFactory : ChatModelFactory
// Create streaming model once — reused for both the tool probe and the real client
val streamingModel = createStreamingModel(settings)
- // Probe tool support once — result is persisted in ModelCapabilitiesCache
+ // Probe tool support once — run async so it never blocks the caller.
+ // Optimistically mark as true (supported) so tools are available immediately;
+ // the background probe will call setToolSupport() with the real result,
+ // which fires ToolSupportDetectedEvent so the UI reacts if the model rejects tools.
if (executionMode.isToolEnabled() &&
!ModelCapabilitiesCache.hasTestedToolSupport(getProvider(), settings.defaultModel)
) {
- val supportsTools = probeToolSupport(settings.defaultModel, streamingModel, executionMode)
- ModelCapabilitiesCache.setToolSupport(getProvider(), settings.defaultModel, supportsTools)
+ ModelCapabilitiesCache.setToolSupport(getProvider(), settings.defaultModel, true)
+ val provider = getProvider()
+ val modelName = settings.defaultModel
+ CoroutineScope(Dispatchers.IO + SupervisorJob()).launch {
+ val supportsTools = probeToolSupport(modelName, streamingModel, executionMode)
+ ModelCapabilitiesCache.setToolSupport(provider, modelName, supportsTools)
+ }
}
- // Probe image generation capability once — result is persisted in ModelCapabilitiesCache
+ // Probe image generation capability once — run async so it never blocks the caller.
+ // Optimistically mark as false (not supported) so the UI is usable immediately;
+ // the background probe will call setImageSupport() again with the real result,
+ // which fires ImageCapabilityDetectedEvent so the UI reacts without a restart.
if (!ModelCapabilitiesCache.hasTestedImageSupport(getProvider(), settings.defaultModel)) {
- val supportsImage = probeImageCapability(getProvider(), settings.defaultModel, streamingModel)
- ModelCapabilitiesCache.setImageSupport(getProvider(), settings.defaultModel, supportsImage)
+ ModelCapabilitiesCache.setImageSupport(getProvider(), settings.defaultModel, false)
+ val provider = getProvider()
+ val modelName = settings.defaultModel
+ CoroutineScope(Dispatchers.IO + SupervisorJob()).launch {
+ val supportsImage = probeImageCapability(provider, modelName, streamingModel)
+ ModelCapabilitiesCache.setImageSupport(provider, modelName, supportsImage)
+ }
}
return AiServiceBuilder.buildChatClient(
diff --git a/shared/src/main/kotlin/io/askimo/core/providers/anthropic/AnthropicModelFactory.kt b/shared/src/main/kotlin/io/askimo/core/providers/anthropic/AnthropicModelFactory.kt
index f92a4393a..0915d08b1 100644
--- a/shared/src/main/kotlin/io/askimo/core/providers/anthropic/AnthropicModelFactory.kt
+++ b/shared/src/main/kotlin/io/askimo/core/providers/anthropic/AnthropicModelFactory.kt
@@ -34,6 +34,10 @@ import io.askimo.core.util.ApiKeyUtils.safeApiKey
import io.askimo.core.util.ProxyUtil
import io.askimo.core.util.appJson
import io.askimo.core.util.httpGet
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.launch
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
@@ -79,18 +83,32 @@ class AnthropicModelFactory : ChatModelFactory {
// Create streaming model once — reused for both the tool probe and the real client
val streamingModel = createStreamingModel(settings)
- // Probe tool support once — result is persisted in ModelCapabilitiesCache
+ // Probe tool support once — run async so it never blocks the caller.
+ // Optimistically mark as true (supported) so tools are available immediately;
+ // the background probe will call setToolSupport() with the real result,
+ // which fires ToolSupportDetectedEvent so the UI reacts if the model rejects tools.
if (executionMode.isToolEnabled() &&
!ModelCapabilitiesCache.hasTestedToolSupport(ANTHROPIC, settings.defaultModel)
) {
- val supportsTools = probeToolSupport(settings.defaultModel, streamingModel, executionMode)
- ModelCapabilitiesCache.setToolSupport(ANTHROPIC, settings.defaultModel, supportsTools)
+ ModelCapabilitiesCache.setToolSupport(ANTHROPIC, settings.defaultModel, true)
+ val modelName = settings.defaultModel
+ CoroutineScope(Dispatchers.IO + SupervisorJob()).launch {
+ val supportsTools = probeToolSupport(modelName, streamingModel, executionMode)
+ ModelCapabilitiesCache.setToolSupport(ANTHROPIC, modelName, supportsTools)
+ }
}
- // Probe image generation capability once — result is persisted in ModelCapabilitiesCache
+ // Probe image generation capability once — run async so it never blocks the caller.
+ // Optimistically mark as false (not supported) so the UI is usable immediately;
+ // the background probe will call setImageSupport() again with the real result,
+ // which fires ImageCapabilityDetectedEvent so the UI reacts without a restart.
if (!ModelCapabilitiesCache.hasTestedImageSupport(ANTHROPIC, settings.defaultModel)) {
- val supportsImage = probeImageCapability(ANTHROPIC, settings.defaultModel, streamingModel)
- ModelCapabilitiesCache.setImageSupport(ANTHROPIC, settings.defaultModel, supportsImage)
+ ModelCapabilitiesCache.setImageSupport(ANTHROPIC, settings.defaultModel, false)
+ val modelName = settings.defaultModel
+ CoroutineScope(Dispatchers.IO + SupervisorJob()).launch {
+ val supportsImage = probeImageCapability(ANTHROPIC, modelName, streamingModel)
+ ModelCapabilitiesCache.setImageSupport(ANTHROPIC, modelName, supportsImage)
+ }
}
return AiServiceBuilder.buildChatClient(
diff --git a/shared/src/main/kotlin/io/askimo/core/providers/gemini/GeminiModelFactory.kt b/shared/src/main/kotlin/io/askimo/core/providers/gemini/GeminiModelFactory.kt
index 85d549930..ed4d3734d 100644
--- a/shared/src/main/kotlin/io/askimo/core/providers/gemini/GeminiModelFactory.kt
+++ b/shared/src/main/kotlin/io/askimo/core/providers/gemini/GeminiModelFactory.kt
@@ -39,6 +39,10 @@ import io.askimo.core.providers.sendStreamingMessageWithCallback
import io.askimo.core.telemetry.TelemetryChatModelListener
import io.askimo.core.util.ApiKeyUtils.safeApiKey
import io.askimo.core.util.ProxyUtil
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.launch
import java.net.http.HttpClient
import java.time.Duration
@@ -78,18 +82,35 @@ class GeminiModelFactory : ChatModelFactory {
// Create streaming model once — reused for both the tool probe and the real client
val streamingModel = createStreamingModel(settings)
- // Probe tool support once — result is persisted in ModelCapabilitiesCache
+ // Probe tool support once — run async so it never blocks the caller.
+ // Optimistically mark as true (supported) so tools are available immediately;
+ // the background probe will call setToolSupport() with the real result,
+ // which fires ToolSupportDetectedEvent so the UI reacts if the model rejects tools.
if (executionMode.isToolEnabled() &&
!ModelCapabilitiesCache.hasTestedToolSupport(GEMINI, settings.defaultModel)
) {
- val supportsTools = probeToolSupport(settings.defaultModel, streamingModel, executionMode)
- ModelCapabilitiesCache.setToolSupport(GEMINI, settings.defaultModel, supportsTools)
+ ModelCapabilitiesCache.setToolSupport(GEMINI, settings.defaultModel, true)
+ val modelName = settings.defaultModel
+ val capturedModel = streamingModel
+ val capturedMode = executionMode
+ CoroutineScope(Dispatchers.IO + SupervisorJob()).launch {
+ val supportsTools = probeToolSupport(modelName, capturedModel, capturedMode)
+ ModelCapabilitiesCache.setToolSupport(GEMINI, modelName, supportsTools)
+ }
}
- // Probe image generation capability once — result is persisted in ModelCapabilitiesCache
+ // Probe image generation capability once — run async so it never blocks the caller.
+ // Optimistically mark as false (not supported) so the UI is usable immediately;
+ // the background probe will call setImageSupport() again with the real result,
+ // which fires ImageCapabilityDetectedEvent so the UI reacts without a restart.
if (!ModelCapabilitiesCache.hasTestedImageSupport(GEMINI, settings.defaultModel)) {
- val supportsImage = probeImageCapability(GEMINI, settings.defaultModel, streamingModel)
- ModelCapabilitiesCache.setImageSupport(GEMINI, settings.defaultModel, supportsImage)
+ ModelCapabilitiesCache.setImageSupport(GEMINI, settings.defaultModel, false)
+ val modelName = settings.defaultModel
+ val capturedModel = streamingModel
+ CoroutineScope(Dispatchers.IO + SupervisorJob()).launch {
+ val supportsImage = probeImageCapability(GEMINI, modelName, capturedModel)
+ ModelCapabilitiesCache.setImageSupport(GEMINI, modelName, supportsImage)
+ }
}
return AiServiceBuilder.buildChatClient(
diff --git a/shared/src/main/kotlin/io/askimo/tools/datetime/DateTimeTools.kt b/shared/src/main/kotlin/io/askimo/tools/datetime/DateTimeTools.kt
index a72ff6c6d..7f899e6c1 100644
--- a/shared/src/main/kotlin/io/askimo/tools/datetime/DateTimeTools.kt
+++ b/shared/src/main/kotlin/io/askimo/tools/datetime/DateTimeTools.kt
@@ -25,7 +25,6 @@ import java.util.Locale
* - [calculateDateDiff]: Number of days between two dates.
* - [addDaysToDate]: Add or subtract days from a date.
*/
-@Suppress("unused")
object DateTimeTools {
private const val CLASS_NAME = "io.askimo.tools.datetime.DateTimeTools"
diff --git a/tools/git/pre-commit b/tools/git/pre-commit
index 600f66ffe..322f601cb 100755
--- a/tools/git/pre-commit
+++ b/tools/git/pre-commit
@@ -29,16 +29,16 @@ fi
echo "✅ Detekt passed."
-#echo "🔍 Checking i18n keys..."
-#./gradlew desktop:checkI18nKeys
-#
-## Check if i18n check failed
-#if [ $? -ne 0 ]; then
-# echo "❌ i18n key check failed! Please fix missing or inconsistent keys before committing."
-# exit 1
-#fi
-#
-#echo "✅ i18n keys check passed."
+echo "🔍 Checking i18n keys..."
+./gradlew desktop:checkI18nKeys
+
+# Check if i18n check failed
+if [ $? -ne 0 ]; then
+ echo "❌ i18n key check failed! Please fix missing or inconsistent keys before committing."
+ exit 1
+fi
+
+echo "✅ i18n keys check passed."
EOF
# Make the hook executable