From 3756c731873cf85eddf99fab8b4a013e7b1647de Mon Sep 17 00:00:00 2001 From: KoheiYamashita Date: Mon, 23 Feb 2026 03:55:56 +0900 Subject: [PATCH] fix: hide UI action parameters and error details from LLM in chat mode Remove UI-only parameters (x, y, tap, swipe, keyevent, etc.) from tool schema when clientType is "main", and use generic "unknown action" error to prevent the LLM from learning about restricted capabilities. Co-Authored-By: Claude Opus 4.6 --- pkg/tools/android.go | 188 ++++++++++++++++++++++--------------------- 1 file changed, 97 insertions(+), 91 deletions(-) diff --git a/pkg/tools/android.go b/pkg/tools/android.go index 16b9b75e4..96e06e066 100644 --- a/pkg/tools/android.go +++ b/pkg/tools/android.go @@ -99,97 +99,103 @@ func (t *AndroidTool) Parameters() map[string]interface{} { } actions = filtered } - return map[string]interface{}{ - "type": "object", - "properties": map[string]interface{}{ - "action": map[string]interface{}{ - "type": "string", - "enum": actions, - "description": "The device action to perform", - }, - "query": map[string]interface{}{ - "type": "string", - "description": "Search query for app name or package name (for search_apps)", - }, - "package_name": map[string]interface{}{ - "type": "string", - "description": "Android package name (for app_info, launch_app)", - }, - "x": map[string]interface{}{ - "type": "number", - "description": "X coordinate (for tap, swipe start)", - }, - "y": map[string]interface{}{ - "type": "number", - "description": "Y coordinate (for tap, swipe start)", - }, - "x2": map[string]interface{}{ - "type": "number", - "description": "End X coordinate (for swipe)", - }, - "y2": map[string]interface{}{ - "type": "number", - "description": "End Y coordinate (for swipe)", - }, - "duration_ms": map[string]interface{}{ - "type": "integer", - "description": "Swipe duration in milliseconds (default 300)", - }, - "text": map[string]interface{}{ - "type": "string", - "description": "Text to input (for text action)", - }, - "key": map[string]interface{}{ - "type": "string", - "enum": []string{"back", "home", "recents"}, - "description": "Key to press (for keyevent action)", - }, - "intent_action": map[string]interface{}{ - "type": "string", - "description": "Intent action string (for broadcast, intent)", - }, - "intent_data": map[string]interface{}{ - "type": "string", - "description": "Intent data URI (for intent)", - }, - "intent_package": map[string]interface{}{ - "type": "string", - "description": "Target package for intent (for intent)", - }, - "intent_type": map[string]interface{}{ - "type": "string", - "description": "MIME type for intent (for intent)", - }, - "intent_extras": map[string]interface{}{ - "type": "object", - "description": "Extra key-value pairs for broadcast/intent", - }, - "resource_id": map[string]interface{}{ - "type": "string", - "description": "View resource ID to start UI tree from (for get_ui_tree, e.g. com.example:id/button)", - }, - "index": map[string]interface{}{ - "type": "integer", - "description": "Which match to use when resource_id has multiple hits (for get_ui_tree, default 0)", - }, - "bounds_x": map[string]interface{}{ - "type": "number", - "description": "X coordinate to find the containing node (for get_ui_tree, alternative to resource_id)", - }, - "bounds_y": map[string]interface{}{ - "type": "number", - "description": "Y coordinate to find the containing node (for get_ui_tree, alternative to resource_id)", - }, - "max_depth": map[string]interface{}{ - "type": "integer", - "description": "Maximum traversal depth (for get_ui_tree, default 15)", - }, - "max_nodes": map[string]interface{}{ - "type": "integer", - "description": "Maximum number of nodes to output (for get_ui_tree, default 300)", - }, + props := map[string]interface{}{ + "action": map[string]interface{}{ + "type": "string", + "enum": actions, + "description": "The device action to perform", }, - "required": []string{"action"}, + "query": map[string]interface{}{ + "type": "string", + "description": "Search query for app name or package name (for search_apps)", + }, + "package_name": map[string]interface{}{ + "type": "string", + "description": "Android package name (for app_info, launch_app)", + }, + "intent_action": map[string]interface{}{ + "type": "string", + "description": "Intent action string (for broadcast, intent)", + }, + "intent_data": map[string]interface{}{ + "type": "string", + "description": "Intent data URI (for intent)", + }, + "intent_package": map[string]interface{}{ + "type": "string", + "description": "Target package for intent (for intent)", + }, + "intent_type": map[string]interface{}{ + "type": "string", + "description": "MIME type for intent (for intent)", + }, + "intent_extras": map[string]interface{}{ + "type": "object", + "description": "Extra key-value pairs for broadcast/intent", + }, + } + + // UI-only parameters: only expose when UI actions are available + if t.clientType != "main" { + props["x"] = map[string]interface{}{ + "type": "number", + "description": "X coordinate (for tap, swipe start)", + } + props["y"] = map[string]interface{}{ + "type": "number", + "description": "Y coordinate (for tap, swipe start)", + } + props["x2"] = map[string]interface{}{ + "type": "number", + "description": "End X coordinate (for swipe)", + } + props["y2"] = map[string]interface{}{ + "type": "number", + "description": "End Y coordinate (for swipe)", + } + props["duration_ms"] = map[string]interface{}{ + "type": "integer", + "description": "Swipe duration in milliseconds (default 300)", + } + props["text"] = map[string]interface{}{ + "type": "string", + "description": "Text to input (for text action)", + } + props["key"] = map[string]interface{}{ + "type": "string", + "enum": []string{"back", "home", "recents"}, + "description": "Key to press (for keyevent action)", + } + props["resource_id"] = map[string]interface{}{ + "type": "string", + "description": "View resource ID to start UI tree from (for get_ui_tree, e.g. com.example:id/button)", + } + props["index"] = map[string]interface{}{ + "type": "integer", + "description": "Which match to use when resource_id has multiple hits (for get_ui_tree, default 0)", + } + props["bounds_x"] = map[string]interface{}{ + "type": "number", + "description": "X coordinate to find the containing node (for get_ui_tree, alternative to resource_id)", + } + props["bounds_y"] = map[string]interface{}{ + "type": "number", + "description": "Y coordinate to find the containing node (for get_ui_tree, alternative to resource_id)", + } + props["max_depth"] = map[string]interface{}{ + "type": "integer", + "description": "Maximum traversal depth (for get_ui_tree, default 15)", + } + props["max_nodes"] = map[string]interface{}{ + "type": "integer", + "description": "Maximum number of nodes to output (for get_ui_tree, default 300)", + } + } + + return map[string]interface{}{ + "type": "object", + "properties": props, + "required": []string{"action"}, } } @@ -217,7 +223,7 @@ func (t *AndroidTool) Execute(ctx context.Context, args map[string]interface{}) // Safety guard: reject UI actions from chat-mode clients if t.clientType == "main" && uiActions[action] { - return ErrorResult(fmt.Sprintf("action %q is not available in chat mode", action)) + return ErrorResult(fmt.Sprintf("unknown action: %s", action)) } params, err := t.validateAndBuildParams(action, args)