diff --git a/main.go b/main.go index 601f970..018dde7 100644 --- a/main.go +++ b/main.go @@ -1136,24 +1136,49 @@ func parseStream(ctx context.Context, r io.Reader) (Message, Usage, error) { var order []int var toolCallsShown bool - // Live, in-place display of tool calls as they stream in, so the user sees - // the invocation text being shaped instead of a blank pause. Only used when - // stdout is a terminal; piped/redirected output is left untouched. + // Progressive, per-token display of tool calls as they stream in, so the + // user watches the invocation being shaped token by token (like reasoning + // text) instead of a blank pause. Only used when stdout is a terminal; + // piped/redirected output is left untouched. Each fragment is appended + // directly to the terminal; the closing ")" is emitted only when the call is + // finalized, so the visible text is always a true prefix of the final form. tty := isTerminal(int(os.Stdout.Fd())) liveIdx := -1 - liveActive := false - showLive := func() { + livePrinted := "" + liveTarget := func(t *ToolCall, closed bool) string { + s := "[tool call: " + t.Function.Name + if closed || t.Function.Arguments != "" { + s += "(" + filterText(t.Function.Arguments) + if closed { s += ")]" } + } + return s + } + finishLive := func(closed bool) { + if liveIdx < 0 { return } + target := liveTarget(tcs[liveIdx], closed) + if len(target) > len(livePrinted) { + fmt.Print(c(target[len(livePrinted):], 33)) + } + if closed { fmt.Print("\n") } + livePrinted = "" + liveIdx = -1 + } + stepLive := func() { if !tty || len(order) == 0 { return } cur := order[len(order)-1] - txt := fmt.Sprintf("[tool call: %s(%s)]", tcs[cur].Function.Name, filterText(tcs[cur].Function.Arguments)) - if liveActive && liveIdx != cur { - // A new tool call started; finalize the previous live line. - fmt.Print("\n") - liveActive = false + if liveIdx != -1 && liveIdx != cur { + // A different tool call is now being shaped: finalize the previous one. + finishLive(true) + } + if liveIdx == -1 { + liveIdx = cur + livePrinted = "" + } + target := liveTarget(tcs[cur], false) + if len(target) > len(livePrinted) { + fmt.Print(c(target[len(livePrinted):], 33)) + livePrinted = target } - fmt.Print("\r\033[K" + c(txt, 33)) - liveActive = true - liveIdx = cur toolCallsShown = true } @@ -1247,7 +1272,7 @@ func parseStream(ctx context.Context, r io.Reader) (Message, Usage, error) { if tc.Function.Name != "" { t.Function.Name += tc.Function.Name } if tc.Function.Arguments != "" { t.Function.Arguments += tc.Function.Arguments } } - showLive() + stepLive() } } if err := ctx.Err(); err != nil { return Message{}, lastUsage, err } @@ -1260,10 +1285,9 @@ func parseStream(ctx context.Context, r io.Reader) (Message, Usage, error) { fmt.Println(renderMDLine(lineBuf, &mdSt)) } } - if liveActive { + if liveIdx >= 0 { // Finalize the in-progress tool-call line so following output starts fresh. - fmt.Print("\n") - liveActive = false + finishLive(true) } if inReasoning { fmt.Print("\n" + c("--- reasoning end ---", 36) + "\n")