llm: make Tool.Run return ToolOut
This is preliminary work towards
allowing tools to add additional information.
No functional changes (at least, that's the intent).
diff --git a/claudetool/browse/browse.go b/claudetool/browse/browse.go
index e96a7f9..99f6ea7 100644
--- a/claudetool/browse/browse.go
+++ b/claudetool/browse/browse.go
@@ -182,19 +182,19 @@
}
}
-func (b *BrowseTools) navigateRun(ctx context.Context, m json.RawMessage) ([]llm.Content, error) {
+func (b *BrowseTools) navigateRun(ctx context.Context, m json.RawMessage) llm.ToolOut {
var input navigateInput
if err := json.Unmarshal(m, &input); err != nil {
- return nil, fmt.Errorf("invalid input: %w", err)
+ return llm.ErrorfToolOut("invalid input: %w", err)
}
if isPort80(input.URL) {
- return nil, fmt.Errorf("port 80 is not the port you're looking for--port 80 is the main sketch server")
+ return llm.ErrorToolOut(fmt.Errorf("port 80 is not the port you're looking for--port 80 is the main sketch server"))
}
browserCtx, err := b.GetBrowserContext()
if err != nil {
- return nil, err
+ return llm.ErrorToolOut(err)
}
// Create a timeout context for this operation
@@ -206,10 +206,10 @@
chromedp.WaitReady("body"),
)
if err != nil {
- return nil, err
+ return llm.ErrorToolOut(err)
}
- return llm.TextContent("done"), nil
+ return llm.ToolOut{LLMContent: llm.TextContent("done")}
}
// ClickTool definition
@@ -246,15 +246,15 @@
}
}
-func (b *BrowseTools) clickRun(ctx context.Context, m json.RawMessage) ([]llm.Content, error) {
+func (b *BrowseTools) clickRun(ctx context.Context, m json.RawMessage) llm.ToolOut {
var input clickInput
if err := json.Unmarshal(m, &input); err != nil {
- return nil, fmt.Errorf("invalid input: %w", err)
+ return llm.ErrorfToolOut("invalid input: %w", err)
}
browserCtx, err := b.GetBrowserContext()
if err != nil {
- return nil, err
+ return llm.ErrorToolOut(err)
}
// Create a timeout context for this operation
@@ -273,10 +273,10 @@
err = chromedp.Run(timeoutCtx, actions...)
if err != nil {
- return nil, err
+ return llm.ErrorToolOut(err)
}
- return llm.TextContent("done"), nil
+ return llm.ToolOut{LLMContent: llm.TextContent("done")}
}
// TypeTool definition
@@ -318,15 +318,15 @@
}
}
-func (b *BrowseTools) typeRun(ctx context.Context, m json.RawMessage) ([]llm.Content, error) {
+func (b *BrowseTools) typeRun(ctx context.Context, m json.RawMessage) llm.ToolOut {
var input typeInput
if err := json.Unmarshal(m, &input); err != nil {
- return nil, fmt.Errorf("invalid input: %w", err)
+ return llm.ErrorfToolOut("invalid input: %w", err)
}
browserCtx, err := b.GetBrowserContext()
if err != nil {
- return nil, err
+ return llm.ErrorToolOut(err)
}
// Create a timeout context for this operation
@@ -346,10 +346,10 @@
err = chromedp.Run(timeoutCtx, actions...)
if err != nil {
- return nil, err
+ return llm.ErrorToolOut(err)
}
- return llm.TextContent("done"), nil
+ return llm.ToolOut{LLMContent: llm.TextContent("done")}
}
// WaitForTool definition
@@ -381,15 +381,15 @@
}
}
-func (b *BrowseTools) waitForRun(ctx context.Context, m json.RawMessage) ([]llm.Content, error) {
+func (b *BrowseTools) waitForRun(ctx context.Context, m json.RawMessage) llm.ToolOut {
var input waitForInput
if err := json.Unmarshal(m, &input); err != nil {
- return nil, fmt.Errorf("invalid input: %w", err)
+ return llm.ErrorfToolOut("invalid input: %w", err)
}
browserCtx, err := b.GetBrowserContext()
if err != nil {
- return nil, err
+ return llm.ErrorToolOut(err)
}
timeoutCtx, cancel := context.WithTimeout(browserCtx, parseTimeout(input.Timeout))
@@ -397,10 +397,10 @@
err = chromedp.Run(timeoutCtx, chromedp.WaitReady(input.Selector))
if err != nil {
- return nil, err
+ return llm.ErrorToolOut(err)
}
- return llm.TextContent("done"), nil
+ return llm.ToolOut{LLMContent: llm.TextContent("done")}
}
// GetTextTool definition
@@ -432,15 +432,15 @@
}
}
-func (b *BrowseTools) getTextRun(ctx context.Context, m json.RawMessage) ([]llm.Content, error) {
+func (b *BrowseTools) getTextRun(ctx context.Context, m json.RawMessage) llm.ToolOut {
var input getTextInput
if err := json.Unmarshal(m, &input); err != nil {
- return nil, fmt.Errorf("invalid input: %w", err)
+ return llm.ErrorfToolOut("invalid input: %w", err)
}
browserCtx, err := b.GetBrowserContext()
if err != nil {
- return nil, err
+ return llm.ErrorToolOut(err)
}
// Create a timeout context for this operation
@@ -453,10 +453,10 @@
chromedp.Text(input.Selector, &text),
)
if err != nil {
- return nil, err
+ return llm.ErrorToolOut(err)
}
- return llm.TextContent("<innerText>" + text + "</innerText>"), nil
+ return llm.ToolOut{LLMContent: llm.TextContent("<innerText>" + text + "</innerText>")}
}
// EvalTool definition
@@ -488,15 +488,15 @@
}
}
-func (b *BrowseTools) evalRun(ctx context.Context, m json.RawMessage) ([]llm.Content, error) {
+func (b *BrowseTools) evalRun(ctx context.Context, m json.RawMessage) llm.ToolOut {
var input evalInput
if err := json.Unmarshal(m, &input); err != nil {
- return nil, fmt.Errorf("invalid input: %w", err)
+ return llm.ErrorfToolOut("invalid input: %w", err)
}
browserCtx, err := b.GetBrowserContext()
if err != nil {
- return nil, err
+ return llm.ErrorToolOut(err)
}
// Create a timeout context for this operation
@@ -506,16 +506,16 @@
var result any
err = chromedp.Run(timeoutCtx, chromedp.Evaluate(input.Expression, &result))
if err != nil {
- return nil, err
+ return llm.ErrorToolOut(err)
}
// Return the result as JSON
response, err := json.Marshal(result)
if err != nil {
- return nil, fmt.Errorf("failed to marshal response: %w", err)
+ return llm.ErrorfToolOut("failed to marshal response: %w", err)
}
- return llm.TextContent("<javascript_result>" + string(response) + "</javascript_result>"), nil
+ return llm.ToolOut{LLMContent: llm.TextContent("<javascript_result>" + string(response) + "</javascript_result>")}
}
// ScreenshotTool definition
@@ -552,15 +552,15 @@
}
}
-func (b *BrowseTools) screenshotRun(ctx context.Context, m json.RawMessage) ([]llm.Content, error) {
+func (b *BrowseTools) screenshotRun(ctx context.Context, m json.RawMessage) llm.ToolOut {
var input screenshotInput
if err := json.Unmarshal(m, &input); err != nil {
- return nil, fmt.Errorf("invalid input: %w", err)
+ return llm.ErrorfToolOut("invalid input: %w", err)
}
browserCtx, err := b.GetBrowserContext()
if err != nil {
- return nil, err
+ return llm.ErrorToolOut(err)
}
// Create a timeout context for this operation
@@ -583,13 +583,13 @@
err = chromedp.Run(timeoutCtx, actions...)
if err != nil {
- return nil, err
+ return llm.ErrorToolOut(err)
}
// Save the screenshot and get its ID for potential future reference
id := b.SaveScreenshot(buf)
if id == "" {
- return nil, fmt.Errorf("failed to save screenshot")
+ return llm.ErrorToolOut(fmt.Errorf("failed to save screenshot"))
}
// Get the full path to the screenshot
@@ -599,7 +599,7 @@
base64Data := base64.StdEncoding.EncodeToString(buf)
// Return the screenshot directly to the LLM
- return []llm.Content{
+ return llm.ToolOut{LLMContent: []llm.Content{
{
Type: llm.ContentTypeText,
Text: fmt.Sprintf("Screenshot taken (saved as %s)", screenshotPath),
@@ -609,7 +609,7 @@
MediaType: "image/png",
Data: base64Data,
},
- }, nil
+ }}
}
// ScrollIntoViewTool definition
@@ -641,15 +641,15 @@
}
}
-func (b *BrowseTools) scrollIntoViewRun(ctx context.Context, m json.RawMessage) ([]llm.Content, error) {
+func (b *BrowseTools) scrollIntoViewRun(ctx context.Context, m json.RawMessage) llm.ToolOut {
var input scrollIntoViewInput
if err := json.Unmarshal(m, &input); err != nil {
- return nil, fmt.Errorf("invalid input: %w", err)
+ return llm.ErrorfToolOut("invalid input: %w", err)
}
browserCtx, err := b.GetBrowserContext()
if err != nil {
- return nil, err
+ return llm.ErrorToolOut(err)
}
// Create a timeout context for this operation
@@ -671,14 +671,14 @@
chromedp.Evaluate(script, &result),
)
if err != nil {
- return nil, err
+ return llm.ErrorToolOut(err)
}
if !result {
- return nil, fmt.Errorf("element not found: %s", input.Selector)
+ return llm.ErrorToolOut(fmt.Errorf("element not found: %s", input.Selector))
}
- return llm.TextContent("done"), nil
+ return llm.ToolOut{LLMContent: llm.TextContent("done")}
}
// ResizeTool definition
@@ -715,15 +715,15 @@
}
}
-func (b *BrowseTools) resizeRun(ctx context.Context, m json.RawMessage) ([]llm.Content, error) {
+func (b *BrowseTools) resizeRun(ctx context.Context, m json.RawMessage) llm.ToolOut {
var input resizeInput
if err := json.Unmarshal(m, &input); err != nil {
- return nil, fmt.Errorf("invalid input: %w", err)
+ return llm.ErrorfToolOut("invalid input: %w", err)
}
browserCtx, err := b.GetBrowserContext()
if err != nil {
- return nil, err
+ return llm.ErrorToolOut(err)
}
// Create a timeout context for this operation
@@ -732,7 +732,7 @@
// Validate dimensions
if input.Width <= 0 || input.Height <= 0 {
- return nil, fmt.Errorf("invalid dimensions: width and height must be positive")
+ return llm.ErrorToolOut(fmt.Errorf("invalid dimensions: width and height must be positive"))
}
// Resize the browser window
@@ -740,10 +740,10 @@
chromedp.EmulateViewport(int64(input.Width), int64(input.Height)),
)
if err != nil {
- return nil, err
+ return llm.ErrorToolOut(err)
}
- return llm.TextContent("done"), nil
+ return llm.ToolOut{LLMContent: llm.TextContent("done")}
}
// GetTools returns browser tools, optionally filtering out screenshot-related tools
@@ -824,34 +824,34 @@
}
}
-func (b *BrowseTools) readImageRun(ctx context.Context, m json.RawMessage) ([]llm.Content, error) {
+func (b *BrowseTools) readImageRun(ctx context.Context, m json.RawMessage) llm.ToolOut {
var input readImageInput
if err := json.Unmarshal(m, &input); err != nil {
- return nil, fmt.Errorf("invalid input: %w", err)
+ return llm.ErrorfToolOut("invalid input: %w", err)
}
// Check if the path exists
if _, err := os.Stat(input.Path); os.IsNotExist(err) {
- return nil, fmt.Errorf("image file not found: %s", input.Path)
+ return llm.ErrorfToolOut("image file not found: %s", input.Path)
}
// Read the file
imageData, err := os.ReadFile(input.Path)
if err != nil {
- return nil, fmt.Errorf("failed to read image file: %w", err)
+ return llm.ErrorfToolOut("failed to read image file: %w", err)
}
// Detect the image type
imageType := http.DetectContentType(imageData)
if !strings.HasPrefix(imageType, "image/") {
- return nil, fmt.Errorf("file is not an image: %s", imageType)
+ return llm.ErrorfToolOut("file is not an image: %s", imageType)
}
// Encode the image as base64
base64Data := base64.StdEncoding.EncodeToString(imageData)
// Create a Content object that includes both text and the image
- return []llm.Content{
+ return llm.ToolOut{LLMContent: []llm.Content{
{
Type: llm.ContentTypeText,
Text: fmt.Sprintf("Image from %s (type: %s)", input.Path, imageType),
@@ -861,7 +861,7 @@
MediaType: imageType,
Data: base64Data,
},
- }, nil
+ }}
}
// parseTimeout parses a timeout string and returns a time.Duration
@@ -916,16 +916,16 @@
}
}
-func (b *BrowseTools) recentConsoleLogsRun(ctx context.Context, m json.RawMessage) ([]llm.Content, error) {
+func (b *BrowseTools) recentConsoleLogsRun(ctx context.Context, m json.RawMessage) llm.ToolOut {
var input recentConsoleLogsInput
if err := json.Unmarshal(m, &input); err != nil {
- return nil, fmt.Errorf("invalid input: %w", err)
+ return llm.ErrorfToolOut("invalid input: %w", err)
}
// Ensure browser is initialized
_, err := b.GetBrowserContext()
if err != nil {
- return nil, err
+ return llm.ErrorToolOut(err)
}
// Apply limit (default to 100 if not specified)
@@ -947,7 +947,7 @@
// Format the logs as JSON
logData, err := json.MarshalIndent(logs, "", " ")
if err != nil {
- return nil, fmt.Errorf("failed to serialize logs: %w", err)
+ return llm.ErrorfToolOut("failed to serialize logs: %w", err)
}
// Format the logs
@@ -961,7 +961,7 @@
sb.WriteString(string(logData))
}
- return llm.TextContent(sb.String()), nil
+ return llm.ToolOut{LLMContent: llm.TextContent(sb.String())}
}
// ClearConsoleLogsTool definition
@@ -977,16 +977,16 @@
}
}
-func (b *BrowseTools) clearConsoleLogsRun(ctx context.Context, m json.RawMessage) ([]llm.Content, error) {
+func (b *BrowseTools) clearConsoleLogsRun(ctx context.Context, m json.RawMessage) llm.ToolOut {
var input clearConsoleLogsInput
if err := json.Unmarshal(m, &input); err != nil {
- return nil, fmt.Errorf("invalid input: %w", err)
+ return llm.ErrorfToolOut("invalid input: %w", err)
}
// Ensure browser is initialized
_, err := b.GetBrowserContext()
if err != nil {
- return nil, err
+ return llm.ErrorToolOut(err)
}
// Clear console logs with mutex protection
@@ -995,5 +995,5 @@
b.consoleLogs = make([]*runtime.EventConsoleAPICalled, 0)
b.consoleLogsMutex.Unlock()
- return llm.TextContent(fmt.Sprintf("Cleared %d console log entries.", logCount)), nil
+ return llm.ToolOut{LLMContent: llm.TextContent(fmt.Sprintf("Cleared %d console log entries.", logCount))}
}