package tool import ( "context" "strings" "testing" ) // coerceParams mirrors the field kinds legacy gollm's coercion supported. type coerceParams struct { Count int `json:"count"` Ratio float64 `json:"ratio"` Flag bool `json:"flag"` Limit *int `json:"limit"` Tags []int `json:"tags"` Nested coerceIn `json:"nested"` Verbose string `json:"verbose"` } type coerceIn struct { Depth uint `json:"depth"` } // TestGatedTool_LenientArgCoercion anchors the legacy gollm-era tolerance the // conversion preserved: numeric and boolean fields supplied as strings // by the model ("3", "true") decode into the typed Args, recursing into // pointers, slices, and nested structs. Models emit this shape // constantly; losing the tolerance would break live tool traffic. func TestGatedTool_LenientArgCoercion(t *testing.T) { var seen coerceParams tool := NewGatedTool[coerceParams]( "coerce_tool", "coercion test", Permission{AuthoringRequirement: RequirementAnyone, SafeForShare: true}, func(ctx context.Context, inv Invocation, args coerceParams) (string, error) { seen = args return "ok", nil }, ) out, err := buildAndExecute(t, tool, Invocation{SkillName: "x"}, VisibilityPrivate, nil, `{"count":"3","ratio":" 2.5 ","flag":"true","limit":"7","tags":["1","2"],"nested":{"depth":"4"},"verbose":"yes"}`) if err != nil || out != "ok" { t.Fatalf("execute: out=%q err=%v", out, err) } if seen.Count != 3 || seen.Ratio != 2.5 || seen.Flag != true { t.Fatalf("scalar coercion failed: %+v", seen) } if seen.Limit == nil || *seen.Limit != 7 { t.Fatalf("pointer coercion failed: %+v", seen.Limit) } if len(seen.Tags) != 2 || seen.Tags[0] != 1 || seen.Tags[1] != 2 { t.Fatalf("slice coercion failed: %+v", seen.Tags) } if seen.Nested.Depth != 4 { t.Fatalf("nested coercion failed: %+v", seen.Nested) } if seen.Verbose != "yes" { t.Fatalf("string field mangled: %q", seen.Verbose) } } // TestGatedTool_StrictPathUnaffected confirms well-typed args take the // zero-cost strict path and uncoercible strings still fail loudly. func TestGatedTool_StrictPathUnaffected(t *testing.T) { tool := NewGatedTool[coerceParams]( "coerce_strict_tool", "coercion test", Permission{AuthoringRequirement: RequirementAnyone, SafeForShare: true}, func(ctx context.Context, inv Invocation, args coerceParams) (string, error) { return "ok", nil }, ) if out, err := buildAndExecute(t, tool, Invocation{SkillName: "x"}, VisibilityPrivate, nil, `{"count":3,"ratio":2.5,"flag":true}`); err != nil || out != "ok" { t.Fatalf("strict path: out=%q err=%v", out, err) } _, err := buildAndExecute(t, tool, Invocation{SkillName: "x"}, VisibilityPrivate, nil, `{"count":"not-a-number"}`) if err == nil || !strings.Contains(err.Error(), "invalid arguments") { t.Fatalf("expected invalid-arguments error for uncoercible string, got %v", err) } }