@@ -27,10 +27,6 @@ type agenticFetchValidationResult struct {
28// validateAgenticFetchParams validates the tool call parameters and extracts required context values.
28// validateAgenticFetchParams validates the tool call parameters and extracts required context values.
29func validateAgenticFetchParams(ctx context.Context, params tools.AgenticFetchParams) (agenticFetchValidationResult, error) {
29func validateAgenticFetchParams(ctx context.Context, params tools.AgenticFetchParams) (agenticFetchValidationResult, error) {
30− if params.URL == "" {
31− return agenticFetchValidationResult{}, errors.New("url is required")
34 if params.Prompt == "" {
30 if params.Prompt == "" {
35 return agenticFetchValidationResult{}, errors.New("prompt is required")
31 return agenticFetchValidationResult{}, errors.New("prompt is required")
@@ -75,6 +71,14 @@ func (c *coordinator) agenticFetchTool(_ context.Context, client *http.Client) (
75 return fantasy.NewTextErrorResponse(err.Error()), nil
71 return fantasy.NewTextErrorResponse(err.Error()), nil
74+ // Determine description based on mode.
75+ var description string
76+ if params.URL != "" {
77+ description = fmt.Sprintf("Fetch and analyze content from URL: %s", params.URL)
79+ description = "Search the web and analyze results"
78 p := c.permissions.Request(
82 p := c.permissions.Request(
79 permission.CreatePermissionRequest{
83 permission.CreatePermissionRequest{
80 SessionID: validationResult.SessionID,
84 SessionID: validationResult.SessionID,
@@ -82,7 +86,7 @@ func (c *coordinator) agenticFetchTool(_ context.Context, client *http.Client) (
82 ToolCallID: call.ID,
86 ToolCallID: call.ID,
83 ToolName: tools.AgenticFetchToolName,
87 ToolName: tools.AgenticFetchToolName,
84 Action: "fetch",
88 Action: "fetch",
85− Description: fmt.Sprintf("Fetch and analyze content from URL: %s", params.URL),
89+ Description: description,
86 Params: tools.AgenticFetchPermissionsParams(params),
90 Params: tools.AgenticFetchPermissionsParams(params),
@@ -91,36 +95,43 @@ func (c *coordinator) agenticFetchTool(_ context.Context, client *http.Client) (
91 return fantasy.ToolResponse{}, permission.ErrorPermissionDenied
95 return fantasy.ToolResponse{}, permission.ErrorPermissionDenied
94− content, err := tools.FetchURLAndConvert(ctx, client, params.URL)
96− return fantasy.NewTextErrorResponse(fmt.Sprintf("Failed to fetch URL: %s", err)), nil
99 tmpDir, err := os.MkdirTemp(c.cfg.Options.DataDirectory, "crush-fetch-*")
98 tmpDir, err := os.MkdirTemp(c.cfg.Options.DataDirectory, "crush-fetch-*")
100 if err != nil {
99 if err != nil {
101 return fantasy.NewTextErrorResponse(fmt.Sprintf("Failed to create temporary directory: %s", err)), nil
100 return fantasy.NewTextErrorResponse(fmt.Sprintf("Failed to create temporary directory: %s", err)), nil
103 defer os.RemoveAll(tmpDir)
102 defer os.RemoveAll(tmpDir)
105− hasLargeContent := len(content) > tools.LargeContentThreshold
106 var fullPrompt string
104 var fullPrompt string
108− if hasLargeContent {
106+ if params.URL != "" {
109− tempFile, err := os.CreateTemp(tmpDir, "page-*.md")
107+ // URL mode: fetch the URL content first.
108+ content, err := tools.FetchURLAndConvert(ctx, client, params.URL)
110 if err != nil {
109 if err != nil {
111− return fantasy.NewTextErrorResponse(fmt.Sprintf("Failed to create temporary file: %s", err)), nil
110+ return fantasy.NewTextErrorResponse(fmt.Sprintf("Failed to fetch URL: %s", err)), nil
113− tempFilePath := tempFile.Name()
113+ hasLargeContent := len(content) > tools.LargeContentThreshold
115+ if hasLargeContent {
116+ tempFile, err := os.CreateTemp(tmpDir, "page-*.md")
118+ return fantasy.NewTextErrorResponse(fmt.Sprintf("Failed to create temporary file: %s", err)), nil
120+ tempFilePath := tempFile.Name()
115− if _, err := tempFile.WriteString(content); err != nil {
122+ if _, err := tempFile.WriteString(content); err != nil {
124+ return fantasy.NewTextErrorResponse(fmt.Sprintf("Failed to write content to file: %s", err)), nil
116 tempFile.Close()
126 tempFile.Close()
117− return fantasy.NewTextErrorResponse(fmt.Sprintf("Failed to write content to file: %s", err)), nil
121− fullPrompt = fmt.Sprintf("%s\n\nThe web page from %s has been saved to: %s\n\nUse the view and grep tools to analyze this file and extract the requested information.", params.Prompt, params.URL, tempFilePath)
128+ fullPrompt = fmt.Sprintf("%s\n\nThe web page from %s has been saved to: %s\n\nUse the view and grep tools to analyze this file and extract the requested information.", params.Prompt, params.URL, tempFilePath)
130+ fullPrompt = fmt.Sprintf("%s\n\nWeb page URL: %s\n\n<webpage_content>\n%s\n</webpage_content>", params.Prompt, params.URL, content)
123− fullPrompt = fmt.Sprintf("%s\n\nWeb page URL: %s\n\n<webpage_content>\n%s\n</webpage_content>", params.Prompt, params.URL, content)
133+ // Search mode: let the sub-agent search and fetch as needed.
134+ fullPrompt = fmt.Sprintf("%s\n\nUse the web_search tool to find relevant information. Break down the question into smaller, focused searches if needed. After searching, use web_fetch to get detailed content from the most relevant results.", params.Prompt)
126 promptOpts := []prompt.Option{
137 promptOpts := []prompt.Option{
@@ -148,10 +159,13 @@ func (c *coordinator) agenticFetchTool(_ context.Context, client *http.Client) (
150 webFetchTool := tools.NewWebFetchTool(tmpDir, client)
161 webFetchTool := tools.NewWebFetchTool(tmpDir, client)
162+ webSearchTool := tools.NewWebSearchTool(client)
151 fetchTools := []fantasy.AgentTool{
163 fetchTools := []fantasy.AgentTool{
152 webFetchTool,
164 webFetchTool,
153 tools.NewGlobTool(tmpDir),
166 tools.NewGlobTool(tmpDir),
154 tools.NewGrepTool(tmpDir),
167 tools.NewGrepTool(tmpDir),
168+ tools.NewSourcegraphTool(client),
155 tools.NewViewTool(c.lspClients, c.permissions, tmpDir),
169 tools.NewViewTool(c.lspClients, c.permissions, tmpDir),