parseReadArgs converts the LLM-supplied JSON envelope into a typed readArgs. Accepts: - JSON in the first arg: `{"file":"x","from_line":10}` (preferred). - Positional + flag mix: `--file x --from_line 10` (compat with the CLI invocation path so /read at the user prompt still works). Returns a zero
(args []string)
| 164 | // callers can fail with a clear "file required" message rather than a |
| 165 | // parse error. |
| 166 | func parseReadArgs(args []string) (readArgs, error) { |
| 167 | var out readArgs |
| 168 | if len(args) == 0 { |
| 169 | return out, nil |
| 170 | } |
| 171 | first := strings.TrimSpace(args[0]) |
| 172 | if strings.HasPrefix(first, "{") { |
| 173 | var raw map[string]json.RawMessage |
| 174 | if err := json.Unmarshal([]byte(first), &raw); err != nil { |
| 175 | return out, fmt.Errorf("@read: malformed JSON args: %w", err) |
| 176 | } |
| 177 | // Support both flat (`{"file":...}`) and nested @coder envelope |
| 178 | // (`{"cmd":"read","args":{...}}`) shapes. The nested case lets |
| 179 | // us reuse a single parser for legacy @coder read invocations. |
| 180 | if inner, ok := raw["args"]; ok { |
| 181 | var innerMap map[string]json.RawMessage |
| 182 | if jsonErr := json.Unmarshal(inner, &innerMap); jsonErr == nil { |
| 183 | raw = innerMap |
| 184 | } |
| 185 | } |
| 186 | out.File = jsonString(raw, "file", "path", "filepath") |
| 187 | out.FromLine = jsonInt(raw, "from_line", "start", "fromLine") |
| 188 | out.ToLine = jsonInt(raw, "to_line", "end", "toLine") |
| 189 | out.Head = jsonInt(raw, "head") |
| 190 | out.Tail = jsonInt(raw, "tail") |
| 191 | out.MaxBytes = jsonInt(raw, "max_bytes", "maxBytes") |
| 192 | out.Encoding = jsonString(raw, "encoding") |
| 193 | return out, nil |
| 194 | } |
| 195 | // Positional / flag form. Scan pairs. |
| 196 | for i := 0; i < len(args); i++ { |
| 197 | switch args[i] { |
| 198 | case "--file": |
| 199 | if i+1 < len(args) { |
| 200 | out.File = args[i+1] |
| 201 | i++ |
| 202 | } |
| 203 | case "--from_line", "--start": |
| 204 | if i+1 < len(args) { |
| 205 | out.FromLine, _ = strconv.Atoi(args[i+1]) |
| 206 | i++ |
| 207 | } |
| 208 | case "--to_line", "--end": |
| 209 | if i+1 < len(args) { |
| 210 | out.ToLine, _ = strconv.Atoi(args[i+1]) |
| 211 | i++ |
| 212 | } |
| 213 | case "--head": |
| 214 | if i+1 < len(args) { |
| 215 | out.Head, _ = strconv.Atoi(args[i+1]) |
| 216 | i++ |
| 217 | } |
| 218 | case "--tail": |
| 219 | if i+1 < len(args) { |
| 220 | out.Tail, _ = strconv.Atoi(args[i+1]) |
| 221 | i++ |
| 222 | } |
| 223 | case "--max_bytes", "--max-bytes": |