Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .roo/rules/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions frontend/app/view/aifilediff/aifilediff.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -80,8 +81,8 @@ const AiFileDiffView: React.FC<ViewComponentProps<AiFileDiffViewModel>> = ({ 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,
Expand Down
4 changes: 2 additions & 2 deletions pkg/aiusechat/openai/openai-backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -939,8 +939,8 @@ func createToolUseData(toolCallID, toolName string, toolDef *uctypes.ToolDefinit
return toolUseData
}

if toolDef.ToolInputDesc != nil {
toolUseData.ToolDesc = toolDef.ToolInputDesc(parsedArgs)
if toolDef.ToolCallDesc != nil {
toolUseData.ToolDesc = toolDef.ToolCallDesc(parsedArgs, nil, nil)
}

if toolDef.ToolApproval != nil {
Expand Down
6 changes: 3 additions & 3 deletions pkg/aiusechat/tools_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func GetBuilderWriteAppFileToolDefinition(appId string) uctypes.ToolDefinition {
"required": []string{"contents"},
"additionalProperties": false,
},
ToolInputDesc: func(input any) 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) {
Expand Down Expand Up @@ -142,7 +142,7 @@ func GetBuilderEditAppFileToolDefinition(appId string) uctypes.ToolDefinition {
"required": []string{"edits"},
"additionalProperties": false,
},
ToolInputDesc: func(input any) 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)
Expand Down Expand Up @@ -185,7 +185,7 @@ func GetBuilderListFilesToolDefinition(appId string) uctypes.ToolDefinition {
"properties": map[string]any{},
"additionalProperties": false,
},
ToolInputDesc: func(input any) 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) {
Expand Down
14 changes: 13 additions & 1 deletion pkg/aiusechat/tools_readdir.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,23 @@ func GetReadDirToolDefinition() uctypes.ToolDefinition {
"required": []string{"path"},
"additionalProperties": false,
},
ToolInputDesc: func(input any) 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,
Expand Down
4 changes: 2 additions & 2 deletions pkg/aiusechat/tools_readdir_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ func TestGetReadDirToolDefinition(t *testing.T) {
t.Error("ToolApproval should not be nil")
}

if toolDef.ToolInputDesc == nil {
t.Error("ToolInputDesc should not be nil")
if toolDef.ToolCallDesc == nil {
t.Error("ToolCallDesc should not be nil")
}
}
23 changes: 18 additions & 5 deletions pkg/aiusechat/tools_readfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -283,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
}

Expand Down Expand Up @@ -319,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) 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)
Expand All @@ -342,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" {
Expand Down
2 changes: 1 addition & 1 deletion pkg/aiusechat/tools_screenshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func GetCaptureScreenshotToolDefinition(tabId string) uctypes.ToolDefinition {
"required": []string{"widget_id"},
"additionalProperties": false,
},
ToolInputDesc: func(input any) 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"
Expand Down
5 changes: 2 additions & 3 deletions pkg/aiusechat/tools_term.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ func GetTermGetScrollbackToolDefinition(tabId string) uctypes.ToolDefinition {
"required": []string{"widget_id"},
"additionalProperties": false,
},
ToolInputDesc: func(input any) 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)
Expand Down Expand Up @@ -214,7 +214,6 @@ func GetTermGetScrollbackToolDefinition(tabId string) uctypes.ToolDefinition {
}
}


type TermCommandOutputToolInput struct {
WidgetId string `json:"widget_id"`
}
Expand Down Expand Up @@ -259,7 +258,7 @@ func GetTermCommandOutputToolDefinition(tabId string) uctypes.ToolDefinition {
"required": []string{"widget_id"},
"additionalProperties": false,
},
ToolInputDesc: func(input any) 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)
Expand Down
6 changes: 3 additions & 3 deletions pkg/aiusechat/tools_tsunami.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func GetTsunamiGetDataToolDefinition(block *waveobj.Block, rtInfo *waveobj.ObjRT
"properties": map[string]any{},
"additionalProperties": false,
},
ToolInputDesc: func(input any) 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"),
Expand All @@ -149,7 +149,7 @@ func GetTsunamiGetConfigToolDefinition(block *waveobj.Block, rtInfo *waveobj.Obj
"properties": map[string]any{},
"additionalProperties": false,
},
ToolInputDesc: func(input any) 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"),
Expand Down Expand Up @@ -182,7 +182,7 @@ func GetTsunamiSetConfigToolDefinition(block *waveobj.Block, rtInfo *waveobj.Obj
Name: toolName,
ToolLogName: "tsunami:setconfig",
InputSchema: inputSchema,
ToolInputDesc: func(input any) 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"),
Expand Down
4 changes: 2 additions & 2 deletions pkg/aiusechat/tools_web.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func GetWebNavigateToolDefinition(tabId string) uctypes.ToolDefinition {
"required": []string{"widget_id", "url"},
"additionalProperties": false,
},
ToolInputDesc: func(input any) 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)
Expand Down Expand Up @@ -105,4 +105,4 @@ func GetWebNavigateToolDefinition(tabId string) uctypes.ToolDefinition {
return true, nil
},
}
}
}
6 changes: 3 additions & 3 deletions pkg/aiusechat/tools_writefile.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ func GetWriteTextFileToolDefinition() uctypes.ToolDefinition {
"required": []string{"filename", "contents"},
"additionalProperties": false,
},
ToolInputDesc: func(input any) 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)
Expand Down Expand Up @@ -369,7 +369,7 @@ func GetEditTextFileToolDefinition() uctypes.ToolDefinition {
"required": []string{"filename", "edits"},
"additionalProperties": false,
},
ToolInputDesc: func(input any) 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)
Expand Down Expand Up @@ -482,7 +482,7 @@ func GetDeleteTextFileToolDefinition() uctypes.ToolDefinition {
"required": []string{"filename"},
"additionalProperties": false,
},
ToolInputDesc: func(input any) 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)
Expand Down
6 changes: 3 additions & 3 deletions pkg/aiusechat/uctypes/usechat-types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
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:"-"`
ToolVerifyInput func(any, *UIMessageDataToolUse) error `json:"-"` // *UIMessageDataToolUse will NOT be nil
}

func (td *ToolDefinition) Clean() *ToolDefinition {
Expand Down
17 changes: 13 additions & 4 deletions pkg/aiusechat/usechat.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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 {
Expand Down
Loading