mirror of
https://github.com/httprunner/httprunner.git
synced 2026-06-08 01:09:44 +08:00
fix: ToolSleepMS tool call
This commit is contained in:
@@ -4,13 +4,15 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"time"
|
"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/mcp"
|
||||||
"github.com/mark3labs/mcp-go/server"
|
"github.com/mark3labs/mcp-go/server"
|
||||||
"github.com/rs/zerolog/log"
|
"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.
|
// ToolSleep implements the sleep tool call.
|
||||||
@@ -98,7 +100,8 @@ func (t *ToolSleep) ConvertActionToCallToolRequest(action option.MobileAction) (
|
|||||||
// ToolSleepMS implements the sleep_ms tool call.
|
// ToolSleepMS implements the sleep_ms tool call.
|
||||||
type ToolSleepMS struct {
|
type ToolSleepMS struct {
|
||||||
// Return data fields - these define the structure of data returned by this tool
|
// 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 {
|
func (t *ToolSleepMS) Name() option.ActionName {
|
||||||
@@ -110,26 +113,44 @@ func (t *ToolSleepMS) Description() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *ToolSleepMS) Options() []mcp.ToolOption {
|
func (t *ToolSleepMS) Options() []mcp.ToolOption {
|
||||||
unifiedReq := &option.ActionOptions{}
|
return []mcp.ToolOption{
|
||||||
return unifiedReq.GetMCPOptions(option.ACTION_SleepMS)
|
mcp.WithNumber("milliseconds", mcp.Description("Number of milliseconds to sleep")),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ToolSleepMS) Implement() server.ToolHandlerFunc {
|
func (t *ToolSleepMS) Implement() server.ToolHandlerFunc {
|
||||||
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||||
unifiedReq, err := parseActionOptions(request.Params.Arguments)
|
milliseconds, ok := request.Params.Arguments["milliseconds"]
|
||||||
if err != nil {
|
if !ok {
|
||||||
return nil, err
|
log.Warn().Msg("milliseconds parameter is required, using default value 1000 milliseconds")
|
||||||
}
|
milliseconds = 1000
|
||||||
|
|
||||||
// Validate required parameters
|
|
||||||
if unifiedReq.Milliseconds == 0 {
|
|
||||||
return nil, fmt.Errorf("milliseconds is required")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sleep MS action logic
|
// 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
|
// Use context-aware sleep instead of blocking time.Sleep
|
||||||
select {
|
select {
|
||||||
@@ -141,8 +162,11 @@ func (t *ToolSleepMS) Implement() server.ToolHandlerFunc {
|
|||||||
return nil, fmt.Errorf("sleep interrupted: %w", ctx.Err())
|
return nil, fmt.Errorf("sleep interrupted: %w", ctx.Err())
|
||||||
}
|
}
|
||||||
|
|
||||||
message := fmt.Sprintf("Successfully slept for %d milliseconds", unifiedReq.Milliseconds)
|
message := fmt.Sprintf("Successfully slept for %d milliseconds", actualMilliseconds)
|
||||||
returnData := ToolSleepMS{Milliseconds: unifiedReq.Milliseconds}
|
returnData := ToolSleepMS{
|
||||||
|
Milliseconds: actualMilliseconds,
|
||||||
|
Duration: duration.String(),
|
||||||
|
}
|
||||||
|
|
||||||
return NewMCPSuccessResponse(message, &returnData), nil
|
return NewMCPSuccessResponse(message, &returnData), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"`
|
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"`
|
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
|
// Control options
|
||||||
Context context.Context `json:"-" yaml:"-"`
|
Context context.Context `json:"-" yaml:"-"`
|
||||||
Identifier string `json:"identifier,omitempty" yaml:"identifier,omitempty" desc:"Action identifier for logging"`
|
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) (
|
func (o *ActionOptions) ApplySwipeOffset(absFromX, absFromY, absToX, absToY float64) (
|
||||||
float64, float64, float64, float64) {
|
float64, float64, float64, float64,
|
||||||
|
) {
|
||||||
if len(o.SwipeOffset) == 4 {
|
if len(o.SwipeOffset) == 4 {
|
||||||
absFromX += float64(o.SwipeOffset[0])
|
absFromX += float64(o.SwipeOffset[0])
|
||||||
absFromY += float64(o.SwipeOffset[1])
|
absFromY += float64(o.SwipeOffset[1])
|
||||||
|
|||||||
@@ -140,16 +140,14 @@ func TestUnifiedActionRequest_CustomOptions(t *testing.T) {
|
|||||||
func TestUnifiedActionRequest_BasicTypeFields(t *testing.T) {
|
func TestUnifiedActionRequest_BasicTypeFields(t *testing.T) {
|
||||||
// Test basic type fields (no longer pointers)
|
// Test basic type fields (no longer pointers)
|
||||||
unifiedReq := &ActionOptions{
|
unifiedReq := &ActionOptions{
|
||||||
Platform: "android",
|
Platform: "android",
|
||||||
Serial: "device123",
|
Serial: "device123",
|
||||||
Count: 5,
|
Count: 5,
|
||||||
Keycode: 123,
|
Keycode: 123,
|
||||||
Delta: 10,
|
Delta: 10,
|
||||||
Width: 800,
|
Width: 800,
|
||||||
Height: 600,
|
Height: 600,
|
||||||
Seconds: 2.5,
|
TabIndex: 3,
|
||||||
Milliseconds: 1500,
|
|
||||||
TabIndex: 3,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test direct field access (no need for Getter methods)
|
// 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, 10, unifiedReq.Delta)
|
||||||
assert.Equal(t, 800, unifiedReq.Width)
|
assert.Equal(t, 800, unifiedReq.Width)
|
||||||
assert.Equal(t, 600, unifiedReq.Height)
|
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)
|
assert.Equal(t, 3, unifiedReq.TabIndex)
|
||||||
|
|
||||||
// Test zero value detection
|
// 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.Delta)
|
||||||
assert.Equal(t, 0, emptyReq.Width)
|
assert.Equal(t, 0, emptyReq.Width)
|
||||||
assert.Equal(t, 0, emptyReq.Height)
|
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)
|
assert.Equal(t, 0, emptyReq.TabIndex)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user