Browser tools: initialize lazily and add timeouts.

Also rename browser_screenshot to browser_take_screenshot for clarity\n- Update both Go and UI code to maintain consistency

Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: s8a5cabff914f88dfk
diff --git a/claudetool/browse/browse.go b/claudetool/browse/browse.go
index 60e66ac..a5a1621 100644
--- a/claudetool/browse/browse.go
+++ b/claudetool/browse/browse.go
@@ -124,10 +124,9 @@
 
 // NavigateTool definition
 type navigateInput struct {
-	URL string `json:"url"`
-}
-
-// NewNavigateTool creates a tool for navigating to URLs
+	URL     string `json:"url"`
+	Timeout string `json:"timeout,omitempty"`
+} // NewNavigateTool creates a tool for navigating to URLs
 func (b *BrowseTools) NewNavigateTool() *llm.Tool {
 	return &llm.Tool{
 		Name:        "browser_navigate",
@@ -138,6 +137,10 @@
 				"url": {
 					"type": "string",
 					"description": "The URL to navigate to"
+				},
+				"timeout": {
+					"type": "string",
+					"description": "Timeout as a Go duration string (default: 5s)"
 				}
 			},
 			"required": ["url"]
@@ -157,7 +160,11 @@
 		return llm.TextContent(errorResponse(err)), nil
 	}
 
-	err = chromedp.Run(browserCtx,
+	// Create a timeout context for this operation
+	timeoutCtx, cancel := context.WithTimeout(browserCtx, parseTimeout(input.Timeout))
+	defer cancel()
+
+	err = chromedp.Run(timeoutCtx,
 		chromedp.Navigate(input.URL),
 		chromedp.WaitReady("body"),
 	)
@@ -172,6 +179,7 @@
 type clickInput struct {
 	Selector    string `json:"selector"`
 	WaitVisible bool   `json:"wait_visible,omitempty"`
+	Timeout     string `json:"timeout,omitempty"`
 }
 
 // NewClickTool creates a tool for clicking elements
@@ -189,6 +197,10 @@
 				"wait_visible": {
 					"type": "boolean",
 					"description": "Wait for the element to be visible before clicking"
+				},
+				"timeout": {
+					"type": "string",
+					"description": "Timeout as a Go duration string (default: 5s)"
 				}
 			},
 			"required": ["selector"]
@@ -208,6 +220,10 @@
 		return llm.TextContent(errorResponse(err)), nil
 	}
 
+	// Create a timeout context for this operation
+	timeoutCtx, cancel := context.WithTimeout(browserCtx, parseTimeout(input.Timeout))
+	defer cancel()
+
 	actions := []chromedp.Action{
 		chromedp.WaitReady(input.Selector),
 	}
@@ -218,7 +234,7 @@
 
 	actions = append(actions, chromedp.Click(input.Selector))
 
-	err = chromedp.Run(browserCtx, actions...)
+	err = chromedp.Run(timeoutCtx, actions...)
 	if err != nil {
 		return llm.TextContent(errorResponse(err)), nil
 	}
@@ -231,6 +247,7 @@
 	Selector string `json:"selector"`
 	Text     string `json:"text"`
 	Clear    bool   `json:"clear,omitempty"`
+	Timeout  string `json:"timeout,omitempty"`
 }
 
 // NewTypeTool creates a tool for typing into input elements
@@ -252,6 +269,10 @@
 				"clear": {
 					"type": "boolean",
 					"description": "Clear the input field before typing"
+				},
+				"timeout": {
+					"type": "string",
+					"description": "Timeout as a Go duration string (default: 5s)"
 				}
 			},
 			"required": ["selector", "text"]
@@ -271,6 +292,10 @@
 		return llm.TextContent(errorResponse(err)), nil
 	}
 
