fix: ToolSleepMS tool call

This commit is contained in:
lilong.129
2025-06-30 13:36:44 +08:00
parent 16bb91a098
commit f332f4e304
3 changed files with 51 additions and 36 deletions

View File

@@ -4,13 +4,15 @@ import (
"context"
"encoding/json"
"fmt"
"strconv"
"time"
"github.com/httprunner/httprunner/v5/internal/builtin"
"github.com/httprunner/httprunner/v5/uixt/option"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
"github.com/rs/zerolog/log"
"github.com/httprunner/httprunner/v5/internal/builtin"
"github.com/httprunner/httprunner/v5/uixt/option"
)
// ToolSleep implements the sleep tool call.
@@ -98,7 +100,8 @@ func (t *ToolSleep) ConvertActionToCallToolRequest(action option.MobileAction) (
// ToolSleepMS implements the sleep_ms tool call.
type ToolSleepMS struct {
// Return data fields - these define the structure of data returned by this tool
Milliseconds int64 `json:"milliseconds" desc:"Duration in milliseconds that was slept"`
Milliseconds int64 `json:"milliseconds" desc:"Duration in milliseconds that was slept"`
Duration string `json:"duration" desc:"Human-readable duration string"`
}
func (t *ToolSleepMS) Name() option.ActionName {
@@ -110,26 +113,44 @@ func (t *ToolSleepMS) Description() string {
}
func (t *ToolSleepMS) Options() []mcp.ToolOption {
unifiedReq := &option.ActionOptions{}
return unifiedReq.GetMCPOptions(option.ACTION_SleepMS)
return []mcp.ToolOption{
mcp.WithNumber("milliseconds", mcp.Description("Number of milliseconds to sleep")),
}
}
func (t *ToolSleepMS) Implement() server.ToolHandlerFunc {
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
unifiedReq, err := parseActionOptions(request.Params.Arguments)
if err != nil {
return nil, err
}
// Validate required parameters
if unifiedReq.Milliseconds == 0 {
return nil, fmt.Errorf("milliseconds is required")
milliseconds, ok := request.Params.Arguments["milliseconds"]
if !ok {
log.Warn().Msg("milliseconds parameter is required, using default value 1000 milliseconds")
milliseconds = 1000
}
// Sleep MS action logic
log.Info().Int64("milliseconds", unifiedReq.Milliseconds).Msg("sleeping in milliseconds")
log.Info().Interface("milliseconds", milliseconds).Msg("sleeping in milliseconds")
duration := time.Duration(unifiedReq.Milliseconds) * time.Millisecond
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 context-aware sleep instead of blocking time.Sleep
select {
@@ -141,8 +162,11 @@ func (t *ToolSleepMS) Implement() server.ToolHandlerFunc {
return nil, fmt.Errorf("sleep interrupted: %w", ctx.Err())
}
message := fmt.Sprintf("Successfully slept for %d milliseconds", unifiedReq.Milliseconds)
returnData := ToolSleepMS{Milliseconds: unifiedReq.Milliseconds}
message := fmt.Sprintf("Successfully slept for %d milliseconds", actualMilliseconds)
returnData := ToolSleepMS{
Milliseconds: actualMilliseconds,
Duration: duration.String(),
}
return NewMCPSuccessResponse(message, &returnData), nil
}

View File

@@ -191,10 +191,6 @@ type ActionOptions struct {
ResetHistory bool `json:"reset_history,omitempty" yaml:"reset_history,omitempty" desc:"Whether to reset conversation history before AI planning"`
OutputSchema interface{} `json:"output_schema,omitempty" yaml:"output_schema,omitempty" desc:"Custom output schema for structured AI query response"`
// Time related
Seconds float64 `json:"seconds,omitempty" yaml:"seconds,omitempty" desc:"Sleep duration in seconds"`
Milliseconds int64 `json:"milliseconds,omitempty" yaml:"milliseconds,omitempty" desc:"Sleep duration in milliseconds"`
// Control options
Context context.Context `json:"-" yaml:"-"`
Identifier string `json:"identifier,omitempty" yaml:"identifier,omitempty" desc:"Action identifier for logging"`
@@ -368,7 +364,8 @@ func (o *ActionOptions) ApplyTapOffset(absX, absY float64) (float64, float64) {
}
func (o *ActionOptions) ApplySwipeOffset(absFromX, absFromY, absToX, absToY float64) (
float64, float64, float64, float64) {
float64, float64, float64, float64,
) {
if len(o.SwipeOffset) == 4 {
absFromX += float64(o.SwipeOffset[0])
absFromY += float64(o.SwipeOffset[1])

View File

@@ -140,16 +140,14 @@ func TestUnifiedActionRequest_CustomOptions(t *testing.T) {
func TestUnifiedActionRequest_BasicTypeFields(t *testing.T) {
// Test basic type fields (no longer pointers)
unifiedReq := &ActionOptions{
Platform: "android",
Serial: "device123",
Count: 5,
Keycode: 123,
Delta: 10,
Width: 800,
Height: 600,
Seconds: 2.5,
Milliseconds: 1500,
TabIndex: 3,
Platform: "android",
Serial: "device123",
Count: 5,
Keycode: 123,
Delta: 10,
Width: 800,
Height: 600,
TabIndex: 3,
}
// Test direct field access (no need for Getter methods)
@@ -158,8 +156,6 @@ func TestUnifiedActionRequest_BasicTypeFields(t *testing.T) {
assert.Equal(t, 10, unifiedReq.Delta)
assert.Equal(t, 800, unifiedReq.Width)
assert.Equal(t, 600, unifiedReq.Height)
assert.Equal(t, 2.5, unifiedReq.Seconds)
assert.Equal(t, int64(1500), unifiedReq.Milliseconds)
assert.Equal(t, 3, unifiedReq.TabIndex)
// Test zero value detection
@@ -169,7 +165,5 @@ func TestUnifiedActionRequest_BasicTypeFields(t *testing.T) {
assert.Equal(t, 0, emptyReq.Delta)
assert.Equal(t, 0, emptyReq.Width)
assert.Equal(t, 0, emptyReq.Height)
assert.Equal(t, 0.0, emptyReq.Seconds)
assert.Equal(t, int64(0), emptyReq.Milliseconds)
assert.Equal(t, 0, emptyReq.TabIndex)
}