mirror of
https://github.com/patriceckhart/zot.git
synced 2026-06-26 21:36:31 +02:00
feat(tui): show read's line range in the tool header
read calls now render their requested line range next to the
path, so you can see at a glance what slice of the file the
model looked at.
before: ▸ read /Users/pat/Developer/zot/internal/tui/view.go
after : ▸ read /Users/pat/Developer/zot/internal/tui/view.go:723-772
▸ read /Users/pat/Developer/zot/internal/tui/view.go:100-
▸ read /Users/pat/Developer/zot/internal/tui/view.go
The ":START-END" suffix appears when the call had a limit arg;
the ":START-" (open-ended) form appears when only offset was
supplied; no suffix appears for whole-file reads (the common
case). Other tools (write, edit, bash) are unchanged - their
args don't carry a range.
Implementation:
- shortArgs -> ShortArgs (exported), now takes the tool name
as a first arg so it can add shape-specific decorations.
For read, parses offset/limit from the args and appends the
range; for everything else it falls back to the old
path-or-command truncated-at-60 shape.
- The truncation budget shrinks by the length of the suffix
so absurdly long paths still leave the range visible (path
gets the "..." in the middle, range stays intact at the
tail).
- toInt helper coerces float64 (json.Unmarshal's default),
int, and numeric strings so we survive the occasional model
that returns "100" instead of 100.
- Dropped the duplicate unexported shortArgs in interactive.go
(pre-dated the tui package's version). All call sites now
go through tui.ShortArgs(name, args); the json import that
only the local copy needed is gone too.
No format string changes elsewhere; the extension intercept
protocol, rpc wire schema, and session file format don't see
the header string.
This commit is contained in:
parent
bb50aa3044
commit
8927ac15dc
2 changed files with 85 additions and 42 deletions
|
|
@ -2,7 +2,6 @@ package modes
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
|
@ -2268,13 +2267,13 @@ func (i *Interactive) handleEvent(ev core.AgentEvent) {
|
|||
// refresh the final Args summary. Otherwise create a new one
|
||||
// (non-streaming providers or legacy paths).
|
||||
if tc, ok := i.toolCalls[e.ID]; ok {
|
||||
tc.Args = shortArgs(e.Args)
|
||||
tc.Args = tui.ShortArgs(e.Name, e.Args)
|
||||
tc.Streaming = false
|
||||
} else {
|
||||
i.toolCalls[e.ID] = &tui.ToolCallView{
|
||||
ID: e.ID,
|
||||
Name: e.Name,
|
||||
Args: shortArgs(e.Args),
|
||||
Args: tui.ShortArgs(e.Name, e.Args),
|
||||
}
|
||||
i.toolOrder = append(i.toolOrder, e.ID)
|
||||
}
|
||||
|
|
@ -2326,29 +2325,6 @@ func (i *Interactive) Agent() *core.Agent {
|
|||
return i.agent
|
||||
}
|
||||
|
||||
func shortArgs(raw json.RawMessage) string {
|
||||
var v any
|
||||
if err := json.Unmarshal(raw, &v); err != nil {
|
||||
return ""
|
||||
}
|
||||
if m, ok := v.(map[string]any); ok {
|
||||
for _, k := range []string{"path", "file_path", "command"} {
|
||||
if s, ok := m[k].(string); ok {
|
||||
if len(s) > 60 {
|
||||
s = s[:57] + "..."
|
||||
}
|
||||
return s
|
||||
}
|
||||
}
|
||||
}
|
||||
b, _ := json.Marshal(v)
|
||||
s := string(b)
|
||||
if len(s) > 60 {
|
||||
s = s[:57] + "..."
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// silence unused import in some build configs
|
||||
var _ = fmt.Sprintf
|
||||
|
||||
|
|
|
|||
|
|
@ -451,7 +451,7 @@ func (v *View) renderMessage(m provider.Message, width int) []string {
|
|||
// assistant prose above. The matching closing rule
|
||||
// is emitted at the end of the tool-role message.
|
||||
lines = append(lines, toolBlockRule(v.Theme, width))
|
||||
lines = append(lines, indent+v.Theme.FG256(v.Theme.Tool, "▸ "+b.Name+" "+shortArgs(b.Arguments)))
|
||||
lines = append(lines, indent+v.Theme.FG256(v.Theme.Tool, "▸ "+b.Name+" "+ShortArgs(b.Name, b.Arguments)))
|
||||
}
|
||||
}
|
||||
case provider.RoleTool:
|
||||
|
|
@ -1158,28 +1158,95 @@ func toolResultBlock(th Theme, text string, width int, color int) []string {
|
|||
return out
|
||||
}
|
||||
|
||||
func shortArgs(raw json.RawMessage) string {
|
||||
// ShortArgs renders a tool call's arguments into a one-line
|
||||
// suffix for the "tool name <args>" header. tool is the tool
|
||||
// name so we can add shape-specific decorations: for read we
|
||||
// append the requested line range (e.g. "path:1-200") pulled
|
||||
// from the offset/limit args, which is useful context at a
|
||||
// glance without expanding the result body. Other tools keep
|
||||
// the legacy "path or command, truncated at 60 cells" shape.
|
||||
//
|
||||
// Exported because the interactive mode pre-populates the
|
||||
// ToolCallView.Args field with this value as soon as the tool
|
||||
// call is announced, so the live overlay's header matches what
|
||||
// the finalised transcript will later render.
|
||||
func ShortArgs(tool string, raw json.RawMessage) string {
|
||||
var v any
|
||||
if err := json.Unmarshal(raw, &v); err != nil {
|
||||
return ""
|
||||
}
|
||||
switch x := v.(type) {
|
||||
case map[string]any:
|
||||
for _, k := range []string{"path", "file_path", "command"} {
|
||||
if s, ok := x[k].(string); ok {
|
||||
if len(s) > 60 {
|
||||
s = s[:57] + "..."
|
||||
}
|
||||
return s
|
||||
}
|
||||
x, ok := v.(map[string]any)
|
||||
if !ok {
|
||||
b, _ := json.Marshal(v)
|
||||
s := string(b)
|
||||
if len(s) > 60 {
|
||||
s = s[:57] + "..."
|
||||
}
|
||||
return s
|
||||
}
|
||||
var primary string
|
||||
for _, k := range []string{"path", "file_path", "command"} {
|
||||
if s, ok := x[k].(string); ok {
|
||||
primary = s
|
||||
break
|
||||
}
|
||||
}
|
||||
b, _ := json.Marshal(v)
|
||||
s := string(b)
|
||||
if len(s) > 60 {
|
||||
s = s[:57] + "..."
|
||||
if primary == "" {
|
||||
b, _ := json.Marshal(v)
|
||||
s := string(b)
|
||||
if len(s) > 60 {
|
||||
s = s[:57] + "..."
|
||||
}
|
||||
return s
|
||||
}
|
||||
return s
|
||||
|
||||
// Tool-specific decoration. Only the read tool gets a range
|
||||
// suffix for now; other tools just truncate the primary arg.
|
||||
suffix := ""
|
||||
switch strings.ToLower(tool) {
|
||||
case "read":
|
||||
start := 1
|
||||
if n, ok := toInt(x["offset"]); ok && n >= 1 {
|
||||
start = n
|
||||
}
|
||||
if lim, ok := toInt(x["limit"]); ok && lim > 0 {
|
||||
end := start + lim - 1
|
||||
suffix = fmt.Sprintf(":%d-%d", start, end)
|
||||
} else if start > 1 {
|
||||
suffix = fmt.Sprintf(":%d-", start)
|
||||
}
|
||||
}
|
||||
|
||||
// Truncate the primary arg leaving room for the suffix so the
|
||||
// range stays visible even on absurdly long paths.
|
||||
max := 60 - len(suffix)
|
||||
if max < 10 {
|
||||
max = 10
|
||||
}
|
||||
if len(primary) > max {
|
||||
primary = primary[:max-3] + "..."
|
||||
}
|
||||
return primary + suffix
|
||||
}
|
||||
|
||||
// toInt coerces a json.Unmarshal'd number (float64) or a string
|
||||
// containing a number into an int. Returns ok=false if the value
|
||||
// is neither. Used by shortArgs to survive model quirks where
|
||||
// numeric args come back as strings.
|
||||
func toInt(v any) (int, bool) {
|
||||
switch n := v.(type) {
|
||||
case float64:
|
||||
return int(n), true
|
||||
case int:
|
||||
return n, true
|
||||
case string:
|
||||
i, err := strconv.Atoi(strings.TrimSpace(n))
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return i, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func collectText(blocks []provider.Content) string {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue