diff --git a/internal/version/VERSION b/internal/version/VERSION index c8fab2c6..6dc66625 100644 --- a/internal/version/VERSION +++ b/internal/version/VERSION @@ -1 +1 @@ -v5.0.0-beta-2506031545 +v5.0.0-beta-2506031815 diff --git a/server/uixt.go b/server/uixt.go index 7722f540..6a81b229 100644 --- a/server/uixt.go +++ b/server/uixt.go @@ -2,7 +2,7 @@ package server import ( "github.com/gin-gonic/gin" - "github.com/httprunner/httprunner/v5/uixt" + "github.com/httprunner/httprunner/v5/uixt/option" "github.com/rs/zerolog/log" ) @@ -13,7 +13,7 @@ func (r *Router) uixtActionHandler(c *gin.Context) { return } - var req uixt.MobileAction + var req option.MobileAction if err := c.ShouldBindJSON(&req); err != nil { RenderErrorValidateRequest(c, err) return @@ -35,7 +35,7 @@ func (r *Router) uixtActionsHandler(c *gin.Context) { return } - var actions []uixt.MobileAction + var actions []option.MobileAction if err := c.ShouldBindJSON(&actions); err != nil { RenderErrorValidateRequest(c, err) return diff --git a/step.go b/step.go index b1cd9d16..a23daf9c 100644 --- a/step.go +++ b/step.go @@ -1,7 +1,7 @@ package hrp import ( - "github.com/httprunner/httprunner/v5/uixt" + "github.com/httprunner/httprunner/v5/uixt/option" "github.com/httprunner/httprunner/v5/uixt/types" ) @@ -57,10 +57,10 @@ type TStep struct { // one step contains one or multiple actions type ActionResult struct { - uixt.MobileAction `json:",inline"` - StartTime int64 `json:"start_time"` // action start time - Elapsed int64 `json:"elapsed_ms"` // action elapsed time(ms) - Error error `json:"error"` // action execution result + option.MobileAction `json:",inline"` + StartTime int64 `json:"start_time"` // action start time + Elapsed int64 `json:"elapsed_ms"` // action elapsed time(ms) + Error error `json:"error"` // action execution result } // one testcase contains one or multiple steps diff --git a/step_ui.go b/step_ui.go index 62ec4766..e235bddd 100644 --- a/step_ui.go +++ b/step_ui.go @@ -16,10 +16,10 @@ import ( ) type MobileUI struct { - OSType string `json:"os_type,omitempty" yaml:"os_type,omitempty"` // mobile device os type - Serial string `json:"serial,omitempty" yaml:"serial,omitempty"` // mobile device serial number - uixt.MobileAction `yaml:",inline"` - Actions []uixt.MobileAction `json:"actions,omitempty" yaml:"actions,omitempty"` + OSType string `json:"os_type,omitempty" yaml:"os_type,omitempty"` // mobile device os type + Serial string `json:"serial,omitempty" yaml:"serial,omitempty"` // mobile device serial number + option.MobileAction `yaml:",inline"` + Actions []option.MobileAction `json:"actions,omitempty" yaml:"actions,omitempty"` } // StepMobile implements IStep interface. @@ -69,7 +69,7 @@ func (s *StepMobile) Serial(serial string) *StepMobile { } func (s *StepMobile) Log(actionName option.ActionName) *StepMobile { - s.obj().Actions = append(s.obj().Actions, uixt.MobileAction{ + s.obj().Actions = append(s.obj().Actions, option.MobileAction{ Method: option.ACTION_LOG, Params: actionName, }) @@ -77,7 +77,7 @@ func (s *StepMobile) Log(actionName option.ActionName) *StepMobile { } func (s *StepMobile) InstallApp(path string) *StepMobile { - s.obj().Actions = append(s.obj().Actions, uixt.MobileAction{ + s.obj().Actions = append(s.obj().Actions, option.MobileAction{ Method: option.ACTION_AppInstall, Params: path, }) @@ -85,7 +85,7 @@ func (s *StepMobile) InstallApp(path string) *StepMobile { } func (s *StepMobile) WebLoginNoneUI(packageName, phoneNumber string, captcha, password string) *StepMobile { - s.obj().Actions = append(s.obj().Actions, uixt.MobileAction{ + s.obj().Actions = append(s.obj().Actions, option.MobileAction{ Method: option.ACTION_WebLoginNoneUI, Params: []string{packageName, phoneNumber, captcha, password}, }) @@ -93,7 +93,7 @@ func (s *StepMobile) WebLoginNoneUI(packageName, phoneNumber string, captcha, pa } func (s *StepMobile) AppLaunch(bundleId string) *StepMobile { - s.obj().Actions = append(s.obj().Actions, uixt.MobileAction{ + s.obj().Actions = append(s.obj().Actions, option.MobileAction{ Method: option.ACTION_AppLaunch, Params: bundleId, }) @@ -101,7 +101,7 @@ func (s *StepMobile) AppLaunch(bundleId string) *StepMobile { } func (s *StepMobile) AppTerminate(bundleId string) *StepMobile { - s.obj().Actions = append(s.obj().Actions, uixt.MobileAction{ + s.obj().Actions = append(s.obj().Actions, option.MobileAction{ Method: option.ACTION_AppTerminate, Params: bundleId, }) @@ -109,7 +109,7 @@ func (s *StepMobile) AppTerminate(bundleId string) *StepMobile { } func (s *StepMobile) Home() *StepMobile { - s.obj().Actions = append(s.obj().Actions, uixt.MobileAction{ + s.obj().Actions = append(s.obj().Actions, option.MobileAction{ Method: option.ACTION_Home, Params: nil, }) @@ -120,7 +120,7 @@ func (s *StepMobile) Home() *StepMobile { // if X<1 & Y<1, {X,Y} will be considered as percentage // else, X & Y will be considered as absolute coordinates func (s *StepMobile) TapXY(x, y float64, opts ...option.ActionOption) *StepMobile { - action := uixt.MobileAction{ + action := option.MobileAction{ Method: option.ACTION_TapXY, Params: []float64{x, y}, Options: option.NewActionOptions(opts...), @@ -132,7 +132,7 @@ func (s *StepMobile) TapXY(x, y float64, opts ...option.ActionOption) *StepMobil // TapAbsXY taps the point {X,Y}, X & Y is absolute coordinates func (s *StepMobile) TapAbsXY(x, y float64, opts ...option.ActionOption) *StepMobile { - action := uixt.MobileAction{ + action := option.MobileAction{ Method: option.ACTION_TapAbsXY, Params: []float64{x, y}, Options: option.NewActionOptions(opts...), @@ -144,7 +144,7 @@ func (s *StepMobile) TapAbsXY(x, y float64, opts ...option.ActionOption) *StepMo // TapByOCR taps on the target element by OCR recognition func (s *StepMobile) TapByOCR(ocrText string, opts ...option.ActionOption) *StepMobile { - action := uixt.MobileAction{ + action := option.MobileAction{ Method: option.ACTION_TapByOCR, Params: ocrText, Options: option.NewActionOptions(opts...), @@ -156,7 +156,7 @@ func (s *StepMobile) TapByOCR(ocrText string, opts ...option.ActionOption) *Step // TapByCV taps on the target element by CV recognition func (s *StepMobile) TapByCV(imagePath string, opts ...option.ActionOption) *StepMobile { - action := uixt.MobileAction{ + action := option.MobileAction{ Method: option.ACTION_TapByCV, Params: imagePath, Options: option.NewActionOptions(opts...), @@ -168,7 +168,7 @@ func (s *StepMobile) TapByCV(imagePath string, opts ...option.ActionOption) *Ste // TapByUITypes taps on the target element specified by uiTypes, the higher the uiTypes, the higher the priority func (s *StepMobile) TapByUITypes(opts ...option.ActionOption) *StepMobile { - action := uixt.MobileAction{ + action := option.MobileAction{ Method: option.ACTION_TapByCV, Options: option.NewActionOptions(opts...), } @@ -179,7 +179,7 @@ func (s *StepMobile) TapByUITypes(opts ...option.ActionOption) *StepMobile { // AIAction do actions with VLM func (s *StepMobile) AIAction(prompt string, opts ...option.ActionOption) *StepMobile { - action := uixt.MobileAction{ + action := option.MobileAction{ Method: option.ACTION_AIAction, Params: prompt, Options: option.NewActionOptions(opts...), @@ -191,7 +191,7 @@ func (s *StepMobile) AIAction(prompt string, opts ...option.ActionOption) *StepM // DoubleTapXY double taps the point {X,Y}, X & Y is percentage of coordinates func (s *StepMobile) DoubleTapXY(x, y float64, opts ...option.ActionOption) *StepMobile { - s.obj().Actions = append(s.obj().Actions, uixt.MobileAction{ + s.obj().Actions = append(s.obj().Actions, option.MobileAction{ Method: option.ACTION_DoubleTapXY, Params: []float64{x, y}, Options: option.NewActionOptions(opts...), @@ -200,7 +200,7 @@ func (s *StepMobile) DoubleTapXY(x, y float64, opts ...option.ActionOption) *Ste } func (s *StepMobile) Back() *StepMobile { - action := uixt.MobileAction{ + action := option.MobileAction{ Method: option.ACTION_Back, Params: nil, Options: nil, @@ -212,7 +212,7 @@ func (s *StepMobile) Back() *StepMobile { // Swipe drags from [sx, sy] to [ex, ey] func (s *StepMobile) Swipe(sx, sy, ex, ey float64, opts ...option.ActionOption) *StepMobile { - action := uixt.MobileAction{ + action := option.MobileAction{ Method: option.ACTION_SwipeCoordinate, Params: []float64{sx, sy, ex, ey}, Options: option.NewActionOptions(opts...), @@ -223,7 +223,7 @@ func (s *StepMobile) Swipe(sx, sy, ex, ey float64, opts ...option.ActionOption) } func (s *StepMobile) SwipeUp(opts ...option.ActionOption) *StepMobile { - action := uixt.MobileAction{ + action := option.MobileAction{ Method: option.ACTION_SwipeDirection, Params: "up", Options: option.NewActionOptions(opts...), @@ -234,7 +234,7 @@ func (s *StepMobile) SwipeUp(opts ...option.ActionOption) *StepMobile { } func (s *StepMobile) SwipeDown(opts ...option.ActionOption) *StepMobile { - action := uixt.MobileAction{ + action := option.MobileAction{ Method: option.ACTION_SwipeDirection, Params: "down", Options: option.NewActionOptions(opts...), @@ -245,7 +245,7 @@ func (s *StepMobile) SwipeDown(opts ...option.ActionOption) *StepMobile { } func (s *StepMobile) SwipeLeft(opts ...option.ActionOption) *StepMobile { - action := uixt.MobileAction{ + action := option.MobileAction{ Method: option.ACTION_SwipeDirection, Params: "left", Options: option.NewActionOptions(opts...), @@ -256,7 +256,7 @@ func (s *StepMobile) SwipeLeft(opts ...option.ActionOption) *StepMobile { } func (s *StepMobile) SwipeRight(opts ...option.ActionOption) *StepMobile { - action := uixt.MobileAction{ + action := option.MobileAction{ Method: option.ACTION_SwipeDirection, Params: "right", Options: option.NewActionOptions(opts...), @@ -267,7 +267,7 @@ func (s *StepMobile) SwipeRight(opts ...option.ActionOption) *StepMobile { } func (s *StepMobile) SwipeToTapApp(appName string, opts ...option.ActionOption) *StepMobile { - action := uixt.MobileAction{ + action := option.MobileAction{ Method: option.ACTION_SwipeToTapApp, Params: appName, Options: option.NewActionOptions(opts...), @@ -278,7 +278,7 @@ func (s *StepMobile) SwipeToTapApp(appName string, opts ...option.ActionOption) } func (s *StepMobile) SwipeToTapText(text string, opts ...option.ActionOption) *StepMobile { - action := uixt.MobileAction{ + action := option.MobileAction{ Method: option.ACTION_SwipeToTapText, Params: text, Options: option.NewActionOptions(opts...), @@ -289,7 +289,7 @@ func (s *StepMobile) SwipeToTapText(text string, opts ...option.ActionOption) *S } func (s *StepMobile) SwipeToTapTexts(texts interface{}, opts ...option.ActionOption) *StepMobile { - action := uixt.MobileAction{ + action := option.MobileAction{ Method: option.ACTION_SwipeToTapTexts, Params: texts, Options: option.NewActionOptions(opts...), @@ -300,7 +300,7 @@ func (s *StepMobile) SwipeToTapTexts(texts interface{}, opts ...option.ActionOpt } func (s *StepMobile) SecondaryClick(x, y float64, options ...option.ActionOption) *StepMobile { - action := uixt.MobileAction{ + action := option.MobileAction{ Method: option.ACTION_SecondaryClick, Params: []float64{x, y}, Options: option.NewActionOptions(options...), @@ -310,7 +310,7 @@ func (s *StepMobile) SecondaryClick(x, y float64, options ...option.ActionOption } func (s *StepMobile) SecondaryClickBySelector(selector string, options ...option.ActionOption) *StepMobile { - action := uixt.MobileAction{ + action := option.MobileAction{ Method: option.ACTION_SecondaryClickBySelector, Params: selector, Options: option.NewActionOptions(options...), @@ -320,7 +320,7 @@ func (s *StepMobile) SecondaryClickBySelector(selector string, options ...option } func (s *StepMobile) HoverBySelector(selector string, options ...option.ActionOption) *StepMobile { - action := uixt.MobileAction{ + action := option.MobileAction{ Method: option.ACTION_HoverBySelector, Params: selector, Options: option.NewActionOptions(options...), @@ -330,7 +330,7 @@ func (s *StepMobile) HoverBySelector(selector string, options ...option.ActionOp } func (s *StepMobile) TapBySelector(selector string, options ...option.ActionOption) *StepMobile { - action := uixt.MobileAction{ + action := option.MobileAction{ Method: option.ACTION_TapBySelector, Params: selector, Options: option.NewActionOptions(options...), @@ -340,7 +340,7 @@ func (s *StepMobile) TapBySelector(selector string, options ...option.ActionOpti } func (s *StepMobile) WebCloseTab(idx int, options ...option.ActionOption) *StepMobile { - action := uixt.MobileAction{ + action := option.MobileAction{ Method: option.ACTION_WebCloseTab, Params: idx, Options: option.NewActionOptions(options...), @@ -350,7 +350,7 @@ func (s *StepMobile) WebCloseTab(idx int, options ...option.ActionOption) *StepM } func (s *StepMobile) GetElementTextBySelector(selector string, options ...option.ActionOption) *StepMobile { - action := uixt.MobileAction{ + action := option.MobileAction{ Method: option.ACTION_GetElementTextBySelector, Params: selector, Options: option.NewActionOptions(options...), @@ -360,7 +360,7 @@ func (s *StepMobile) GetElementTextBySelector(selector string, options ...option } func (s *StepMobile) Input(text string, opts ...option.ActionOption) *StepMobile { - action := uixt.MobileAction{ + action := option.MobileAction{ Method: option.ACTION_Input, Params: text, Options: option.NewActionOptions(opts...), @@ -372,7 +372,7 @@ func (s *StepMobile) Input(text string, opts ...option.ActionOption) *StepMobile // Sleep specify sleep seconds after last action func (s *StepMobile) Sleep(nSeconds float64, startTime ...time.Time) *StepMobile { - action := uixt.MobileAction{ + action := option.MobileAction{ Method: option.ACTION_Sleep, Params: nSeconds, Options: nil, @@ -388,7 +388,7 @@ func (s *StepMobile) Sleep(nSeconds float64, startTime ...time.Time) *StepMobile } func (s *StepMobile) SleepMS(nMilliseconds int64, startTime ...time.Time) *StepMobile { - action := uixt.MobileAction{ + action := option.MobileAction{ Method: option.ACTION_SleepMS, Params: nMilliseconds, Options: nil, @@ -408,7 +408,7 @@ func (s *StepMobile) SleepMS(nMilliseconds int64, startTime ...time.Time) *StepM // 1. [min, max] : min and max are float64 time range boundaries // 2. [min1, max1, weight1, min2, max2, weight2, ...] : weight is the probability of the time range func (s *StepMobile) SleepRandom(params ...float64) *StepMobile { - s.obj().Actions = append(s.obj().Actions, uixt.MobileAction{ + s.obj().Actions = append(s.obj().Actions, option.MobileAction{ Method: option.ACTION_SleepRandom, Params: params, Options: nil, @@ -417,7 +417,7 @@ func (s *StepMobile) SleepRandom(params ...float64) *StepMobile { } func (s *StepMobile) EndToEndDelay(opts ...option.ActionOption) *StepMobile { - s.obj().Actions = append(s.obj().Actions, uixt.MobileAction{ + s.obj().Actions = append(s.obj().Actions, option.MobileAction{ Method: option.ACTION_EndToEndDelay, Params: nil, Options: option.NewActionOptions(opts...), @@ -426,7 +426,7 @@ func (s *StepMobile) EndToEndDelay(opts ...option.ActionOption) *StepMobile { } func (s *StepMobile) ScreenShot(opts ...option.ActionOption) *StepMobile { - s.obj().Actions = append(s.obj().Actions, uixt.MobileAction{ + s.obj().Actions = append(s.obj().Actions, option.MobileAction{ Method: option.ACTION_ScreenShot, Params: nil, Options: option.NewActionOptions(opts...), @@ -440,7 +440,7 @@ func (s *StepMobile) DisableAutoPopupHandler() *StepMobile { } func (s *StepMobile) ClosePopups(opts ...option.ActionOption) *StepMobile { - s.obj().Actions = append(s.obj().Actions, uixt.MobileAction{ + s.obj().Actions = append(s.obj().Actions, option.MobileAction{ Method: option.ACTION_ClosePopups, Params: nil, Options: option.NewActionOptions(opts...), @@ -449,7 +449,7 @@ func (s *StepMobile) ClosePopups(opts ...option.ActionOption) *StepMobile { } func (s *StepMobile) Call(name string, fn func(), opts ...option.ActionOption) *StepMobile { - s.obj().Actions = append(s.obj().Actions, uixt.MobileAction{ + s.obj().Actions = append(s.obj().Actions, option.MobileAction{ Method: option.ACTION_CallFunction, Params: name, // function description Fn: fn, @@ -743,7 +743,7 @@ func runStepMobileUI(s *SessionRunner, step IStep) (stepResult *StepResult, err // save foreground app startTime := time.Now() actionResult := &ActionResult{ - MobileAction: uixt.MobileAction{ + MobileAction: option.MobileAction{ Method: option.ACTION_GetForegroundApp, Params: "[ForDebug] check foreground app", }, @@ -766,7 +766,7 @@ func runStepMobileUI(s *SessionRunner, step IStep) (stepResult *StepResult, err if !ignorePopup && (config == nil || !config.IgnorePopup) && uiDriver != nil { startTime := time.Now() actionResult := &ActionResult{ - MobileAction: uixt.MobileAction{ + MobileAction: option.MobileAction{ Method: option.ACTION_ClosePopups, Params: "[ForDebug] close popups handler", }, diff --git a/uixt/driver_action.go b/uixt/driver_action.go deleted file mode 100644 index a313e06d..00000000 --- a/uixt/driver_action.go +++ /dev/null @@ -1,23 +0,0 @@ -package uixt - -import ( - "github.com/httprunner/httprunner/v5/uixt/option" -) - -type MobileAction struct { - Method option.ActionName `json:"method,omitempty" yaml:"method,omitempty"` - Params interface{} `json:"params,omitempty" yaml:"params,omitempty"` - Fn func() `json:"-" yaml:"-"` // used for function action, not serialized - Options *option.ActionOptions `json:"options,omitempty" yaml:"options,omitempty"` - option.ActionOptions -} - -func (ma MobileAction) GetOptions() []option.ActionOption { - var actionOptionList []option.ActionOption - // Notice: merge options from ma.Options and ma.ActionOptions - if ma.Options != nil { - actionOptionList = append(actionOptionList, ma.Options.Options()...) - } - actionOptionList = append(actionOptionList, ma.ActionOptions.Options()...) - return actionOptionList -} diff --git a/uixt/mcp_server.go b/uixt/mcp_server.go index ce234373..908ec1fd 100644 --- a/uixt/mcp_server.go +++ b/uixt/mcp_server.go @@ -142,61 +142,13 @@ func (s *MCPServer4XTDriver) registerTool(tool ActionTool) { } // ActionTool interface defines the contract for MCP tools -// -// This interface standardizes how UI automation actions are exposed through MCP protocol. -// Each tool implementation must provide: -// -// 1. Identity and Documentation: -// - Name(): Unique identifier for the action (e.g., ACTION_TapXY) -// - Description(): Human-readable description for AI models -// -// 2. MCP Integration: -// - Options(): Parameter definitions with validation rules -// - Implement(): Actual execution logic as MCP handler -// -// 3. Legacy Compatibility: -// - ConvertActionToCallToolRequest(): Converts old MobileAction format -// -// Implementation Pattern: -// -// type ToolExample struct{} -// -// func (t *ToolExample) Name() option.ActionName { -// return option.ACTION_Example -// } -// -// func (t *ToolExample) Description() string { -// return "Performs example operation" -// } -// -// func (t *ToolExample) Options() []mcp.ToolOption { -// return []mcp.ToolOption{ -// mcp.WithString("param", mcp.Description("Parameter description")), -// } -// } -// -// func (t *ToolExample) Implement() server.ToolHandlerFunc { -// return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { -// // 1. Setup driver -// // 2. Parse parameters -// // 3. Execute operation -// // 4. Return result -// } -// } -// -// Benefits of this architecture: -// - Complete decoupling between tools -// - Consistent parameter handling -// - Standardized error reporting -// - Easy testing and maintenance -// - Seamless MCP protocol integration type ActionTool interface { Name() option.ActionName Description() string Options() []mcp.ToolOption Implement() server.ToolHandlerFunc // ConvertActionToCallToolRequest converts MobileAction to mcp.CallToolRequest - ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) + ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) // ReturnSchema returns the expected return value schema based on mcp.CallToolResult conventions ReturnSchema() map[string]string } @@ -272,43 +224,6 @@ func getFloat64ValueOrDefault(value float64, defaultValue float64) float64 { } // parseActionOptions converts MCP request arguments to ActionOptions struct -// -// This function provides unified parameter parsing for all MCP tools by: -// -// 1. Converting map[string]any arguments to JSON bytes -// 2. Unmarshaling JSON into strongly-typed ActionOptions struct -// 3. Providing automatic validation and type conversion -// -// The ActionOptions struct contains all possible parameters for UI operations: -// - Coordinates: X, Y, FromX, FromY, ToX, ToY -// - Text/Content: Text, Content, AppName, PackageName -// - Timing: Duration, PressDuration, Milliseconds -// - Behavior: AntiRisk, IgnoreNotFoundError, Regex -// - Indices: Index, MaxRetryTimes, TabIndex -// - Device: Platform, Serial, Button, Direction -// - Web: Selector, PhoneNumber, Captcha, Password -// - AI: Prompt -// - Collections: Texts, Params, Points -// -// Parameters: -// - arguments: Raw MCP request arguments as map[string]any -// -// Returns: -// - *option.ActionOptions: Parsed and validated options struct -// - error: Parsing or validation error -// -// Usage: -// -// unifiedReq, err := parseActionOptions(request.Params.Arguments) -// if err != nil { -// return nil, err -// } -// // Use unifiedReq.X, unifiedReq.Y, etc. -// -// Error Handling: -// - JSON marshal errors (invalid argument types) -// - JSON unmarshal errors (type conversion failures) -// - Missing required fields (handled by individual tools) func parseActionOptions(arguments map[string]any) (*option.ActionOptions, error) { b, err := json.Marshal(arguments) if err != nil { diff --git a/uixt/mcp_server_test.go b/uixt/mcp_server_test.go index ff785b12..b6378533 100644 --- a/uixt/mcp_server_test.go +++ b/uixt/mcp_server_test.go @@ -140,7 +140,7 @@ func TestIgnoreNotFoundErrorOption(t *testing.T) { option.WithRegex(true), option.WithTapRandomRect(true), ) - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_TapByOCR, Params: "test_text", ActionOptions: *actionOptions, @@ -201,7 +201,7 @@ func TestToolListAvailableDevices(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_ListAvailableDevices, Params: nil, } @@ -227,7 +227,7 @@ func TestToolSelectDevice(t *testing.T) { assert.Len(t, options, 2) // platform and serial // Test ConvertActionToCallToolRequest - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_SelectDevice, Params: nil, } @@ -251,7 +251,7 @@ func TestToolTapXY(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest with valid params - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_TapXY, Params: []float64{0.5, 0.6}, ActionOptions: option.ActionOptions{ @@ -266,7 +266,7 @@ func TestToolTapXY(t *testing.T) { assert.Equal(t, 1.5, request.Params.Arguments["duration"]) // Test ConvertActionToCallToolRequest with invalid params - invalidAction := MobileAction{ + invalidAction := option.MobileAction{ Method: option.ACTION_TapXY, Params: "invalid", } @@ -289,7 +289,7 @@ func TestToolTapAbsXY(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest with valid params - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_TapAbsXY, Params: []float64{100.0, 200.0}, ActionOptions: option.ActionOptions{ @@ -304,7 +304,7 @@ func TestToolTapAbsXY(t *testing.T) { assert.Equal(t, 2.0, request.Params.Arguments["duration"]) // Test ConvertActionToCallToolRequest with invalid params - invalidAction := MobileAction{ + invalidAction := option.MobileAction{ Method: option.ACTION_TapAbsXY, Params: []float64{100.0}, // missing y coordinate } @@ -334,7 +334,7 @@ func TestToolTapByOCR(t *testing.T) { option.WithRegex(true), option.WithTapRandomRect(true), ) - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_TapByOCR, Params: "test_text", ActionOptions: *actionOptions, @@ -350,7 +350,7 @@ func TestToolTapByOCR(t *testing.T) { assert.Equal(t, true, request.Params.Arguments["tap_random_rect"]) // Test ConvertActionToCallToolRequest with invalid params - invalidAction := MobileAction{ + invalidAction := option.MobileAction{ Method: option.ACTION_TapByOCR, Params: 123, // should be string } @@ -378,7 +378,7 @@ func TestToolTapByCV(t *testing.T) { option.WithMaxRetryTimes(2), option.WithTapRandomRect(true), ) - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_TapByCV, Params: nil, ActionOptions: *actionOptions, @@ -407,7 +407,7 @@ func TestToolDoubleTapXY(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest with valid params - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_DoubleTapXY, Params: []float64{0.3, 0.7}, } @@ -418,7 +418,7 @@ func TestToolDoubleTapXY(t *testing.T) { assert.Equal(t, 0.7, request.Params.Arguments["y"]) // Test ConvertActionToCallToolRequest with invalid params - invalidAction := MobileAction{ + invalidAction := option.MobileAction{ Method: option.ACTION_DoubleTapXY, Params: "invalid", } @@ -441,7 +441,7 @@ func TestToolSwipe(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest with direction params (string) - directionAction := MobileAction{ + directionAction := option.MobileAction{ Method: option.ACTION_Swipe, Params: "up", ActionOptions: option.ActionOptions{ @@ -457,7 +457,7 @@ func TestToolSwipe(t *testing.T) { assert.Equal(t, 0.5, request.Params.Arguments["pressDuration"]) // Test ConvertActionToCallToolRequest with coordinate params - coordinateAction := MobileAction{ + coordinateAction := option.MobileAction{ Method: option.ACTION_Swipe, Params: []float64{0.1, 0.2, 0.8, 0.9}, ActionOptions: option.ActionOptions{ @@ -476,7 +476,7 @@ func TestToolSwipe(t *testing.T) { assert.Equal(t, 1.0, request.Params.Arguments["pressDuration"]) // Test ConvertActionToCallToolRequest with invalid params - invalidAction := MobileAction{ + invalidAction := option.MobileAction{ Method: option.ACTION_Swipe, Params: 123, // should be string or []float64 } @@ -484,7 +484,7 @@ func TestToolSwipe(t *testing.T) { assert.Error(t, err) // Test ConvertActionToCallToolRequest with incomplete coordinate params - incompleteAction := MobileAction{ + incompleteAction := option.MobileAction{ Method: option.ACTION_Swipe, Params: []float64{0.1, 0.2}, // missing toX and toY } @@ -507,7 +507,7 @@ func TestToolSwipeDirection(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest with valid params - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_SwipeDirection, Params: "up", ActionOptions: option.ActionOptions{ @@ -523,7 +523,7 @@ func TestToolSwipeDirection(t *testing.T) { assert.Equal(t, 0.5, request.Params.Arguments["pressDuration"]) // Test ConvertActionToCallToolRequest with invalid params - invalidAction := MobileAction{ + invalidAction := option.MobileAction{ Method: option.ACTION_SwipeDirection, Params: 123, // should be string } @@ -546,7 +546,7 @@ func TestToolSwipeCoordinate(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest with valid params - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_SwipeCoordinate, Params: []float64{0.1, 0.2, 0.8, 0.9}, ActionOptions: option.ActionOptions{ @@ -565,7 +565,7 @@ func TestToolSwipeCoordinate(t *testing.T) { assert.Equal(t, 1.0, request.Params.Arguments["pressDuration"]) // Test ConvertActionToCallToolRequest with invalid params - invalidAction := MobileAction{ + invalidAction := option.MobileAction{ Method: option.ACTION_SwipeCoordinate, Params: []float64{0.1, 0.2}, // missing toX and toY } @@ -593,7 +593,7 @@ func TestToolSwipeToTapApp(t *testing.T) { option.WithMaxRetryTimes(3), option.WithIndex(1), ) - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_SwipeToTapApp, Params: "WeChat", ActionOptions: *actionOptions, @@ -607,7 +607,7 @@ func TestToolSwipeToTapApp(t *testing.T) { assert.Equal(t, 1, request.Params.Arguments["index"]) // Test ConvertActionToCallToolRequest with invalid params - invalidAction := MobileAction{ + invalidAction := option.MobileAction{ Method: option.ACTION_SwipeToTapApp, Params: 123, // should be string } @@ -635,7 +635,7 @@ func TestToolSwipeToTapText(t *testing.T) { option.WithMaxRetryTimes(2), option.WithRegex(true), ) - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_SwipeToTapText, Params: "Submit", ActionOptions: *actionOptions, @@ -649,7 +649,7 @@ func TestToolSwipeToTapText(t *testing.T) { assert.Equal(t, true, request.Params.Arguments["regex"]) // Test ConvertActionToCallToolRequest with invalid params - invalidAction := MobileAction{ + invalidAction := option.MobileAction{ Method: option.ACTION_SwipeToTapText, Params: []int{1, 2, 3}, // should be string } @@ -676,7 +676,7 @@ func TestToolSwipeToTapTexts(t *testing.T) { option.WithIgnoreNotFoundError(true), option.WithRegex(true), ) - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_SwipeToTapTexts, Params: []string{"OK", "确定", "Submit"}, ActionOptions: *actionOptions, @@ -692,7 +692,7 @@ func TestToolSwipeToTapTexts(t *testing.T) { assert.Equal(t, true, request.Params.Arguments["regex"]) // Test ConvertActionToCallToolRequest with invalid params - invalidAction := MobileAction{ + invalidAction := option.MobileAction{ Method: option.ACTION_SwipeToTapTexts, Params: "single_string", // should be []string } @@ -715,7 +715,7 @@ func TestToolDrag(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest with valid params - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_Drag, Params: []float64{0.1, 0.2, 0.8, 0.9}, ActionOptions: option.ActionOptions{ @@ -732,7 +732,7 @@ func TestToolDrag(t *testing.T) { assert.Equal(t, 2500.0, request.Params.Arguments["duration"]) // converted to milliseconds // Test ConvertActionToCallToolRequest with invalid params - invalidAction := MobileAction{ + invalidAction := option.MobileAction{ Method: option.ACTION_Drag, Params: []float64{0.1, 0.2}, // missing toX and toY } @@ -755,7 +755,7 @@ func TestToolInput(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest with valid params - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_Input, Params: "Hello World", } @@ -780,7 +780,7 @@ func TestToolScreenShot(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_ScreenShot, Params: nil, } @@ -805,7 +805,7 @@ func TestToolGetScreenSize(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_GetScreenSize, Params: nil, } @@ -830,7 +830,7 @@ func TestToolPressButton(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest with valid params - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_PressButton, Params: "HOME", } @@ -840,7 +840,7 @@ func TestToolPressButton(t *testing.T) { assert.Equal(t, "HOME", request.Params.Arguments["button"]) // Test ConvertActionToCallToolRequest with invalid params - invalidAction := MobileAction{ + invalidAction := option.MobileAction{ Method: option.ACTION_PressButton, Params: 123, // should be string } @@ -863,7 +863,7 @@ func TestToolHome(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_Home, Params: nil, } @@ -888,7 +888,7 @@ func TestToolBack(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_Back, Params: nil, } @@ -913,7 +913,7 @@ func TestToolListPackages(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_ListPackages, Params: nil, } @@ -938,7 +938,7 @@ func TestToolLaunchApp(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest with valid params - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_AppLaunch, Params: "com.example.app", } @@ -948,7 +948,7 @@ func TestToolLaunchApp(t *testing.T) { assert.Equal(t, "com.example.app", request.Params.Arguments["packageName"]) // Test ConvertActionToCallToolRequest with invalid params - invalidAction := MobileAction{ + invalidAction := option.MobileAction{ Method: option.ACTION_AppLaunch, Params: 123, // should be string } @@ -971,7 +971,7 @@ func TestToolTerminateApp(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest with valid params - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_AppTerminate, Params: "com.example.app", } @@ -981,7 +981,7 @@ func TestToolTerminateApp(t *testing.T) { assert.Equal(t, "com.example.app", request.Params.Arguments["packageName"]) // Test ConvertActionToCallToolRequest with invalid params - invalidAction := MobileAction{ + invalidAction := option.MobileAction{ Method: option.ACTION_AppTerminate, Params: []int{1, 2, 3}, // should be string } @@ -1004,7 +1004,7 @@ func TestToolAppInstall(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest with valid params - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_AppInstall, Params: "https://example.com/app.apk", } @@ -1014,7 +1014,7 @@ func TestToolAppInstall(t *testing.T) { assert.Equal(t, "https://example.com/app.apk", request.Params.Arguments["appUrl"]) // Test ConvertActionToCallToolRequest with invalid params - invalidAction := MobileAction{ + invalidAction := option.MobileAction{ Method: option.ACTION_AppInstall, Params: 123, // should be string } @@ -1037,7 +1037,7 @@ func TestToolAppUninstall(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest with valid params - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_AppUninstall, Params: "com.example.app", } @@ -1047,7 +1047,7 @@ func TestToolAppUninstall(t *testing.T) { assert.Equal(t, "com.example.app", request.Params.Arguments["packageName"]) // Test ConvertActionToCallToolRequest with invalid params - invalidAction := MobileAction{ + invalidAction := option.MobileAction{ Method: option.ACTION_AppUninstall, Params: 123, // should be string } @@ -1070,7 +1070,7 @@ func TestToolAppClear(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest with valid params - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_AppClear, Params: "com.example.app", } @@ -1080,7 +1080,7 @@ func TestToolAppClear(t *testing.T) { assert.Equal(t, "com.example.app", request.Params.Arguments["packageName"]) // Test ConvertActionToCallToolRequest with invalid params - invalidAction := MobileAction{ + invalidAction := option.MobileAction{ Method: option.ACTION_AppClear, Params: 123, // should be string } @@ -1103,7 +1103,7 @@ func TestToolSleep(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest with valid params - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_Sleep, Params: 2.5, } @@ -1128,7 +1128,7 @@ func TestToolSleepMS(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest with valid params - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_SleepMS, Params: int64(1500), } @@ -1138,7 +1138,7 @@ func TestToolSleepMS(t *testing.T) { assert.Equal(t, int64(1500), request.Params.Arguments["milliseconds"]) // Test ConvertActionToCallToolRequest with invalid params - invalidAction := MobileAction{ + invalidAction := option.MobileAction{ Method: option.ACTION_SleepMS, Params: "invalid", // should be int64 } @@ -1161,7 +1161,7 @@ func TestToolSleepRandom(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest with valid params - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_SleepRandom, Params: []float64{1.0, 3.0}, } @@ -1174,7 +1174,7 @@ func TestToolSleepRandom(t *testing.T) { assert.Equal(t, []float64{1.0, 3.0}, params) // Test ConvertActionToCallToolRequest with invalid params - invalidAction := MobileAction{ + invalidAction := option.MobileAction{ Method: option.ACTION_SleepRandom, Params: "invalid", // should be []float64 } @@ -1197,7 +1197,7 @@ func TestToolSetIme(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest with valid params - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_SetIme, Params: "com.google.android.inputmethod.latin", } @@ -1207,7 +1207,7 @@ func TestToolSetIme(t *testing.T) { assert.Equal(t, "com.google.android.inputmethod.latin", request.Params.Arguments["ime"]) // Test ConvertActionToCallToolRequest with invalid params - invalidAction := MobileAction{ + invalidAction := option.MobileAction{ Method: option.ACTION_SetIme, Params: 123, // should be string } @@ -1230,7 +1230,7 @@ func TestToolGetSource(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest with valid params - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_GetSource, Params: "com.example.app", } @@ -1240,7 +1240,7 @@ func TestToolGetSource(t *testing.T) { assert.Equal(t, "com.example.app", request.Params.Arguments["packageName"]) // Test ConvertActionToCallToolRequest with invalid params - invalidAction := MobileAction{ + invalidAction := option.MobileAction{ Method: option.ACTION_GetSource, Params: 123, // should be string } @@ -1263,7 +1263,7 @@ func TestToolClosePopups(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_ClosePopups, Params: nil, } @@ -1288,7 +1288,7 @@ func TestToolAIAction(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest with valid params - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_AIAction, Params: "Click on the login button", } @@ -1298,7 +1298,7 @@ func TestToolAIAction(t *testing.T) { assert.Equal(t, "Click on the login button", request.Params.Arguments["prompt"]) // Test ConvertActionToCallToolRequest with invalid params - invalidAction := MobileAction{ + invalidAction := option.MobileAction{ Method: option.ACTION_AIAction, Params: 123, // should be string } @@ -1321,7 +1321,7 @@ func TestToolFinished(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest with valid params - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_Finished, Params: "Task completed successfully", } @@ -1331,7 +1331,7 @@ func TestToolFinished(t *testing.T) { assert.Equal(t, "Task completed successfully", request.Params.Arguments["content"]) // Test ConvertActionToCallToolRequest with invalid params - invalidAction := MobileAction{ + invalidAction := option.MobileAction{ Method: option.ACTION_Finished, Params: 123, // should be string } @@ -1354,7 +1354,7 @@ func TestToolWebLoginNoneUI(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_WebLoginNoneUI, Params: nil, } @@ -1379,7 +1379,7 @@ func TestToolSecondaryClick(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest with valid params - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_SecondaryClick, Params: []float64{0.5, 0.6}, } @@ -1390,7 +1390,7 @@ func TestToolSecondaryClick(t *testing.T) { assert.Equal(t, 0.6, request.Params.Arguments["y"]) // Test ConvertActionToCallToolRequest with invalid params - invalidAction := MobileAction{ + invalidAction := option.MobileAction{ Method: option.ACTION_SecondaryClick, Params: "invalid", // should be []float64 } @@ -1413,7 +1413,7 @@ func TestToolHoverBySelector(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest with valid params - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_HoverBySelector, Params: "#login-button", } @@ -1423,7 +1423,7 @@ func TestToolHoverBySelector(t *testing.T) { assert.Equal(t, "#login-button", request.Params.Arguments["selector"]) // Test ConvertActionToCallToolRequest with invalid params - invalidAction := MobileAction{ + invalidAction := option.MobileAction{ Method: option.ACTION_HoverBySelector, Params: 123, // should be string } @@ -1446,7 +1446,7 @@ func TestToolTapBySelector(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest with valid params - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_TapBySelector, Params: "//button[@id='submit']", } @@ -1456,7 +1456,7 @@ func TestToolTapBySelector(t *testing.T) { assert.Equal(t, "//button[@id='submit']", request.Params.Arguments["selector"]) // Test ConvertActionToCallToolRequest with invalid params - invalidAction := MobileAction{ + invalidAction := option.MobileAction{ Method: option.ACTION_TapBySelector, Params: 123, // should be string } @@ -1479,7 +1479,7 @@ func TestToolSecondaryClickBySelector(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest with valid params - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_SecondaryClickBySelector, Params: ".context-menu-trigger", } @@ -1489,7 +1489,7 @@ func TestToolSecondaryClickBySelector(t *testing.T) { assert.Equal(t, ".context-menu-trigger", request.Params.Arguments["selector"]) // Test ConvertActionToCallToolRequest with invalid params - invalidAction := MobileAction{ + invalidAction := option.MobileAction{ Method: option.ACTION_SecondaryClickBySelector, Params: 123, // should be string } @@ -1512,7 +1512,7 @@ func TestToolWebCloseTab(t *testing.T) { assert.NotNil(t, options) // Test ConvertActionToCallToolRequest with valid params - action := MobileAction{ + action := option.MobileAction{ Method: option.ACTION_WebCloseTab, Params: 1, } @@ -1522,7 +1522,7 @@ func TestToolWebCloseTab(t *testing.T) { assert.Equal(t, 1, request.Params.Arguments["tabIndex"]) // Test ConvertActionToCallToolRequest with invalid params - invalidAction := MobileAction{ + invalidAction := option.MobileAction{ Method: option.ACTION_WebCloseTab, Params: "invalid", // should be int } @@ -1539,7 +1539,7 @@ func TestPreMarkOperationConfiguration(t *testing.T) { assert.NotNil(t, tapTool) // Test conversion with pre_mark_operation enabled - actionWithPreMark := MobileAction{ + actionWithPreMark := option.MobileAction{ Method: option.ACTION_TapXY, Params: []float64{0.5, 0.5}, ActionOptions: *option.NewActionOptions(option.WithPreMarkOperation(true)), @@ -1550,7 +1550,7 @@ func TestPreMarkOperationConfiguration(t *testing.T) { assert.Equal(t, true, request.Params.Arguments["pre_mark_operation"]) // Test conversion without pre_mark_operation - actionWithoutPreMark := MobileAction{ + actionWithoutPreMark := option.MobileAction{ Method: option.ACTION_TapXY, Params: []float64{0.5, 0.5}, ActionOptions: *option.NewActionOptions(option.WithPreMarkOperation(false)), diff --git a/uixt/mcp_tools_ai.go b/uixt/mcp_tools_ai.go index bb8f0481..1c6bcdd2 100644 --- a/uixt/mcp_tools_ai.go +++ b/uixt/mcp_tools_ai.go @@ -49,7 +49,7 @@ func (t *ToolAIAction) Implement() server.ToolHandlerFunc { } } -func (t *ToolAIAction) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolAIAction) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { if prompt, ok := action.Params.(string); ok { arguments := map[string]any{ "prompt": prompt, @@ -95,7 +95,7 @@ func (t *ToolFinished) Implement() server.ToolHandlerFunc { } } -func (t *ToolFinished) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolFinished) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { if reason, ok := action.Params.(string); ok { arguments := map[string]any{ "content": reason, diff --git a/uixt/mcp_tools_app.go b/uixt/mcp_tools_app.go index ef09b6fc..874e4ca0 100644 --- a/uixt/mcp_tools_app.go +++ b/uixt/mcp_tools_app.go @@ -41,7 +41,7 @@ func (t *ToolListPackages) Implement() server.ToolHandlerFunc { } } -func (t *ToolListPackages) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolListPackages) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { return buildMCPCallToolRequest(t.Name(), map[string]any{}), nil } @@ -94,7 +94,7 @@ func (t *ToolLaunchApp) Implement() server.ToolHandlerFunc { } } -func (t *ToolLaunchApp) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolLaunchApp) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { if packageName, ok := action.Params.(string); ok { arguments := map[string]any{ "packageName": packageName, @@ -154,7 +154,7 @@ func (t *ToolTerminateApp) Implement() server.ToolHandlerFunc { } } -func (t *ToolTerminateApp) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolTerminateApp) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { if packageName, ok := action.Params.(string); ok { arguments := map[string]any{ "packageName": packageName, @@ -207,7 +207,7 @@ func (t *ToolAppInstall) Implement() server.ToolHandlerFunc { } } -func (t *ToolAppInstall) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolAppInstall) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { if appUrl, ok := action.Params.(string); ok { arguments := map[string]any{ "appUrl": appUrl, @@ -263,7 +263,7 @@ func (t *ToolAppUninstall) Implement() server.ToolHandlerFunc { } } -func (t *ToolAppUninstall) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolAppUninstall) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { if packageName, ok := action.Params.(string); ok { arguments := map[string]any{ "packageName": packageName, @@ -319,7 +319,7 @@ func (t *ToolAppClear) Implement() server.ToolHandlerFunc { } } -func (t *ToolAppClear) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolAppClear) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { if packageName, ok := action.Params.(string); ok { arguments := map[string]any{ "packageName": packageName, diff --git a/uixt/mcp_tools_button.go b/uixt/mcp_tools_button.go index 814612d4..637a29ed 100644 --- a/uixt/mcp_tools_button.go +++ b/uixt/mcp_tools_button.go @@ -50,7 +50,7 @@ func (t *ToolPressButton) Implement() server.ToolHandlerFunc { } } -func (t *ToolPressButton) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolPressButton) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { if button, ok := action.Params.(string); ok { arguments := map[string]any{ "button": button, @@ -101,7 +101,7 @@ func (t *ToolHome) Implement() server.ToolHandlerFunc { } } -func (t *ToolHome) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolHome) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { return buildMCPCallToolRequest(t.Name(), map[string]any{}), nil } @@ -145,7 +145,7 @@ func (t *ToolBack) Implement() server.ToolHandlerFunc { } } -func (t *ToolBack) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolBack) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { return buildMCPCallToolRequest(t.Name(), map[string]any{}), nil } diff --git a/uixt/mcp_tools_device.go b/uixt/mcp_tools_device.go index d840a3c0..2816cd21 100644 --- a/uixt/mcp_tools_device.go +++ b/uixt/mcp_tools_device.go @@ -64,7 +64,7 @@ func (t *ToolListAvailableDevices) Implement() server.ToolHandlerFunc { } } -func (t *ToolListAvailableDevices) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolListAvailableDevices) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { return buildMCPCallToolRequest(t.Name(), map[string]any{}), nil } @@ -105,7 +105,7 @@ func (t *ToolSelectDevice) Implement() server.ToolHandlerFunc { } } -func (t *ToolSelectDevice) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolSelectDevice) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { return buildMCPCallToolRequest(t.Name(), map[string]any{}), nil } diff --git a/uixt/mcp_tools_input.go b/uixt/mcp_tools_input.go index 723ebb2d..e0ef3b0a 100644 --- a/uixt/mcp_tools_input.go +++ b/uixt/mcp_tools_input.go @@ -53,7 +53,7 @@ func (t *ToolInput) Implement() server.ToolHandlerFunc { } } -func (t *ToolInput) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolInput) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { text := fmt.Sprintf("%v", action.Params) arguments := map[string]any{ "text": text, @@ -107,7 +107,7 @@ func (t *ToolSetIme) Implement() server.ToolHandlerFunc { } } -func (t *ToolSetIme) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolSetIme) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { if ime, ok := action.Params.(string); ok { arguments := map[string]any{ "ime": ime, diff --git a/uixt/mcp_tools_screen.go b/uixt/mcp_tools_screen.go index 8170ee33..61fa5055 100644 --- a/uixt/mcp_tools_screen.go +++ b/uixt/mcp_tools_screen.go @@ -43,7 +43,7 @@ func (t *ToolScreenShot) Implement() server.ToolHandlerFunc { } } -func (t *ToolScreenShot) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolScreenShot) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { return buildMCPCallToolRequest(t.Name(), map[string]any{}), nil } @@ -88,7 +88,7 @@ func (t *ToolGetScreenSize) Implement() server.ToolHandlerFunc { } } -func (t *ToolGetScreenSize) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolGetScreenSize) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { return buildMCPCallToolRequest(t.Name(), map[string]any{}), nil } @@ -139,7 +139,7 @@ func (t *ToolGetSource) Implement() server.ToolHandlerFunc { } } -func (t *ToolGetSource) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolGetSource) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { if packageName, ok := action.Params.(string); ok { arguments := map[string]any{ "packageName": packageName, diff --git a/uixt/mcp_tools_swipe.go b/uixt/mcp_tools_swipe.go index 7b0431e8..b90e7419 100644 --- a/uixt/mcp_tools_swipe.go +++ b/uixt/mcp_tools_swipe.go @@ -45,7 +45,7 @@ func (t *ToolSwipe) Implement() server.ToolHandlerFunc { } } -func (t *ToolSwipe) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolSwipe) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { // Check if params is a string (direction-based swipe) if _, ok := action.Params.(string); ok { // Delegate to ToolSwipeDirection but use our tool name @@ -159,7 +159,7 @@ func (t *ToolSwipeDirection) Implement() server.ToolHandlerFunc { } } -func (t *ToolSwipeDirection) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolSwipeDirection) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { // Handle direction swipe like "up", "down", "left", "right" if direction, ok := action.Params.(string); ok { arguments := map[string]any{ @@ -252,7 +252,7 @@ func (t *ToolSwipeCoordinate) Implement() server.ToolHandlerFunc { } } -func (t *ToolSwipeCoordinate) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolSwipeCoordinate) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { if paramSlice, err := builtin.ConvertToFloat64Slice(action.Params); err == nil && len(paramSlice) == 4 { arguments := map[string]any{ "from_x": paramSlice[0], @@ -341,7 +341,7 @@ func (t *ToolSwipeToTapApp) Implement() server.ToolHandlerFunc { } } -func (t *ToolSwipeToTapApp) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolSwipeToTapApp) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { if appName, ok := action.Params.(string); ok { arguments := map[string]any{ "appName": appName, @@ -420,7 +420,7 @@ func (t *ToolSwipeToTapText) Implement() server.ToolHandlerFunc { } } -func (t *ToolSwipeToTapText) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolSwipeToTapText) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { if text, ok := action.Params.(string); ok { arguments := map[string]any{ "text": text, @@ -499,7 +499,7 @@ func (t *ToolSwipeToTapTexts) Implement() server.ToolHandlerFunc { } } -func (t *ToolSwipeToTapTexts) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolSwipeToTapTexts) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { var texts []string if textsSlice, ok := action.Params.([]string); ok { texts = textsSlice @@ -587,7 +587,7 @@ func (t *ToolDrag) Implement() server.ToolHandlerFunc { } } -func (t *ToolDrag) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolDrag) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { if paramSlice, err := builtin.ConvertToFloat64Slice(action.Params); err == nil && len(paramSlice) == 4 { arguments := map[string]any{ "from_x": paramSlice[0], diff --git a/uixt/mcp_tools_touch.go b/uixt/mcp_tools_touch.go index 9b48cfdd..12bcd1f1 100644 --- a/uixt/mcp_tools_touch.go +++ b/uixt/mcp_tools_touch.go @@ -64,7 +64,7 @@ func (t *ToolTapXY) Implement() server.ToolHandlerFunc { } } -func (t *ToolTapXY) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolTapXY) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { if params, err := builtin.ConvertToFloat64Slice(action.Params); err == nil && len(params) == 2 { x, y := params[0], params[1] arguments := map[string]any{ @@ -148,7 +148,7 @@ func (t *ToolTapAbsXY) Implement() server.ToolHandlerFunc { } } -func (t *ToolTapAbsXY) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolTapAbsXY) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { if params, err := builtin.ConvertToFloat64Slice(action.Params); err == nil && len(params) == 2 { x, y := params[0], params[1] arguments := map[string]any{ @@ -233,7 +233,7 @@ func (t *ToolTapByOCR) Implement() server.ToolHandlerFunc { } } -func (t *ToolTapByOCR) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolTapByOCR) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { if text, ok := action.Params.(string); ok { arguments := map[string]any{ "text": text, @@ -304,7 +304,7 @@ func (t *ToolTapByCV) Implement() server.ToolHandlerFunc { } } -func (t *ToolTapByCV) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolTapByCV) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { // For TapByCV, the original action might not have params but relies on options arguments := map[string]any{ "imagePath": "", // Will be handled by the tool based on UI types @@ -364,7 +364,7 @@ func (t *ToolDoubleTapXY) Implement() server.ToolHandlerFunc { } } -func (t *ToolDoubleTapXY) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolDoubleTapXY) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { if params, err := builtin.ConvertToFloat64Slice(action.Params); err == nil && len(params) == 2 { x, y := params[0], params[1] arguments := map[string]any{ diff --git a/uixt/mcp_tools_utility.go b/uixt/mcp_tools_utility.go index 5b9db979..69c26030 100644 --- a/uixt/mcp_tools_utility.go +++ b/uixt/mcp_tools_utility.go @@ -64,7 +64,7 @@ func (t *ToolSleep) Implement() server.ToolHandlerFunc { } } -func (t *ToolSleep) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolSleep) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { arguments := map[string]any{ "seconds": action.Params, } @@ -114,7 +114,7 @@ func (t *ToolSleepMS) Implement() server.ToolHandlerFunc { } } -func (t *ToolSleepMS) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolSleepMS) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { var milliseconds int64 if param, ok := action.Params.(json.Number); ok { milliseconds, _ = param.Int64() @@ -167,7 +167,7 @@ func (t *ToolSleepRandom) Implement() server.ToolHandlerFunc { } } -func (t *ToolSleepRandom) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolSleepRandom) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { if params, err := builtin.ConvertToFloat64Slice(action.Params); err == nil { arguments := map[string]any{ "params": params, @@ -219,7 +219,7 @@ func (t *ToolClosePopups) Implement() server.ToolHandlerFunc { } } -func (t *ToolClosePopups) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolClosePopups) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { return buildMCPCallToolRequest(t.Name(), map[string]any{}), nil } diff --git a/uixt/mcp_tools_web.go b/uixt/mcp_tools_web.go index 1f4eda5b..ddbca74c 100644 --- a/uixt/mcp_tools_web.go +++ b/uixt/mcp_tools_web.go @@ -56,7 +56,7 @@ func (t *ToolWebLoginNoneUI) Implement() server.ToolHandlerFunc { } } -func (t *ToolWebLoginNoneUI) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolWebLoginNoneUI) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { return buildMCPCallToolRequest(t.Name(), map[string]any{}), nil } @@ -111,7 +111,7 @@ func (t *ToolSecondaryClick) Implement() server.ToolHandlerFunc { } } -func (t *ToolSecondaryClick) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolSecondaryClick) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { if params, err := builtin.ConvertToFloat64Slice(action.Params); err == nil && len(params) == 2 { arguments := map[string]any{ "x": params[0], @@ -169,7 +169,7 @@ func (t *ToolHoverBySelector) Implement() server.ToolHandlerFunc { } } -func (t *ToolHoverBySelector) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolHoverBySelector) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { if selector, ok := action.Params.(string); ok { arguments := map[string]any{ "selector": selector, @@ -225,7 +225,7 @@ func (t *ToolTapBySelector) Implement() server.ToolHandlerFunc { } } -func (t *ToolTapBySelector) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolTapBySelector) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { if selector, ok := action.Params.(string); ok { arguments := map[string]any{ "selector": selector, @@ -281,7 +281,7 @@ func (t *ToolSecondaryClickBySelector) Implement() server.ToolHandlerFunc { } } -func (t *ToolSecondaryClickBySelector) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolSecondaryClickBySelector) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { if selector, ok := action.Params.(string); ok { arguments := map[string]any{ "selector": selector, @@ -347,7 +347,7 @@ func (t *ToolWebCloseTab) Implement() server.ToolHandlerFunc { } } -func (t *ToolWebCloseTab) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) { +func (t *ToolWebCloseTab) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { var tabIndex int if param, ok := action.Params.(json.Number); ok { paramInt64, _ := param.Int64() diff --git a/uixt/option/action.go b/uixt/option/action.go index 47580ea5..8b6068c3 100644 --- a/uixt/option/action.go +++ b/uixt/option/action.go @@ -13,6 +13,24 @@ import ( "github.com/rs/zerolog/log" ) +type MobileAction struct { + Method ActionName `json:"method,omitempty" yaml:"method,omitempty"` + Params interface{} `json:"params,omitempty" yaml:"params,omitempty"` + Fn func() `json:"-" yaml:"-"` // used for function action, not serialized + Options *ActionOptions `json:"options,omitempty" yaml:"options,omitempty"` + ActionOptions +} + +func (ma MobileAction) GetOptions() []ActionOption { + var actionOptionList []ActionOption + // Notice: merge options from ma.Options and ma.ActionOptions + if ma.Options != nil { + actionOptionList = append(actionOptionList, ma.Options.Options()...) + } + actionOptionList = append(actionOptionList, ma.ActionOptions.Options()...) + return actionOptionList +} + type ActionName string const ( diff --git a/uixt/sdk.go b/uixt/sdk.go index 090afcfc..08840576 100644 --- a/uixt/sdk.go +++ b/uixt/sdk.go @@ -87,7 +87,7 @@ func (c *MCPClient4XTDriver) GetToolByAction(actionName option.ActionName) Actio return c.Server.GetToolByAction(actionName) } -func (dExt *XTDriver) ExecuteAction(ctx context.Context, action MobileAction) (err error) { +func (dExt *XTDriver) ExecuteAction(ctx context.Context, action option.MobileAction) (err error) { // Find the corresponding tool for this action method tool := dExt.client.Server.GetToolByAction(action.Method) if tool == nil {