From e557dd543df302c9c0fca2f396fe164972ecc071 Mon Sep 17 00:00:00 2001 From: sawka Date: Sat, 1 Nov 2025 13:28:58 -0700 Subject: [PATCH 1/4] add toolusedata to inputdesc --- pkg/aiusechat/openai/openai-backend.go | 2 +- pkg/aiusechat/tools_builder.go | 6 +++--- pkg/aiusechat/tools_readdir.go | 2 +- pkg/aiusechat/tools_readfile.go | 3 +-- pkg/aiusechat/tools_screenshot.go | 2 +- pkg/aiusechat/tools_term.go | 4 ++-- pkg/aiusechat/tools_tsunami.go | 6 +++--- pkg/aiusechat/tools_web.go | 2 +- pkg/aiusechat/tools_writefile.go | 6 +++--- pkg/aiusechat/uctypes/usechat-types.go | 7 ++++--- 10 files changed, 20 insertions(+), 20 deletions(-) diff --git a/pkg/aiusechat/openai/openai-backend.go b/pkg/aiusechat/openai/openai-backend.go index 9ceaffb865..69bdf8d16f 100644 --- a/pkg/aiusechat/openai/openai-backend.go +++ b/pkg/aiusechat/openai/openai-backend.go @@ -940,7 +940,7 @@ func createToolUseData(toolCallID, toolName string, toolDef *uctypes.ToolDefinit } if toolDef.ToolInputDesc != nil { - toolUseData.ToolDesc = toolDef.ToolInputDesc(parsedArgs) + toolUseData.ToolDesc = toolDef.ToolInputDesc(parsedArgs, nil) } if toolDef.ToolApproval != nil { diff --git a/pkg/aiusechat/tools_builder.go b/pkg/aiusechat/tools_builder.go index c6fd0c4443..5ffa717b2a 100644 --- a/pkg/aiusechat/tools_builder.go +++ b/pkg/aiusechat/tools_builder.go @@ -55,7 +55,7 @@ func GetBuilderWriteAppFileToolDefinition(appId string) uctypes.ToolDefinition { "required": []string{"contents"}, "additionalProperties": false, }, - ToolInputDesc: func(input any) string { + ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { return fmt.Sprintf("writing app.go for %s", appId) }, ToolAnyCallback: func(input any, toolUseData *uctypes.UIMessageDataToolUse) (any, error) { @@ -142,7 +142,7 @@ func GetBuilderEditAppFileToolDefinition(appId string) uctypes.ToolDefinition { "required": []string{"edits"}, "additionalProperties": false, }, - ToolInputDesc: func(input any) string { + ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { params, err := parseBuilderEditAppFileInput(input) if err != nil { return fmt.Sprintf("error parsing input: %v", err) @@ -185,7 +185,7 @@ func GetBuilderListFilesToolDefinition(appId string) uctypes.ToolDefinition { "properties": map[string]any{}, "additionalProperties": false, }, - ToolInputDesc: func(input any) string { + ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { return fmt.Sprintf("listing files for %s", appId) }, ToolAnyCallback: func(input any, toolUseData *uctypes.UIMessageDataToolUse) (any, error) { diff --git a/pkg/aiusechat/tools_readdir.go b/pkg/aiusechat/tools_readdir.go index cb75dd58ae..c43db0f967 100644 --- a/pkg/aiusechat/tools_readdir.go +++ b/pkg/aiusechat/tools_readdir.go @@ -106,7 +106,7 @@ func GetReadDirToolDefinition() uctypes.ToolDefinition { "required": []string{"path"}, "additionalProperties": false, }, - ToolInputDesc: func(input any) string { + ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { parsed, err := parseReadDirInput(input) if err != nil { return fmt.Sprintf("error parsing input: %v", err) diff --git a/pkg/aiusechat/tools_readfile.go b/pkg/aiusechat/tools_readfile.go index f8cf4b7556..dd1d2645d1 100644 --- a/pkg/aiusechat/tools_readfile.go +++ b/pkg/aiusechat/tools_readfile.go @@ -197,7 +197,6 @@ func isBlockedFile(expandedPath string) (bool, string) { return false, "" } - func readTextFileCallback(input any, toolUseData *uctypes.UIMessageDataToolUse) (any, error) { const ReadLimit = 1024 * 1024 * 1024 @@ -332,7 +331,7 @@ func GetReadTextFileToolDefinition() uctypes.ToolDefinition { "required": []string{"filename"}, "additionalProperties": false, }, - ToolInputDesc: func(input any) string { + ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { parsed, err := parseReadTextFileInput(input) if err != nil { return fmt.Sprintf("error parsing input: %v", err) diff --git a/pkg/aiusechat/tools_screenshot.go b/pkg/aiusechat/tools_screenshot.go index 5b4007933f..182aa08451 100644 --- a/pkg/aiusechat/tools_screenshot.go +++ b/pkg/aiusechat/tools_screenshot.go @@ -67,7 +67,7 @@ func GetCaptureScreenshotToolDefinition(tabId string) uctypes.ToolDefinition { "required": []string{"widget_id"}, "additionalProperties": false, }, - ToolInputDesc: func(input any) string { + ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { inputMap, ok := input.(map[string]any) if !ok { return "error parsing input: invalid format" diff --git a/pkg/aiusechat/tools_term.go b/pkg/aiusechat/tools_term.go index 8d9040df16..94067c31b1 100644 --- a/pkg/aiusechat/tools_term.go +++ b/pkg/aiusechat/tools_term.go @@ -178,7 +178,7 @@ func GetTermGetScrollbackToolDefinition(tabId string) uctypes.ToolDefinition { "required": []string{"widget_id"}, "additionalProperties": false, }, - ToolInputDesc: func(input any) string { + ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { parsed, err := parseTermGetScrollbackInput(input) if err != nil { return fmt.Sprintf("error parsing input: %v", err) @@ -259,7 +259,7 @@ func GetTermCommandOutputToolDefinition(tabId string) uctypes.ToolDefinition { "required": []string{"widget_id"}, "additionalProperties": false, }, - ToolInputDesc: func(input any) string { + ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { parsed, err := parseTermCommandOutputInput(input) if err != nil { return fmt.Sprintf("error parsing input: %v", err) diff --git a/pkg/aiusechat/tools_tsunami.go b/pkg/aiusechat/tools_tsunami.go index e6ab8756fd..0a4cb22370 100644 --- a/pkg/aiusechat/tools_tsunami.go +++ b/pkg/aiusechat/tools_tsunami.go @@ -124,7 +124,7 @@ func GetTsunamiGetDataToolDefinition(block *waveobj.Block, rtInfo *waveobj.ObjRT "properties": map[string]any{}, "additionalProperties": false, }, - ToolInputDesc: func(input any) string { + ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { return fmt.Sprintf("getting data from %s (%s)", desc, blockIdPrefix) }, ToolAnyCallback: makeTsunamiGetCallback(status, "/api/data"), @@ -149,7 +149,7 @@ func GetTsunamiGetConfigToolDefinition(block *waveobj.Block, rtInfo *waveobj.Obj "properties": map[string]any{}, "additionalProperties": false, }, - ToolInputDesc: func(input any) string { + ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { return fmt.Sprintf("getting config from %s (%s)", desc, blockIdPrefix) }, ToolAnyCallback: makeTsunamiGetCallback(status, "/api/config"), @@ -182,7 +182,7 @@ func GetTsunamiSetConfigToolDefinition(block *waveobj.Block, rtInfo *waveobj.Obj Name: toolName, ToolLogName: "tsunami:setconfig", InputSchema: inputSchema, - ToolInputDesc: func(input any) string { + ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { return fmt.Sprintf("updating config for %s (%s)", desc, blockIdPrefix) }, ToolAnyCallback: makeTsunamiPostCallback(status, "/api/config"), diff --git a/pkg/aiusechat/tools_web.go b/pkg/aiusechat/tools_web.go index c707c14479..3ce154188f 100644 --- a/pkg/aiusechat/tools_web.go +++ b/pkg/aiusechat/tools_web.go @@ -70,7 +70,7 @@ func GetWebNavigateToolDefinition(tabId string) uctypes.ToolDefinition { "required": []string{"widget_id", "url"}, "additionalProperties": false, }, - ToolInputDesc: func(input any) string { + ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { parsed, err := parseWebNavigateInput(input) if err != nil { return fmt.Sprintf("error parsing input: %v", err) diff --git a/pkg/aiusechat/tools_writefile.go b/pkg/aiusechat/tools_writefile.go index 5d6ffeebcc..7af25ff504 100644 --- a/pkg/aiusechat/tools_writefile.go +++ b/pkg/aiusechat/tools_writefile.go @@ -194,7 +194,7 @@ func GetWriteTextFileToolDefinition() uctypes.ToolDefinition { "required": []string{"filename", "contents"}, "additionalProperties": false, }, - ToolInputDesc: func(input any) string { + ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { params, err := parseWriteTextFileInput(input) if err != nil { return fmt.Sprintf("error parsing input: %v", err) @@ -369,7 +369,7 @@ func GetEditTextFileToolDefinition() uctypes.ToolDefinition { "required": []string{"filename", "edits"}, "additionalProperties": false, }, - ToolInputDesc: func(input any) string { + ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { params, err := parseEditTextFileInput(input) if err != nil { return fmt.Sprintf("error parsing input: %v", err) @@ -482,7 +482,7 @@ func GetDeleteTextFileToolDefinition() uctypes.ToolDefinition { "required": []string{"filename"}, "additionalProperties": false, }, - ToolInputDesc: func(input any) string { + ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { params, err := parseDeleteTextFileInput(input) if err != nil { return fmt.Sprintf("error parsing input: %v", err) diff --git a/pkg/aiusechat/uctypes/usechat-types.go b/pkg/aiusechat/uctypes/usechat-types.go index 55a6804bf9..97e75737d9 100644 --- a/pkg/aiusechat/uctypes/usechat-types.go +++ b/pkg/aiusechat/uctypes/usechat-types.go @@ -86,10 +86,10 @@ type ToolDefinition struct { Strict bool `json:"strict,omitempty"` ToolTextCallback func(any) (string, error) `json:"-"` - ToolAnyCallback func(any, *UIMessageDataToolUse) (any, error) `json:"-"` - ToolInputDesc func(any) string `json:"-"` + ToolAnyCallback func(any, *UIMessageDataToolUse) (any, error) `json:"-"` // *UIMessageDataToolUse will NOT be nil + ToolInputDesc func(any, *UIMessageDataToolUse) string `json:"-"` // *UIMessageDataToolUse may be nil (unlike ToolAnyCallback/ToolVerifyInput where it is guaranteed not to be nil) ToolApproval func(any) string `json:"-"` - ToolVerifyInput func(any, *UIMessageDataToolUse) error `json:"-"` + ToolVerifyInput func(any, *UIMessageDataToolUse) error `json:"-"` // *UIMessageDataToolUse will NOT be nil } func (td *ToolDefinition) Clean() *ToolDefinition { @@ -148,6 +148,7 @@ type UIMessageDataToolUse struct { BlockId string `json:"blockid,omitempty"` WriteBackupFileName string `json:"writebackupfilename,omitempty"` InputFileName string `json:"inputfilename,omitempty"` + TotalItems *int `json:"totalitems,omitempty"` // metadata specific to tool call } func (d *UIMessageDataToolUse) IsApproved() bool { From ba135ce3fb2525fee3ccaa82be63e592961da64c Mon Sep 17 00:00:00 2001 From: sawka Date: Sun, 2 Nov 2025 11:25:44 -0800 Subject: [PATCH 2/4] fix utf-8 diffs --- .roo/rules/rules.md | 2 ++ frontend/app/view/aifilediff/aifilediff.tsx | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.roo/rules/rules.md b/.roo/rules/rules.md index 54ef512159..d455e69277 100644 --- a/.roo/rules/rules.md +++ b/.roo/rules/rules.md @@ -41,6 +41,8 @@ It has a TypeScript/React frontend and a Go backend. They talk together over `ws - NEVER use cursor-help (it looks terrible) - useAtom() and useAtomValue() are react HOOKS, so they must be called at the component level not inline in JSX - If you use React.memo(), make sure to add a displayName for the component + - Other + - never use atob() or btoa() (not UTF-8 safe). use functions in frontend/util/util.ts for base64 decoding and encoding - In general, when writing functions, we prefer _early returns_ rather than putting the majority of a function inside of an if block. ### Styling diff --git a/frontend/app/view/aifilediff/aifilediff.tsx b/frontend/app/view/aifilediff/aifilediff.tsx index 68409b0137..ce56c1b0f7 100644 --- a/frontend/app/view/aifilediff/aifilediff.tsx +++ b/frontend/app/view/aifilediff/aifilediff.tsx @@ -3,6 +3,7 @@ import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; +import { base64ToString } from "@/util/util"; import { DiffViewer } from "@/app/view/codeeditor/diffviewer"; import { globalStore, WOS } from "@/store/global"; import * as jotai from "jotai"; @@ -80,8 +81,8 @@ const AiFileDiffView: React.FC> = ({ blo return; } - const originalContent = atob(result.originalcontents64); - const modifiedContent = atob(result.modifiedcontents64); + const originalContent = base64ToString(result.originalcontents64); + const modifiedContent = base64ToString(result.modifiedcontents64); globalStore.set(model.diffDataAtom, { original: originalContent, From 330030809b1a183cbb35a65d1503b6d585a34114 Mon Sep 17 00:00:00 2001 From: sawka Date: Sun, 2 Nov 2025 11:26:26 -0800 Subject: [PATCH 3/4] better toolinput desc --- pkg/aiusechat/openai/openai-backend.go | 4 ++-- pkg/aiusechat/tools_builder.go | 6 +++--- pkg/aiusechat/tools_readdir.go | 14 +++++++++++++- pkg/aiusechat/tools_readdir_test.go | 2 +- pkg/aiusechat/tools_readfile.go | 22 ++++++++++++++++++---- pkg/aiusechat/tools_screenshot.go | 2 +- pkg/aiusechat/tools_term.go | 5 ++--- pkg/aiusechat/tools_tsunami.go | 6 +++--- pkg/aiusechat/tools_web.go | 4 ++-- pkg/aiusechat/tools_writefile.go | 6 +++--- pkg/aiusechat/uctypes/usechat-types.go | 3 +-- pkg/aiusechat/usechat.go | 17 +++++++++++++---- 12 files changed, 62 insertions(+), 29 deletions(-) diff --git a/pkg/aiusechat/openai/openai-backend.go b/pkg/aiusechat/openai/openai-backend.go index 69bdf8d16f..7edf2c0a9a 100644 --- a/pkg/aiusechat/openai/openai-backend.go +++ b/pkg/aiusechat/openai/openai-backend.go @@ -939,8 +939,8 @@ func createToolUseData(toolCallID, toolName string, toolDef *uctypes.ToolDefinit return toolUseData } - if toolDef.ToolInputDesc != nil { - toolUseData.ToolDesc = toolDef.ToolInputDesc(parsedArgs, nil) + if toolDef.ToolCallDesc != nil { + toolUseData.ToolDesc = toolDef.ToolCallDesc(parsedArgs, nil, nil) } if toolDef.ToolApproval != nil { diff --git a/pkg/aiusechat/tools_builder.go b/pkg/aiusechat/tools_builder.go index 5ffa717b2a..5d5022d7c9 100644 --- a/pkg/aiusechat/tools_builder.go +++ b/pkg/aiusechat/tools_builder.go @@ -55,7 +55,7 @@ func GetBuilderWriteAppFileToolDefinition(appId string) uctypes.ToolDefinition { "required": []string{"contents"}, "additionalProperties": false, }, - ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { + ToolCallDesc: func(input any, output any, toolUseData *uctypes.UIMessageDataToolUse) string { return fmt.Sprintf("writing app.go for %s", appId) }, ToolAnyCallback: func(input any, toolUseData *uctypes.UIMessageDataToolUse) (any, error) { @@ -142,7 +142,7 @@ func GetBuilderEditAppFileToolDefinition(appId string) uctypes.ToolDefinition { "required": []string{"edits"}, "additionalProperties": false, }, - ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { + ToolCallDesc: func(input any, output any, toolUseData *uctypes.UIMessageDataToolUse) string { params, err := parseBuilderEditAppFileInput(input) if err != nil { return fmt.Sprintf("error parsing input: %v", err) @@ -185,7 +185,7 @@ func GetBuilderListFilesToolDefinition(appId string) uctypes.ToolDefinition { "properties": map[string]any{}, "additionalProperties": false, }, - ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { + ToolCallDesc: func(input any, output any, toolUseData *uctypes.UIMessageDataToolUse) string { return fmt.Sprintf("listing files for %s", appId) }, ToolAnyCallback: func(input any, toolUseData *uctypes.UIMessageDataToolUse) (any, error) { diff --git a/pkg/aiusechat/tools_readdir.go b/pkg/aiusechat/tools_readdir.go index c43db0f967..2716166fa5 100644 --- a/pkg/aiusechat/tools_readdir.go +++ b/pkg/aiusechat/tools_readdir.go @@ -106,11 +106,23 @@ func GetReadDirToolDefinition() uctypes.ToolDefinition { "required": []string{"path"}, "additionalProperties": false, }, - ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { + ToolCallDesc: func(input any, output any, toolUseData *uctypes.UIMessageDataToolUse) string { parsed, err := parseReadDirInput(input) if err != nil { return fmt.Sprintf("error parsing input: %v", err) } + + readFullDir := false + if output != nil { + if outputMap, ok := output.(map[string]any); ok { + _, wasTruncated := outputMap["truncated"] + readFullDir = !wasTruncated + } + } + + if readFullDir { + return fmt.Sprintf("reading directory %q (entire directory)", parsed.Path) + } return fmt.Sprintf("reading directory %q (max_entries: %d)", parsed.Path, *parsed.MaxEntries) }, ToolAnyCallback: readDirCallback, diff --git a/pkg/aiusechat/tools_readdir_test.go b/pkg/aiusechat/tools_readdir_test.go index d73d5a6d89..d07c99887c 100644 --- a/pkg/aiusechat/tools_readdir_test.go +++ b/pkg/aiusechat/tools_readdir_test.go @@ -287,7 +287,7 @@ func TestGetReadDirToolDefinition(t *testing.T) { t.Error("ToolApproval should not be nil") } - if toolDef.ToolInputDesc == nil { + if toolDef.ToolCallDesc == nil { t.Error("ToolInputDesc should not be nil") } } diff --git a/pkg/aiusechat/tools_readfile.go b/pkg/aiusechat/tools_readfile.go index dd1d2645d1..1d0a21969b 100644 --- a/pkg/aiusechat/tools_readfile.go +++ b/pkg/aiusechat/tools_readfile.go @@ -282,7 +282,7 @@ func readTextFileCallback(input any, toolUseData *uctypes.UIMessageDataToolUse) "modified_time": modTime.UTC().Format(time.RFC3339), "mode": fileInfo.Mode().String(), } - if stopReason != "" { + if stopReason == "read_limit" || stopReason == StopReasonMaxBytes { result["truncated"] = stopReason } @@ -318,20 +318,20 @@ func GetReadTextFileToolDefinition() uctypes.ToolDefinition { "count": map[string]any{ "type": "integer", "minimum": 1, - "default": 100, + "default": ReadFileDefaultLineCount, "description": "Number of lines to return", }, "max_bytes": map[string]any{ "type": "integer", "minimum": 1, - "default": 51200, + "default": ReadFileDefaultMaxBytes, "description": "Maximum bytes to return. If the result exceeds this, it will be truncated at line boundaries", }, }, "required": []string{"filename"}, "additionalProperties": false, }, - ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { + ToolCallDesc: func(input any, output any, toolUseData *uctypes.UIMessageDataToolUse) string { parsed, err := parseReadTextFileInput(input) if err != nil { return fmt.Sprintf("error parsing input: %v", err) @@ -341,10 +341,24 @@ func GetReadTextFileToolDefinition() uctypes.ToolDefinition { offset := *parsed.Offset count := *parsed.Count + readFullFile := false + if output != nil { + if outputMap, ok := output.(map[string]any); ok { + _, wasTruncated := outputMap["truncated"] + readFullFile = !wasTruncated + } + } + if origin == "start" && offset == 0 { + if readFullFile { + return fmt.Sprintf("reading %q (entire file)", parsed.Filename) + } return fmt.Sprintf("reading %q (first %d lines)", parsed.Filename, count) } if origin == "end" && offset == 0 { + if readFullFile { + return fmt.Sprintf("reading %q (entire file)", parsed.Filename) + } return fmt.Sprintf("reading %q (last %d lines)", parsed.Filename, count) } if origin == "end" { diff --git a/pkg/aiusechat/tools_screenshot.go b/pkg/aiusechat/tools_screenshot.go index 182aa08451..4c924db292 100644 --- a/pkg/aiusechat/tools_screenshot.go +++ b/pkg/aiusechat/tools_screenshot.go @@ -67,7 +67,7 @@ func GetCaptureScreenshotToolDefinition(tabId string) uctypes.ToolDefinition { "required": []string{"widget_id"}, "additionalProperties": false, }, - ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { + ToolCallDesc: func(input any, output any, toolUseData *uctypes.UIMessageDataToolUse) string { inputMap, ok := input.(map[string]any) if !ok { return "error parsing input: invalid format" diff --git a/pkg/aiusechat/tools_term.go b/pkg/aiusechat/tools_term.go index 94067c31b1..7d0ad90507 100644 --- a/pkg/aiusechat/tools_term.go +++ b/pkg/aiusechat/tools_term.go @@ -178,7 +178,7 @@ func GetTermGetScrollbackToolDefinition(tabId string) uctypes.ToolDefinition { "required": []string{"widget_id"}, "additionalProperties": false, }, - ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { + ToolCallDesc: func(input any, output any, toolUseData *uctypes.UIMessageDataToolUse) string { parsed, err := parseTermGetScrollbackInput(input) if err != nil { return fmt.Sprintf("error parsing input: %v", err) @@ -214,7 +214,6 @@ func GetTermGetScrollbackToolDefinition(tabId string) uctypes.ToolDefinition { } } - type TermCommandOutputToolInput struct { WidgetId string `json:"widget_id"` } @@ -259,7 +258,7 @@ func GetTermCommandOutputToolDefinition(tabId string) uctypes.ToolDefinition { "required": []string{"widget_id"}, "additionalProperties": false, }, - ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { + ToolCallDesc: func(input any, output any, toolUseData *uctypes.UIMessageDataToolUse) string { parsed, err := parseTermCommandOutputInput(input) if err != nil { return fmt.Sprintf("error parsing input: %v", err) diff --git a/pkg/aiusechat/tools_tsunami.go b/pkg/aiusechat/tools_tsunami.go index 0a4cb22370..239b0155a5 100644 --- a/pkg/aiusechat/tools_tsunami.go +++ b/pkg/aiusechat/tools_tsunami.go @@ -124,7 +124,7 @@ func GetTsunamiGetDataToolDefinition(block *waveobj.Block, rtInfo *waveobj.ObjRT "properties": map[string]any{}, "additionalProperties": false, }, - ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { + ToolCallDesc: func(input any, output any, toolUseData *uctypes.UIMessageDataToolUse) string { return fmt.Sprintf("getting data from %s (%s)", desc, blockIdPrefix) }, ToolAnyCallback: makeTsunamiGetCallback(status, "/api/data"), @@ -149,7 +149,7 @@ func GetTsunamiGetConfigToolDefinition(block *waveobj.Block, rtInfo *waveobj.Obj "properties": map[string]any{}, "additionalProperties": false, }, - ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { + ToolCallDesc: func(input any, output any, toolUseData *uctypes.UIMessageDataToolUse) string { return fmt.Sprintf("getting config from %s (%s)", desc, blockIdPrefix) }, ToolAnyCallback: makeTsunamiGetCallback(status, "/api/config"), @@ -182,7 +182,7 @@ func GetTsunamiSetConfigToolDefinition(block *waveobj.Block, rtInfo *waveobj.Obj Name: toolName, ToolLogName: "tsunami:setconfig", InputSchema: inputSchema, - ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { + ToolCallDesc: func(input any, output any, toolUseData *uctypes.UIMessageDataToolUse) string { return fmt.Sprintf("updating config for %s (%s)", desc, blockIdPrefix) }, ToolAnyCallback: makeTsunamiPostCallback(status, "/api/config"), diff --git a/pkg/aiusechat/tools_web.go b/pkg/aiusechat/tools_web.go index 3ce154188f..49b0cc33f4 100644 --- a/pkg/aiusechat/tools_web.go +++ b/pkg/aiusechat/tools_web.go @@ -70,7 +70,7 @@ func GetWebNavigateToolDefinition(tabId string) uctypes.ToolDefinition { "required": []string{"widget_id", "url"}, "additionalProperties": false, }, - ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { + ToolCallDesc: func(input any, output any, toolUseData *uctypes.UIMessageDataToolUse) string { parsed, err := parseWebNavigateInput(input) if err != nil { return fmt.Sprintf("error parsing input: %v", err) @@ -105,4 +105,4 @@ func GetWebNavigateToolDefinition(tabId string) uctypes.ToolDefinition { return true, nil }, } -} \ No newline at end of file +} diff --git a/pkg/aiusechat/tools_writefile.go b/pkg/aiusechat/tools_writefile.go index 7af25ff504..2c830fd64c 100644 --- a/pkg/aiusechat/tools_writefile.go +++ b/pkg/aiusechat/tools_writefile.go @@ -194,7 +194,7 @@ func GetWriteTextFileToolDefinition() uctypes.ToolDefinition { "required": []string{"filename", "contents"}, "additionalProperties": false, }, - ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { + ToolCallDesc: func(input any, output any, toolUseData *uctypes.UIMessageDataToolUse) string { params, err := parseWriteTextFileInput(input) if err != nil { return fmt.Sprintf("error parsing input: %v", err) @@ -369,7 +369,7 @@ func GetEditTextFileToolDefinition() uctypes.ToolDefinition { "required": []string{"filename", "edits"}, "additionalProperties": false, }, - ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { + ToolCallDesc: func(input any, output any, toolUseData *uctypes.UIMessageDataToolUse) string { params, err := parseEditTextFileInput(input) if err != nil { return fmt.Sprintf("error parsing input: %v", err) @@ -482,7 +482,7 @@ func GetDeleteTextFileToolDefinition() uctypes.ToolDefinition { "required": []string{"filename"}, "additionalProperties": false, }, - ToolInputDesc: func(input any, toolUseData *uctypes.UIMessageDataToolUse) string { + ToolCallDesc: func(input any, output any, toolUseData *uctypes.UIMessageDataToolUse) string { params, err := parseDeleteTextFileInput(input) if err != nil { return fmt.Sprintf("error parsing input: %v", err) diff --git a/pkg/aiusechat/uctypes/usechat-types.go b/pkg/aiusechat/uctypes/usechat-types.go index 97e75737d9..05db8a3c3a 100644 --- a/pkg/aiusechat/uctypes/usechat-types.go +++ b/pkg/aiusechat/uctypes/usechat-types.go @@ -87,7 +87,7 @@ type ToolDefinition struct { ToolTextCallback func(any) (string, error) `json:"-"` ToolAnyCallback func(any, *UIMessageDataToolUse) (any, error) `json:"-"` // *UIMessageDataToolUse will NOT be nil - ToolInputDesc func(any, *UIMessageDataToolUse) string `json:"-"` // *UIMessageDataToolUse may be nil (unlike ToolAnyCallback/ToolVerifyInput where it is guaranteed not to be nil) + ToolCallDesc func(any, any, *UIMessageDataToolUse) string `json:"-"` // passed input, output (may be nil), *UIMessageDataToolUse (may be nil) ToolApproval func(any) string `json:"-"` ToolVerifyInput func(any, *UIMessageDataToolUse) error `json:"-"` // *UIMessageDataToolUse will NOT be nil } @@ -148,7 +148,6 @@ type UIMessageDataToolUse struct { BlockId string `json:"blockid,omitempty"` WriteBackupFileName string `json:"writebackupfilename,omitempty"` InputFileName string `json:"inputfilename,omitempty"` - TotalItems *int `json:"totalitems,omitempty"` // metadata specific to tool call } func (d *UIMessageDataToolUse) IsApproved() bool { diff --git a/pkg/aiusechat/usechat.go b/pkg/aiusechat/usechat.go index f00463c2e4..8f380d7a9e 100644 --- a/pkg/aiusechat/usechat.go +++ b/pkg/aiusechat/usechat.go @@ -329,6 +329,9 @@ func processToolCallInternal(toolCall uctypes.WaveToolCall, chatOpts uctypes.Wav ErrorText: errorMsg, } } + // ToolVerifyInput can modify the toolusedata. re-send it here. + _ = sseHandler.AiMsgData("data-tooluse", toolCall.ID, *toolCall.ToolUseData) + updateToolUseDataInChat(chatOpts, toolCall.ID, toolCall.ToolUseData) } if toolCall.ToolUseData.Approval == uctypes.ApprovalNeedsApproval { @@ -361,7 +364,7 @@ func processToolCallInternal(toolCall uctypes.WaveToolCall, chatOpts uctypes.Wav } toolCall.ToolUseData.RunTs = time.Now().UnixMilli() - result := ResolveToolCall(toolCall, chatOpts) + result := ResolveToolCall(toolDef, toolCall, chatOpts) if result.ErrorText != "" { toolCall.ToolUseData.Status = uctypes.ToolUseStatusError @@ -536,7 +539,7 @@ func RunAIChat(ctx context.Context, sseHandler *sse.SSEHandlerCh, chatOpts uctyp return metrics, nil } -func ResolveToolCall(toolCall uctypes.WaveToolCall, chatOpts uctypes.WaveChatOpts) (result uctypes.AIToolResult) { +func ResolveToolCall(toolDef *uctypes.ToolDefinition, toolCall uctypes.WaveToolCall, chatOpts uctypes.WaveChatOpts) (result uctypes.AIToolResult) { result = uctypes.AIToolResult{ ToolName: toolCall.Name, ToolUseID: toolCall.ID, @@ -549,8 +552,6 @@ func ResolveToolCall(toolCall uctypes.WaveToolCall, chatOpts uctypes.WaveChatOpt } }() - toolDef := chatOpts.GetToolDefinition(toolCall.Name) - if toolDef == nil { result.ErrorText = fmt.Sprintf("tool '%s' not found", toolCall.Name) return @@ -563,6 +564,10 @@ func ResolveToolCall(toolCall uctypes.WaveToolCall, chatOpts uctypes.WaveChatOpt result.ErrorText = err.Error() } else { result.Text = text + // Recompute tool description with the result + if toolDef.ToolCallDesc != nil && toolCall.ToolUseData != nil { + toolCall.ToolUseData.ToolDesc = toolDef.ToolCallDesc(toolCall.Input, text, toolCall.ToolUseData) + } } } else if toolDef.ToolAnyCallback != nil { output, err := toolDef.ToolAnyCallback(toolCall.Input, toolCall.ToolUseData) @@ -575,6 +580,10 @@ func ResolveToolCall(toolCall uctypes.WaveToolCall, chatOpts uctypes.WaveChatOpt result.ErrorText = fmt.Sprintf("failed to marshal tool output: %v", marshalErr) } else { result.Text = string(jsonBytes) + // Recompute tool description with the result + if toolDef.ToolCallDesc != nil && toolCall.ToolUseData != nil { + toolCall.ToolUseData.ToolDesc = toolDef.ToolCallDesc(toolCall.Input, output, toolCall.ToolUseData) + } } } } else { From 1759df26a21efef418b4dd47035149c1ad9b8152 Mon Sep 17 00:00:00 2001 From: sawka Date: Sun, 2 Nov 2025 12:33:19 -0800 Subject: [PATCH 4/4] fix name in error --- pkg/aiusechat/tools_readdir_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/aiusechat/tools_readdir_test.go b/pkg/aiusechat/tools_readdir_test.go index d07c99887c..7560a73a4d 100644 --- a/pkg/aiusechat/tools_readdir_test.go +++ b/pkg/aiusechat/tools_readdir_test.go @@ -288,6 +288,6 @@ func TestGetReadDirToolDefinition(t *testing.T) { } if toolDef.ToolCallDesc == nil { - t.Error("ToolInputDesc should not be nil") + t.Error("ToolCallDesc should not be nil") } }