Keep the last tool call outside its group once it finishes
A call left its group only while it was running, so the moment a command ended it vanished behind "Called 3 tools" -- and a session that has run its last command and is composing its answer, or has finished the turn entirely, spends most of its time in exactly that state. What folds a call back into its run is therefore not finishing but being overtaken: anything arriving behind it, a reply included, makes it history. Standing outside the run is the call's place in the list as it is now rather than something recorded on the call, so it is asked of the list while grouping it, where the rest of that decision already lives. Checked with ktfmtFormat, compileDebugKotlin, testDebugUnitTest and lintDebug, and on the emulator against the sandbox: "/tools 3 1" settles as "Called 2 tools" with the third Bash card beneath it, and folds to "Called 3 tools" the moment the next reply lands.
This commit is contained in:
1 parent
036eb375aa
commit
1e52b2910c
4 files changed
+75
-42
No files matched your search
@@ -5,9 +5,10 @@ import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* How a run of tool calls is cut into rows: the call still running is drawn on its own, and every
|
||||
* piece the cut leaves behind still has a key of its own -- two rows sharing one key take the app
|
||||
* down, and a key that moves takes the reader's place with it.
|
||||
* How a run of tool calls is cut into rows: the call still running and the last call in the
|
||||
* transcript are drawn on their own, and every piece the cut leaves behind still has a key of its
|
||||
* own -- two rows sharing one key take the app down, and a key that moves takes the reader's place
|
||||
* with it.
|
||||
*/
|
||||
class ToolRowsTest {
|
||||
private var seq = 0L
|
||||
@@ -23,10 +24,13 @@ class ToolRowsTest {
|
||||
done = done,
|
||||
)
|
||||
|
||||
/** Something that is not a tool call, to put behind the run so its last call folds in. */
|
||||
private fun reply() = TranscriptItem.AssistantMsg(seq = ++seq, text = "done")
|
||||
|
||||
private fun shape(rows: List<TranscriptRow>) = rows.map { row ->
|
||||
when (row) {
|
||||
is TranscriptRow.Tools -> row.calls.map { it.id }
|
||||
is TranscriptRow.Single -> listOf((row.item as TranscriptItem.ToolRun).id)
|
||||
is TranscriptRow.Single -> listOf((row.item as? TranscriptItem.ToolRun)?.id ?: "reply")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,9 +41,18 @@ class ToolRowsTest {
|
||||
fun the_call_still_running_is_a_row_of_its_own() {
|
||||
val rows =
|
||||
groupToolRuns(
|
||||
listOf(call("a"), call("b", runId = "a"), call("c", runId = "a", done = false))
|
||||
listOf(
|
||||
call("a"),
|
||||
call("b", runId = "a"),
|
||||
call("c", runId = "a", done = false),
|
||||
call("d", runId = "a"),
|
||||
reply(),
|
||||
)
|
||||
)
|
||||
assertEquals(listOf(listOf("a", "b"), listOf("c")), shape(rows))
|
||||
assertEquals(
|
||||
listOf(listOf("a", "b"), listOf("c"), listOf("d"), listOf("reply")),
|
||||
shape(rows),
|
||||
)
|
||||
assertKeysDistinct(rows)
|
||||
}
|
||||
|
||||
@@ -52,9 +65,13 @@ class ToolRowsTest {
|
||||
call("b", runId = "a", done = false),
|
||||
call("c", runId = "a"),
|
||||
call("d", runId = "a"),
|
||||
reply(),
|
||||
)
|
||||
)
|
||||
assertEquals(listOf(listOf("a"), listOf("b"), listOf("c", "d")), shape(rows))
|
||||
assertEquals(
|
||||
listOf(listOf("a"), listOf("b"), listOf("c", "d"), listOf("reply")),
|
||||
shape(rows),
|
||||
)
|
||||
assertKeysDistinct(rows)
|
||||
}
|
||||
|
||||
@@ -71,23 +88,34 @@ class ToolRowsTest {
|
||||
assertEquals("b", rows.first().key)
|
||||
}
|
||||
|
||||
/** What happens the moment a command finishes: it folds back into the run it was cut out of. */
|
||||
/**
|
||||
* Finishing is not what folds a call back in -- being overtaken is. A session that has run its
|
||||
* last command and is writing its reply leaves that command standing until the reply starts.
|
||||
*/
|
||||
@Test
|
||||
fun a_call_that_finishes_rejoins_its_run_without_moving_the_run() {
|
||||
val running = listOf(call("a"), call("b", runId = "a", done = false))
|
||||
val finished = listOf(running[0], (running[1] as TranscriptItem.ToolRun).copy(done = true))
|
||||
val before = groupToolRuns(running)
|
||||
val after = groupToolRuns(finished)
|
||||
assertEquals(listOf(listOf("a", "b")), shape(after))
|
||||
// The run keeps the key it was drawn under, so the list rebuilds a row rather than losing
|
||||
// its anchor.
|
||||
assertEquals(before.first().key, after.first().key)
|
||||
fun the_last_call_stays_out_when_it_finishes_and_folds_in_when_something_follows() {
|
||||
val a = call("a")
|
||||
val running = call("b", runId = "a", done = false)
|
||||
val finished = running.copy(done = true)
|
||||
val whileRunning = groupToolRuns(listOf(a, running))
|
||||
val afterItEnds = groupToolRuns(listOf(a, finished))
|
||||
val afterTheReply = groupToolRuns(listOf(a, finished, reply()))
|
||||
assertEquals(listOf(listOf("a"), listOf("b")), shape(whileRunning))
|
||||
assertEquals(listOf(listOf("a"), listOf("b")), shape(afterItEnds))
|
||||
assertEquals(listOf(listOf("a", "b"), listOf("reply")), shape(afterTheReply))
|
||||
// The run keeps the key it was drawn under throughout, so the list rebuilds a row rather
|
||||
// than losing its anchor.
|
||||
assertEquals(whileRunning.first().key, afterItEnds.first().key)
|
||||
assertEquals(whileRunning.first().key, afterTheReply.first().key)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun a_run_of_finished_calls_is_still_one_group() {
|
||||
val rows = groupToolRuns(listOf(call("a"), call("b", runId = "a"), call("c", runId = "a")))
|
||||
assertEquals(listOf(listOf("a", "b", "c")), shape(rows))
|
||||
assertTrue(rows.single() is TranscriptRow.Tools, "$rows")
|
||||
fun a_run_of_finished_calls_is_one_group_once_something_follows_it() {
|
||||
val rows =
|
||||
groupToolRuns(
|
||||
listOf(call("a"), call("b", runId = "a"), call("c", runId = "a"), reply())
|
||||
)
|
||||
assertEquals(listOf(listOf("a", "b", "c"), listOf("reply")), shape(rows))
|
||||
assertTrue(rows.first() is TranscriptRow.Tools, "$rows")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user