From 1253d5848d25564d50703b18a8e6902ad507e85b Mon Sep 17 00:00:00 2001 From: "lilong.129" Date: Tue, 12 Aug 2025 14:49:47 +0800 Subject: [PATCH] refactor: unify float64 conversion logic in ToolSleep and ToolSleepMS, enhance error logging --- internal/builtin/utils.go | 31 +++--------- internal/version/VERSION | 2 +- uixt/mcp_tools_utility.go | 87 +++++++++++----------------------- uixt/mcp_tools_utility_test.go | 45 ++++++++++++++++++ 4 files changed, 80 insertions(+), 85 deletions(-) diff --git a/internal/builtin/utils.go b/internal/builtin/utils.go index 5e378598..3beb0679 100644 --- a/internal/builtin/utils.go +++ b/internal/builtin/utils.go @@ -217,6 +217,8 @@ func Interface2Float64(i interface{}) (float64, error) { case string: // e.g. "1", "0.5" floatVar, err := strconv.ParseFloat(v, 64) if err != nil { + log.Error().Err(err).Str("value", v). + Msg("convert string to float64 failed") return 0, err } return floatVar, nil @@ -226,6 +228,10 @@ func Interface2Float64(i interface{}) (float64, error) { if ok { return value.Float64() } + + // Log error for unsupported types + log.Error().Interface("value", i).Type("type", i). + Msg("convert float64 failed") return 0, errors.New("failed to convert interface to float64") } @@ -334,29 +340,6 @@ func IsZeroFloat64(f float64) bool { return math.Abs(f) < threshold } -func ConvertToFloat64(val interface{}) (float64, error) { - switch v := val.(type) { - case float64: - return v, nil - case int: - return float64(v), nil - case int64: - return float64(v), nil - case string: - f, err := strconv.ParseFloat(v, 64) - if err != nil { - log.Error().Err(err).Str("value", v). - Msg("convert string to float64 failed") - return 0, err - } - return f, nil - default: - log.Error().Interface("value", val).Type("type", val). - Msg("convert float64 failed") - return 0, errors.New("convert float64 error") - } -} - func ConvertToFloat64Slice(val interface{}) ([]float64, error) { if paramsSlice, ok := val.([]float64); ok { return paramsSlice, nil @@ -369,7 +352,7 @@ func ConvertToFloat64Slice(val interface{}) ([]float64, error) { var err error float64Slice := make([]float64, len(paramsSlice)) for i, v := range paramsSlice { - float64Slice[i], err = ConvertToFloat64(v) + float64Slice[i], err = Interface2Float64(v) if err != nil { return nil, err } diff --git a/internal/version/VERSION b/internal/version/VERSION index 333d36aa..b7fffde9 100644 --- a/internal/version/VERSION +++ b/internal/version/VERSION @@ -1 +1 @@ -v5.0.0-250811 +v5.0.0-250812 diff --git a/uixt/mcp_tools_utility.go b/uixt/mcp_tools_utility.go index 79f49bac..5b3656d9 100644 --- a/uixt/mcp_tools_utility.go +++ b/uixt/mcp_tools_utility.go @@ -2,9 +2,7 @@ package uixt import ( "context" - "encoding/json" "fmt" - "strconv" "time" "github.com/mark3labs/mcp-go/mcp" @@ -70,28 +68,12 @@ func (t *ToolSleep) Implement() server.ToolHandlerFunc { // Sleep action logic log.Info().Interface("seconds", seconds).Msg("sleeping") - var duration time.Duration - var actualSeconds float64 - switch v := seconds.(type) { - case float64: - actualSeconds = v - duration = time.Duration(v*1000) * time.Millisecond - case int: - actualSeconds = float64(v) - duration = time.Duration(v) * time.Second - case int64: - actualSeconds = float64(v) - duration = time.Duration(v) * time.Second - case string: - s, err := builtin.ConvertToFloat64(v) - if err != nil { - return nil, fmt.Errorf("invalid sleep duration: %v", v) - } - actualSeconds = s - duration = time.Duration(s*1000) * time.Millisecond - default: - return nil, fmt.Errorf("unsupported sleep duration type: %T", v) + // Use Interface2Float64 for unified type conversion + actualSeconds, err := builtin.Interface2Float64(seconds) + if err != nil { + return nil, fmt.Errorf("invalid sleep duration: %v", seconds) } + duration := time.Duration(actualSeconds) * time.Second // Extract start_time_ms and use sleepStrict for unified sleep logic startTime, err := extractStartTimeMs(request) @@ -116,19 +98,19 @@ func (t *ToolSleep) ConvertActionToCallToolRequest(action option.MobileAction) ( arguments := map[string]any{} var seconds float64 - if param, ok := action.Params.(json.Number); ok { - seconds, _ = param.Float64() - arguments["seconds"] = seconds - } else if param, ok := action.Params.(int64); ok { - seconds = float64(param) - arguments["seconds"] = seconds - } else if sleepConfig, ok := action.Params.(SleepConfig); ok { + if sleepConfig, ok := action.Params.(SleepConfig); ok { // When startTime is provided, pass both seconds and startTime seconds = sleepConfig.Seconds arguments["seconds"] = seconds arguments["start_time_ms"] = sleepConfig.StartTime.UnixMilli() } else { - return mcp.CallToolRequest{}, fmt.Errorf("invalid sleep params: %v", action.Params) + // Use builtin.Interface2Float64 for unified parameter handling + var err error + seconds, err = builtin.Interface2Float64(action.Params) + if err != nil { + return mcp.CallToolRequest{}, fmt.Errorf("invalid sleep params: %v", action.Params) + } + arguments["seconds"] = seconds } return BuildMCPCallToolRequest(t.Name(), arguments, action), nil @@ -167,28 +149,13 @@ func (t *ToolSleepMS) Implement() server.ToolHandlerFunc { // Sleep MS action logic log.Info().Interface("milliseconds", milliseconds).Msg("sleeping in milliseconds") - var duration time.Duration - var actualMilliseconds int64 - switch v := milliseconds.(type) { - case float64: - actualMilliseconds = int64(v) - duration = time.Duration(v) * time.Millisecond - case int: - actualMilliseconds = int64(v) - duration = time.Duration(v) * time.Millisecond - case int64: - actualMilliseconds = v - duration = time.Duration(v) * time.Millisecond - case string: - ms, err := strconv.ParseInt(v, 10, 64) - if err != nil { - return nil, fmt.Errorf("invalid sleep duration: %v", v) - } - actualMilliseconds = ms - duration = time.Duration(ms) * time.Millisecond - default: - return nil, fmt.Errorf("unsupported sleep duration type: %T", v) + // Use Interface2Float64 for unified type conversion, then convert to int64 + floatVal, err := builtin.Interface2Float64(milliseconds) + if err != nil { + return nil, fmt.Errorf("invalid sleep duration: %v", milliseconds) } + actualMilliseconds := int64(floatVal) + duration := time.Duration(actualMilliseconds) * time.Millisecond // Extract start_time_ms and use sleepStrict for unified sleep logic startTime, err := extractStartTimeMs(request) @@ -212,19 +179,19 @@ func (t *ToolSleepMS) ConvertActionToCallToolRequest(action option.MobileAction) arguments := map[string]any{} var milliseconds int64 - if param, ok := action.Params.(json.Number); ok { - milliseconds, _ = param.Int64() - arguments["milliseconds"] = milliseconds - } else if param, ok := action.Params.(int64); ok { - milliseconds = param - arguments["milliseconds"] = milliseconds - } else if sleepConfig, ok := action.Params.(SleepConfig); ok { + if sleepConfig, ok := action.Params.(SleepConfig); ok { // When startTime is provided, pass both milliseconds and startTime milliseconds = sleepConfig.Milliseconds arguments["milliseconds"] = milliseconds arguments["start_time_ms"] = sleepConfig.StartTime.UnixMilli() } else { - return mcp.CallToolRequest{}, fmt.Errorf("invalid sleep ms params: %v", action.Params) + // Use builtin.Interface2Float64 for unified parameter handling, then convert to int64 + floatVal, err := builtin.Interface2Float64(action.Params) + if err != nil { + return mcp.CallToolRequest{}, fmt.Errorf("invalid sleep ms params: %v", action.Params) + } + milliseconds = int64(floatVal) + arguments["milliseconds"] = milliseconds } return BuildMCPCallToolRequest(t.Name(), arguments, action), nil diff --git a/uixt/mcp_tools_utility_test.go b/uixt/mcp_tools_utility_test.go index 9e53ac2c..733114c0 100644 --- a/uixt/mcp_tools_utility_test.go +++ b/uixt/mcp_tools_utility_test.go @@ -30,6 +30,15 @@ func TestToolSleep_ConvertActionToCallToolRequest(t *testing.T) { expectedArgs: map[string]any{"seconds": float64(3.5)}, shouldError: false, }, + { + name: "float64 parameter", + action: option.MobileAction{ + Method: option.ACTION_Sleep, + Params: float64(5.2), + }, + expectedArgs: map[string]any{"seconds": float64(5.2)}, + shouldError: false, + }, { name: "int64 parameter", action: option.MobileAction{ @@ -63,6 +72,24 @@ func TestToolSleep_ConvertActionToCallToolRequest(t *testing.T) { expectedArgs: nil, shouldError: true, }, + { + name: "json.Number with integer value", + action: option.MobileAction{ + Method: option.ACTION_Sleep, + Params: json.Number("10"), + }, + expectedArgs: map[string]any{"seconds": float64(10)}, + shouldError: false, + }, + { + name: "json.Number with decimal value", + action: option.MobileAction{ + Method: option.ACTION_Sleep, + Params: json.Number("1.25"), + }, + expectedArgs: map[string]any{"seconds": float64(1.25)}, + shouldError: false, + }, } for _, tt := range tests { @@ -109,6 +136,15 @@ func TestToolSleepMS_ConvertActionToCallToolRequest(t *testing.T) { expectedArgs: map[string]any{"milliseconds": int64(2000)}, shouldError: false, }, + { + name: "float64 parameter", + action: option.MobileAction{ + Method: option.ACTION_SleepMS, + Params: float64(2500.7), + }, + expectedArgs: map[string]any{"milliseconds": int64(2500)}, + shouldError: false, + }, { name: "SleepConfig with startTime", action: option.MobileAction{ @@ -124,6 +160,15 @@ func TestToolSleepMS_ConvertActionToCallToolRequest(t *testing.T) { }, shouldError: false, }, + { + name: "json.Number with decimal value", + action: option.MobileAction{ + Method: option.ACTION_SleepMS, + Params: json.Number("1234.56"), + }, + expectedArgs: map[string]any{"milliseconds": int64(1234)}, + shouldError: false, + }, { name: "invalid parameter type", action: option.MobileAction{