+	// Create a timeout context for this operation
+	timeoutCtx, cancel := context.WithTimeout(browserCtx, parseTimeout(input.Timeout))
+	defer cancel()
+
 	actions := []chromedp.Action{
 		chromedp.WaitReady(input.Selector),
 		chromedp.WaitVisible(input.Selector),
@@ -282,7 +307,7 @@
 
 	actions = append(actions, chromedp.SendKeys(input.Selector, input.Text))
 
-	err = chromedp.Run(browserCtx, actions...)
+	err = chromedp.Run(timeoutCtx, actions...)
 	if err != nil {
 		return llm.TextContent(errorResponse(err)), nil
 	}
@@ -292,8 +317,8 @@
 
 // WaitForTool definition
 type waitForInput struct {
-	Selector  string `json:"selector"`
-	TimeoutMS int    `json:"timeout_ms,omitempty"`
+	Selector string `json:"selector"`
+	Timeout  string `json:"timeout,omitempty"`
 }
 
 // NewWaitForTool creates a tool for waiting for elements
@@ -308,9 +333,9 @@
 					"type": "string",
 					"description": "CSS selector for the element to wait for"
 				},
-				"timeout_ms": {
-					"type": "integer",
-					"description": "Maximum time to wait in milliseconds (default: 30000)"
+				"timeout": {
+					"type": "string",
+					"description": "Timeout as a Go duration string (default: 5s)"
 				}
 			},
 			"required": ["selector"]
@@ -325,17 +350,12 @@
 		return llm.TextContent(errorResponse(fmt.Errorf("invalid input: %w", err))), nil
 	}
 
-	timeout := 30000 // default timeout 30 seconds
-	if input.TimeoutMS > 0 {
-		timeout = input.TimeoutMS
-	}
-
 	browserCtx, err := b.GetBrowserContext()
 	if err != nil {
 		return llm.TextContent(errorResponse(err)), nil
 	}
 
-	timeoutCtx, cancel := context.WithTimeout(browserCtx, time.Duration(timeout)*time.Millisecond)
+	timeoutCtx, cancel := context.WithTimeout(browserCtx, parseTimeout(input.Timeout))
 	defer cancel()
 
 	err = chromedp.Run(timeoutCtx, chromedp.WaitReady(input.Selector))
@@ -349,6 +369,7 @@
 // GetTextTool definition
 type getTextInput struct {
 	Selector string `json:"selector"`
+	Timeout  string `json:"timeout,omitempty"`
 }
 
 type getTextOutput struct {
@@ -359,13 +380,17 @@
 func (b *BrowseTools) NewGetTextTool() *llm.Tool {
 	return &llm.Tool{
 		Name:        "browser_get_text",
-		Description: "Get the innerText of an element",
+		Description: "Get the innerText of an element. Can be used to read the web page.",
 		InputSchema: json.RawMessage(`{
 			"type": "object",
 			"properties": {
 				"selector": {
 					"type": "string",
 					"description": "CSS selector for the element to get text from"
+				},
+				"timeout": {
+					"type": "string",
+					"description": "Timeout as a Go duration string (default: 5s)"
 				}
 			},
 			"required": ["selector"]
@@ -385,8 +410,12 @@
 		return llm.TextContent(errorResponse(err)), nil
 	}
 
+	// Create a timeout context for this operation
+	timeoutCtx, cancel := context.WithTimeout(browserCtx, parseTimeout(input.Timeout))
+	defer cancel()
+
 	var text string
-	err = chromedp.Run(browserCtx,
+	err = chromedp.Run(timeoutCtx,
 		chromedp.WaitReady(input.Selector),
 		chromedp.Text(input.Selector, &text),
 	)
@@ -406,6 +435,7 @@
 // EvalTool definition
 type evalInput struct {
 	Expression string `json:"expression"`
+	Timeout    string `json:"timeout,omitempty"`
 }
 
 type evalOutput struct {
@@ -423,6 +453,10 @@
 				"expression": {
 					"type": "string",
 					"description": "JavaScript expression to evaluate"
+				},
+				"timeout": {
+					"type": "string",
+					"description": "Timeout as a Go duration string (default: 5s)"
 				}
 			},
 			"required": ["expression"]
@@ -442,8 +476,12 @@
 		return llm.TextContent(errorResponse(err)), nil
 	}
 
+	// Create a timeout context for this operation
+	timeoutCtx, cancel := context.WithTimeout(browserCtx, parseTimeout(input.Timeout))
+	defer cancel()
+
 	var result any
-	err = chromedp.Run(browserCtx, chromedp.Evaluate(input.Expression, &result))
+	err = chromedp.Run(timeoutCtx, chromedp.Evaluate(input.Expression, &result))
 	if err != nil {
 		return llm.TextContent(errorResponse(err)), nil
 	}
@@ -461,6 +499,7 @@
 type screenshotInput struct {
 	Selector string `json:"selector,omitempty"`
 	Format   string `json:"format,omitempty"`
+	Timeout  string `json:"timeout,omitempty"`
 }
 
 type screenshotOutput struct {
@@ -470,7 +509,7 @@
 // NewScreenshotTool creates a tool for taking screenshots
 func (b *BrowseTools) NewScreenshotTool() *llm.Tool {
 	return &llm.Tool{
-		Name:        "browser_screenshot",
+		Name:        "browser_take_screenshot",
 		Description: "Take a screenshot of the page or a specific element",
 		InputSchema: json.RawMessage(`{
 			"type": "object",
@@ -483,6 +522,10 @@
 					"type": "string",
 					"description": "Output format ('base64' or 'png'), defaults to 'base64'",
 					"enum": ["base64", "png"]
+				},
+				"timeout": {
+					"type": "string",
+					"description": "Timeout as a Go duration string (default: 5s)"
 				}
 			}
 		}`),
@@ -501,6 +544,10 @@
 		return llm.TextContent(errorResponse(err)), nil
 	}
 
+	// Create a timeout context for this operation
+	timeoutCtx, cancel := context.WithTimeout(browserCtx, parseTimeout(input.Timeout))
+	defer cancel()
+
 	var buf []byte
 	var actions []chromedp.Action
 
@@ -515,7 +562,7 @@
 		actions = append(actions, chromedp.CaptureScreenshot(&buf))
 	}
 
-	err = chromedp.Run(browserCtx, actions...)
+	err = chromedp.Run(timeoutCtx, actions...)
 	if err != nil {
 		return llm.TextContent(errorResponse(err)), nil
 	}
@@ -542,6 +589,7 @@
 // ScrollIntoViewTool definition
 type scrollIntoViewInput struct {
 	Selector string `json:"selector"`
+	Timeout  string `json:"timeout,omitempty"`
 }
 
 // NewScrollIntoViewTool creates a tool for scrolling elements into view
@@ -555,6 +603,10 @@
 				"selector": {
 					"type": "string",
 					"description": "CSS selector for the element to scroll into view"
+				},
+				"timeout": {
+					"type": "string",
+					"description": "Timeout as a Go duration string (default: 5s)"
 				}
 			},
 			"required": ["selector"]
@@ -574,6 +626,10 @@
 		return llm.TextContent(errorResponse(err)), nil
 	}
 
+	// Create a timeout context for this operation
+	timeoutCtx, cancel := context.WithTimeout(browserCtx, parseTimeout(input.Timeout))
+	defer cancel()
+
 	script := fmt.Sprintf(`
 		const el = document.querySelector('%s');
 		if (el) {
@@ -584,7 +640,7 @@
 	`, input.Selector)
 
 	var result bool
-	err = chromedp.Run(browserCtx,
+	err = chromedp.Run(timeoutCtx,
 		chromedp.WaitReady(input.Selector),
 		chromedp.Evaluate(script, &result),
 	)
@@ -647,7 +703,8 @@
 
 // ReadImageTool definition
 type readImageInput struct {
-	Path string `json:"path"`
+	Path    string `json:"path"`
+	Timeout string `json:"timeout,omitempty"`
 }
 
 // NewReadImageTool creates a tool for reading images and returning them as base64 encoded data
@@ -661,6 +718,10 @@
 				"path": {
 					"type": "string",
 					"description": "Path to the image file to read"
+				},
+				"timeout": {
+					"type": "string",
+					"description": "Timeout as a Go duration string (default: 5s)"
 				}
 			},
 			"required": ["path"]
@@ -708,3 +769,19 @@
 		},
 	}, nil
 }
+
+// parseTimeout parses a timeout string and returns a time.Duration
+// It returns a default of 5 seconds if the timeout is empty or invalid
+func parseTimeout(timeout string) time.Duration {
+	if timeout == "" {
+		return 5 * time.Second // default 5 seconds
+	}
+
+	dur, err := time.ParseDuration(timeout)
+	if err != nil {
+		// If parsing fails, return the default
+		return 5 * time.Second
+	}
+
+	return dur
+}
diff --git a/claudetool/browse/browse_test.go b/claudetool/browse/browse_test.go
index f1360d8..ae4fefd 100644
--- a/claudetool/browse/browse_test.go
+++ b/claudetool/browse/browse_test.go
@@ -32,7 +32,7 @@
 		{tools.NewWaitForTool(), "browser_wait_for", "Wait", []string{"selector"}},
 		{tools.NewGetTextTool(), "browser_get_text", "Get", []string{"selector"}},
 		{tools.NewEvalTool(), "browser_eval", "Evaluate", []string{"expression"}},
-		{tools.NewScreenshotTool(), "browser_screenshot", "Take", nil},
+		{tools.NewScreenshotTool(), "browser_take_screenshot", "Take", nil},
 		{tools.NewScrollIntoViewTool(), "browser_scroll_into_view", "Scroll", []string{"selector"}},
 	}
 
@@ -101,7 +101,7 @@
 	}
 
 	// Create browser tools instance
-	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 	defer cancel()
 
 	tools := NewBrowseTools(ctx)
@@ -147,7 +147,7 @@
 	}
 
 	// Create browser tools instance
-	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 	defer cancel()
 
 	tools := NewBrowseTools(ctx)
diff --git a/claudetool/browse/register.go b/claudetool/browse/register.go
index 183bf14..0e60d73 100644
--- a/claudetool/browse/register.go
+++ b/claudetool/browse/register.go
@@ -2,22 +2,16 @@
 
 import (
 	"context"
-	"log"
 
 	"sketch.dev/llm"
 )
 
-// RegisterBrowserTools initializes the browser tools and returns all the tools
-// ready to be added to an agent. It also returns a cleanup function that should
-// be called when done to properly close the browser.
+// RegisterBrowserTools returns all browser tools ready to be added to an agent.
+// It also returns a cleanup function that should be called when done to properly close the browser.
+// The browser will be initialized lazily when a browser tool is first used.
 func RegisterBrowserTools(ctx context.Context, supportsScreenshots bool) ([]*llm.Tool, func()) {
 	browserTools := NewBrowseTools(ctx)
 
-	// Initialize the browser
-	if err := browserTools.Initialize(); err != nil {
-		log.Printf("Warning: Failed to initialize browser: %v", err)
-	}
-
 	return browserTools.GetTools(supportsScreenshots), func() {
 		browserTools.Close()
 	}
diff --git a/loop/agent.go b/loop/agent.go
index 2c8eec9..95a32bb 100644
--- a/loop/agent.go
+++ b/loop/agent.go
@@ -904,18 +904,17 @@
 	// template in termui/termui.go has pretty-printing support for all tools.
 
 	var browserTools []*llm.Tool
-	// Add browser tools if enabled
-	// if experiment.Enabled("browser") {
-	if true {
-		_, supportsScreenshots := a.config.Service.(*ant.Service)
-		bTools, browserCleanup := browse.RegisterBrowserTools(a.config.Context, supportsScreenshots)
-		// Add cleanup function to context cancel
-		go func() {
-			<-a.config.Context.Done()
-			browserCleanup()
-		}()
-		browserTools = bTools
-	}
+	_, supportsScreenshots := a.config.Service.(*ant.Service)
+	var bTools []*llm.Tool
+	var browserCleanup func()
+
+	bTools, browserCleanup = browse.RegisterBrowserTools(a.config.Context, supportsScreenshots)
+	// Add cleanup function to context cancel
+	go func() {
+		<-a.config.Context.Done()
+		browserCleanup()
+	}()
+	browserTools = bTools
 
 	convo.Tools = []*llm.Tool{
 		bashTool, claudetool.Keyword,
diff --git a/loop/testdata/agent_loop.httprr b/loop/testdata/agent_loop.httprr
index 512e5d1..fe631a1 100644
--- a/loop/testdata/agent_loop.httprr
+++ b/loop/testdata/agent_loop.httprr
@@ -1,9 +1,9 @@
 httprr trace v1
-14628 2230
+15608 2329
 POST https://api.anthropic.com/v1/messages HTTP/1.1

 Host: api.anthropic.com

 User-Agent: Go-http-client/1.1

-Content-Length: 14430

+Content-Length: 15410

 Anthropic-Version: 2023-06-01

 Content-Type: application/json

 

@@ -245,6 +245,10 @@
      "url": {
       "type": "string",
       "description": "The URL to navigate to"
+     },
+     "timeout": {
+      "type": "string",
+      "description": "Timeout as a Go duration string (default: 5s)"
      }
     },
     "required": [
@@ -265,6 +269,10 @@
      "wait_visible": {
       "type": "boolean",
       "description": "Wait for the element to be visible before clicking"
+     },
+     "timeout": {
+      "type": "string",
+      "description": "Timeout as a Go duration string (default: 5s)"
      }
     },
     "required": [
@@ -289,6 +297,10 @@
      "clear": {
       "type": "boolean",
       "description": "Clear the input field before typing"
+     },
+     "timeout": {
+      "type": "string",
+      "description": "Timeout as a Go duration string (default: 5s)"
      }
     },
     "required": [
@@ -307,9 +319,9 @@
       "type": "string",
       "description": "CSS selector for the element to wait for"
      },
-     "timeout_ms": {
-      "type": "integer",
-      "description": "Maximum time to wait in milliseconds (default: 30000)"
+     "timeout": {
+      "type": "string",
+      "description": "Timeout as a Go duration string (default: 5s)"
      }
     },
     "required": [
@@ -319,13 +331,17 @@
   },
   {
    "name": "browser_get_text",
-   "description": "Get the innerText of an element",
+   "description": "Get the innerText of an element. Can be used to read the web page.",
    "input_schema": {
     "type": "object",
     "properties": {
      "selector": {
       "type": "string",
       "description": "CSS selector for the element to get text from"
+     },
+     "timeout": {
+      "type": "string",
+      "description": "Timeout as a Go duration string (default: 5s)"
      }
     },
     "required": [
@@ -342,6 +358,10 @@
      "expression": {
       "type": "string",
       "description": "JavaScript expression to evaluate"
+     },
+     "timeout": {
+      "type": "string",
+      "description": "Timeout as a Go duration string (default: 5s)"
      }
     },
     "required": [
@@ -358,6 +378,10 @@
      "selector": {
       "type": "string",
       "description": "CSS selector for the element to scroll into view"
+     },
+     "timeout": {
+      "type": "string",
+      "description": "Timeout as a Go duration string (default: 5s)"
      }
     },
     "required": [
@@ -366,7 +390,7 @@
    }
   },
   {
-   "name": "browser_screenshot",
+   "name": "browser_take_screenshot",
    "description": "Take a screenshot of the page or a specific element",
    "input_schema": {
     "type": "object",
@@ -382,6 +406,10 @@
        "base64",
        "png"
       ]
+     },
+     "timeout": {
+      "type": "string",
+      "description": "Timeout as a Go duration string (default: 5s)"
      }
     }
    }
@@ -395,6 +423,10 @@
      "path": {
       "type": "string",
       "description": "Path to the image file to read"
+     },
+     "timeout": {
+      "type": "string",
+      "description": "Timeout as a Go duration string (default: 5s)"
      }
     },
     "required": [
@@ -463,25 +495,25 @@
 }HTTP/2.0 200 OK

 Anthropic-Organization-Id: 3c473a21-7208-450a-a9f8-80aebda45c1b

 Anthropic-Ratelimit-Input-Tokens-Limit: 200000

-Anthropic-Ratelimit-Input-Tokens-Remaining: 200000

-Anthropic-Ratelimit-Input-Tokens-Reset: 2025-05-10T13:34:48Z

+Anthropic-Ratelimit-Input-Tokens-Remaining: 199000

+Anthropic-Ratelimit-Input-Tokens-Reset: 2025-05-11T01:21:43Z

 Anthropic-Ratelimit-Output-Tokens-Limit: 80000

 Anthropic-Ratelimit-Output-Tokens-Remaining: 80000

-Anthropic-Ratelimit-Output-Tokens-Reset: 2025-05-10T13:34:52Z

+Anthropic-Ratelimit-Output-Tokens-Reset: 2025-05-11T01:21:47Z

 Anthropic-Ratelimit-Requests-Limit: 4000

 Anthropic-Ratelimit-Requests-Remaining: 3999

-Anthropic-Ratelimit-Requests-Reset: 2025-05-10T13:34:47Z

+Anthropic-Ratelimit-Requests-Reset: 2025-05-11T01:21:42Z

 Anthropic-Ratelimit-Tokens-Limit: 280000

-Anthropic-Ratelimit-Tokens-Remaining: 280000

-Anthropic-Ratelimit-Tokens-Reset: 2025-05-10T13:34:48Z

+Anthropic-Ratelimit-Tokens-Remaining: 279000

+Anthropic-Ratelimit-Tokens-Reset: 2025-05-11T01:21:43Z

 Cf-Cache-Status: DYNAMIC

-Cf-Ray: 93d9d0e80caa67fe-SJC

+Cf-Ray: 93dddc70ba53942c-SJC

 Content-Type: application/json

-Date: Sat, 10 May 2025 13:34:52 GMT

-Request-Id: req_011CNywHj3Qrj3hEz9qFRVgK

+Date: Sun, 11 May 2025 01:21:47 GMT

+Request-Id: req_011CNzsCMP4tG7GTL8PrNTDw

 Server: cloudflare

 Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

 Via: 1.1 google

 X-Robots-Tag: none

 

-{"id":"msg_014KGRNEmFdTUGqDQN7sRmzc","type":"message","role":"assistant","model":"claude-3-7-sonnet-20250219","content":[{"type":"text","text":"Here are the tools available to me:\n\n1. bash - Executes shell commands\n2. keyword_search - Searches files with given keywords\n3. think - For recording thoughts, notes, and plans\n4. title - Sets conversation title\n5. precommit - Creates git branches and provides commit message guidance\n6. done - Marks task completion with a checklist\n7. codereview - Runs automated code review\n8. multiplechoice - Presents multiple choice options to the user\n9. Browser tools:\n   - browser_navigate - Opens a URL\n   - browser_click - Clicks elements\n   - browser_type - Types text into elements\n   - browser_wait_for - Waits for elements\n   - browser_get_text - Gets text from elements\n   - browser_eval - Runs JavaScript in browser\n   - browser_scroll_into_view - Scrolls to elements\n   - browser_screenshot - Takes screenshots\n   - browser_read_image - Reads image files\n10. patch - Makes precise text modifications to files"}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":4,"cache_creation_input_tokens":3376,"cache_read_input_tokens":0,"output_tokens":236}}
\ No newline at end of file
+{"id":"msg_0152wEWVypxw8wDR31nYSXQd","type":"message","role":"assistant","model":"claude-3-7-sonnet-20250219","content":[{"type":"text","text":"Here are the tools available to me:\n\n1. bash - Execute shell commands\n2. keyword_search - Search for files based on keywords\n3. think - Record thoughts and plans\n4. title - Set conversation title\n5. precommit - Create a git branch for work\n6. done - Mark a task as complete with checklist\n7. codereview - Run automated code review\n8. multiplechoice - Present multiple choice options to user\n9. Browser tools:\n   - browser_navigate - Navigate to URLs\n   - browser_click - Click elements\n   - browser_type - Type into elements\n   - browser_wait_for - Wait for elements\n   - browser_get_text - Get text from elements\n   - browser_eval - Evaluate JavaScript\n   - browser_scroll_into_view - Scroll elements into view\n   - browser_take_screenshot - Take screenshots\n   - browser_read_image - Read image files\n10. patch - Make precise text edits to files\n\nThese tools allow me to execute commands, search codebases, plan, manage git branches, evaluate code, interact with web browsers, and make file modifications."}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":4,"cache_creation_input_tokens":3609,"cache_read_input_tokens":0,"output_tokens":254}}
\ No newline at end of file
diff --git a/webui/src/web-components/sketch-tool-calls.ts b/webui/src/web-components/sketch-tool-calls.ts
index ae63de0..3245a45 100644
--- a/webui/src/web-components/sketch-tool-calls.ts
+++ b/webui/src/web-components/sketch-tool-calls.ts
@@ -3,7 +3,7 @@
 import { repeat } from "lit/directives/repeat.js";
 import { ToolCall } from "../types";
 import "./sketch-tool-card";
-import "./sketch-tool-card-screenshot";
+import "./sketch-tool-card-take-screenshot";
 
 @customElement("sketch-tool-calls")
 export class SketchToolCalls extends LitElement {
@@ -116,11 +116,11 @@
           .open=${open}
           .toolCall=${toolCall}
         ></sketch-tool-card-precommit>`;
-      case "browser_screenshot":
-        return html`<sketch-tool-card-screenshot
+      case "browser_take_screenshot":
+        return html`<sketch-tool-card-take-screenshot
           .open=${open}
           .toolCall=${toolCall}
-        ></sketch-tool-card-screenshot>`;
+        ></sketch-tool-card-take-screenshot>`;
     }
     return html`<sketch-tool-card-generic
       .open=${open}
@@ -147,7 +147,7 @@
           let shouldOpen = false;
           // Always expand screenshot tool calls, expand last tool call if this.open is true
           if (
-            toolCall.name === "browser_screenshot" ||
+            toolCall.name === "browser_take_screenshot" ||
             (idx == this.toolCalls?.length - 1 && this.open)
           ) {
             shouldOpen = true;
diff --git a/webui/src/web-components/sketch-tool-card-screenshot.ts b/webui/src/web-components/sketch-tool-card-take-screenshot.ts
similarity index 95%
rename from webui/src/web-components/sketch-tool-card-screenshot.ts
rename to webui/src/web-components/sketch-tool-card-take-screenshot.ts
index ea56aa8..33bfae3 100644
--- a/webui/src/web-components/sketch-tool-card-screenshot.ts
+++ b/webui/src/web-components/sketch-tool-card-take-screenshot.ts
@@ -2,8 +2,8 @@
 import { customElement, property, state } from "lit/decorators.js";
 import { ToolCall } from "../types";
 
-@customElement("sketch-tool-card-screenshot")
-export class SketchToolCardScreenshot extends LitElement {
+@customElement("sketch-tool-card-take-screenshot")
+export class SketchToolCardTakeScreenshot extends LitElement {
   @property()
   toolCall: ToolCall;
 
@@ -153,6 +153,6 @@
 
 declare global {
   interface HTMLElementTagNameMap {
-    "sketch-tool-card-screenshot": SketchToolCardScreenshot;
+    "sketch-tool-card-take-screenshot": SketchToolCardTakeScreenshot;
   }
 }