feat(chat): surface model switch warning messages in chat (#1239)

This commit is contained in:
m57 authored and GitHub committed 2026-09-23 15:33:54 +03:00
1 parent 4a732870d7
commit 8cd1ec5298
3 files changed
+107 -1

No files matched your search

@@ -340,6 +340,19 @@ class ChatModelSwitchDelegate(
it.copy(modelSwitchConfirmMessage = confirmMessage)
}
}
} else {
if (pending != null && pending.sequence == modelSwitchSequence) {
val warning = (map["warning"] as? String)?.trim()
if (!warning.isNullOrBlank()) {
val formattedWarning =
if (warning.startsWith("⚠") || warning.startsWith("⚠️")) {
warning
} else {
"⚠ $warning"
}
addAssistantMessage(formattedWarning)
}
}
}
} else if (key == "fast") {
val rawVal = map["value"] as? String
@@ -647,4 +647,95 @@ class ChatModelSwitchDelegateTest {
delegate.dismissModelSwitchConfirm()
assertEquals("anthropic/claude-3", uiState.value.currentSessionModel)
}
@Test
fun handleConfigSetResult_modelWarningWithoutConfirm_addsAssistantMessage() =
testScope.runTest {
delegate.handleModelSwitch("/model gpt-69-sol --provider openai-codex --session")
advanceUntilIdle()
val warningText =
"Note: `gpt-69-sol` was not found in the OpenAI Codex model listing. " +
"Similar models: `gpt-5.6-sol`"
delegate.handleConfigSetResult(
id = "req-1",
result =
mapOf(
"key" to "model",
"value" to "gpt-69-sol",
"confirm_required" to false,
"warning" to warningText,
),
)
assertEquals(1, assistantMessages.size)
assertEquals("⚠ $warningText", assistantMessages.first())
}
@Test
fun handleConfigSetResult_modelWarningWithExistingWarningEmoji_doesNotDuplicateEmoji() =
testScope.runTest {
delegate.handleModelSwitch("/model gpt-69-sol --provider openai-codex --session")
advanceUntilIdle()
val warningText = "⚠ Note: Already prefixed"
delegate.handleConfigSetResult(
id = "req-1",
result =
mapOf(
"key" to "model",
"value" to "gpt-69-sol",
"confirm_required" to false,
"warning" to warningText,
),
)
assertEquals(listOf(warningText), assistantMessages)
}
@Test
fun handleConfigSetResult_staleSequenceWarning_isIgnored() =
testScope.runTest {
// First switch
delegate.handleModelSwitch("/model gpt-4o --provider openai --session")
advanceUntilIdle()
// Second switch supersedes first
delegate.handleModelSwitch("/model claude-3-5-sonnet --provider anthropic --session")
advanceUntilIdle()
// First switch response arrives with warning
delegate.handleConfigSetResult(
id = "req-1",
result =
mapOf(
"key" to "model",
"value" to "gpt-4o",
"confirm_required" to false,
"warning" to "Stale warning",
),
)
assertTrue(assistantMessages.isEmpty())
}
@Test
fun handleConfigSetResult_blankWarning_doesNotAddAssistantMessage() =
testScope.runTest {
delegate.handleModelSwitch("/model gpt-4o --provider openai --session")
advanceUntilIdle()
delegate.handleConfigSetResult(
id = "req-1",
result =
mapOf(
"key" to "model",
"value" to "gpt-4o",
"confirm_required" to false,
"warning" to " ",
),
)
assertTrue(assistantMessages.isEmpty())
}
}
@@ -38,7 +38,9 @@ class KanbanScopeTest {
repository = mockRepository,
preferences = preferences,
eventsClientProvider = { mockEventsClient },
)
).also {
it.ioDispatcher = testDispatcher
}
@Before
fun setUp() {