main 4509ec7

tool_args.go RawHistoryFolder

1package provider
2
3import (
4	"encoding/json"
5	"strings"
6)
7
8// normalizeToolArgs turns streamed tool-call arguments into valid JSON.
9// Models sometimes emit raw control characters (tabs, newlines) inside JSON
10// strings; those are escaped so the call keeps its intended arguments.
11// Empty or otherwise unrepairable input becomes "{}" so the message can
12// always be serialized to transcripts and sent back to the provider.
13func normalizeToolArgs(raw string) json.RawMessage {
14	if raw == "" {
15		return json.RawMessage("{}")
16	}
17	if json.Valid([]byte(raw)) {
18		return json.RawMessage(raw)
19	}
20	if repaired := escapeControlCharsInStrings(raw); json.Valid([]byte(repaired)) {
21		return json.RawMessage(repaired)
22	}
23	return json.RawMessage("{}")
24}
25
26// escapeControlCharsInStrings escapes raw U+0000–U+001F bytes that appear
27// inside JSON string literals. Bytes outside strings are left untouched.
28func escapeControlCharsInStrings(raw string) string {
29	const hex = "0123456789abcdef"
30	var b strings.Builder
31	b.Grow(len(raw) + 8)
32	inString, escaped := false, false
33	for i := 0; i < len(raw); i++ {
34		c := raw[i]
35		if !inString {
36			if c == '"' {
37				inString = true
38			}
39			b.WriteByte(c)
40			continue
41		}
42		switch {
43		case escaped:
44			escaped = false
45			b.WriteByte(c)
46		case c == '\\':
47			escaped = true
48			b.WriteByte(c)
49		case c == '"':
50			inString = false
51			b.WriteByte(c)
52		case c == '\t':
53			b.WriteString(`\t`)
54		case c == '\n':
55			b.WriteString(`\n`)
56		case c == '\r':
57			b.WriteString(`\r`)
58		case c < 0x20:
59			b.WriteString(`\u00`)
60			b.WriteByte(hex[c>>4])
61			b.WriteByte(hex[c&0xf])
62		default:
63			b.WriteByte(c)
64		}
65	}
66	return b.String()
67}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close