@@ -18,10 +18,11 @@ package main
1818
1919import (
2020 "context"
21+ "encoding/json"
2122 "fmt"
2223 "net/http"
2324 "net/url"
24- "path/filepath "
25+ "path"
2526 "strings"
2627
2728 sandboxsdk "github.com/agent-infra/sandbox-sdk-go"
@@ -71,6 +72,14 @@ func NewAIOSandboxBackend(ctx context.Context, config *AIOSandboxBackendConfig)
7172 return nil , fmt .Errorf ("invalid BaseURL: %w" , err )
7273 }
7374
75+ if parsedURL .Scheme == "" || parsedURL .Host == "" {
76+ return nil , fmt .Errorf ("invalid BaseURL: scheme and host are required" )
77+ }
78+
79+ if parsedURL .Scheme != "http" && parsedURL .Scheme != "https" {
80+ return nil , fmt .Errorf ("invalid BaseURL: only http and https schemes are supported" )
81+ }
82+
7483 baseURL := fmt .Sprintf ("%s://%s%s" , parsedURL .Scheme , parsedURL .Host , parsedURL .Path )
7584
7685 opts := []option.RequestOption {
@@ -160,8 +169,22 @@ func escapeShellArg(s string) string {
160169 return strings .ReplaceAll (s , "'" , "'\\ ''" )
161170}
162171
172+ // rgJSONMatch represents the ripgrep JSON output for a match.
173+ type rgJSONMatch struct {
174+ Type string `json:"type"`
175+ Data struct {
176+ Path struct {
177+ Text string `json:"text"`
178+ } `json:"path"`
179+ Lines struct {
180+ Text string `json:"text"`
181+ } `json:"lines"`
182+ LineNumber int `json:"line_number"`
183+ } `json:"data"`
184+ }
185+
163186// GrepRaw searches for content matching the specified pattern in files.
164- // Uses ripgrep (rg) for better performance .
187+ // Uses ripgrep (rg) with JSON output for reliable parsing .
165188func (b * AIOSandboxBackend ) GrepRaw (ctx context.Context , req * filesystem.GrepRequest ) ([]filesystem.GrepMatch , error ) {
166189 if req .Pattern == "" {
167190 return nil , nil
@@ -172,22 +195,19 @@ func (b *AIOSandboxBackend) GrepRaw(ctx context.Context, req *filesystem.GrepReq
172195 searchPath = b .config .WorkDir
173196 }
174197
175- // Build rg command
176- // -n: show line numbers
198+ // Build rg command with JSON output for reliable parsing
177199 // -F: treat pattern as literal string
178- // --no-heading: show file path on each line
179- // --null: use \0 as separator to handle : in filenames
180- // Output format: filename\0linenum\0content
200+ // --json: output in JSON format
181201 pattern := escapeShellArg (req .Pattern )
182202 searchPath = escapeShellArg (searchPath )
183203
184204 var cmd string
185205 if req .Glob != "" {
186206 glob := escapeShellArg (req .Glob )
187- cmd = fmt .Sprintf ("rg -n - F --no-heading --null -g '%s' '%s' '%s' 2>/dev/null || true" ,
207+ cmd = fmt .Sprintf ("rg -F --json -g '%s' '%s' '%s' 2>/dev/null || true" ,
188208 glob , pattern , searchPath )
189209 } else {
190- cmd = fmt .Sprintf ("rg -n - F --no-heading --null '%s' '%s' 2>/dev/null || true" ,
210+ cmd = fmt .Sprintf ("rg -F --json '%s' '%s' 2>/dev/null || true" ,
191211 pattern , searchPath )
192212 }
193213
@@ -204,23 +224,24 @@ func (b *AIOSandboxBackend) GrepRaw(ctx context.Context, req *filesystem.GrepReq
204224 return nil , nil
205225 }
206226
207- // Parse rg --null output: filename\0linenum\0content
227+ // Parse rg --json output: each line is a JSON object
208228 var matches []filesystem.GrepMatch
209229 lines := strings .Split (* data .Output , "\n " )
210230 for _ , line := range lines {
211231 if line == "" {
212232 continue
213233 }
214- parts := strings .SplitN (line , "\x00 " , 3 )
215- if len (parts ) < 3 {
234+ var m rgJSONMatch
235+ if err := json .Unmarshal ([]byte (line ), & m ); err != nil {
236+ continue
237+ }
238+ if m .Type != "match" {
216239 continue
217240 }
218- lineNum := 0
219- fmt .Sscanf (parts [1 ], "%d" , & lineNum )
220241 matches = append (matches , filesystem.GrepMatch {
221- Path : parts [ 0 ] ,
222- Line : lineNum ,
223- Content : parts [ 2 ] ,
242+ Path : m . Data . Path . Text ,
243+ Line : m . Data . LineNumber ,
244+ Content : strings . TrimSuffix ( m . Data . Lines . Text , " \n " ) ,
224245 })
225246 }
226247
@@ -319,11 +340,11 @@ func (b *AIOSandboxBackend) Edit(ctx context.Context, req *filesystem.EditReques
319340 return nil
320341}
321342
322- func (b * AIOSandboxBackend ) resolvePath (path string ) string {
323- if filepath .IsAbs (path ) {
324- return path
343+ func (b * AIOSandboxBackend ) resolvePath (p string ) string {
344+ if path .IsAbs (p ) {
345+ return p
325346 }
326- return filepath .Join (b .config .WorkDir , path )
347+ return path .Join (b .config .WorkDir , p )
327348}
328349
329350// Execute runs a shell command in the sandbox.
0 commit comments