package llm import ( "context" "testing" ) func TestExecuteCoercesStringNumbers(t *testing.T) { type params struct { Memory string `json:"memory"` ReplaceMemoryID *uint `json:"replace_memory_id,omitempty"` RelationshipChange int `json:"relationship_change"` } var got params tool := Define("process", "test", func(ctx context.Context, p params) (string, error) { got = p return "ok", nil }, ) cases := []struct { name string args string wantInt int wantUint uint }{ {"int as string", `{"memory":"x","relationship_change":"3"}`, 3, 0}, {"int as string with plus", `{"memory":"x","relationship_change":"+3"}`, 3, 0}, {"int as string negative", `{"memory":"x","relationship_change":"-2"}`, -2, 0}, {"int as string with whitespace", `{"memory":"x","relationship_change":" 4 "}`, 4, 0}, {"int as string with decimal", `{"memory":"x","relationship_change":"2.7"}`, 2, 0}, {"native int still works", `{"memory":"x","relationship_change":5}`, 5, 0}, {"pointer uint as string", `{"memory":"x","replace_memory_id":"42","relationship_change":0}`, 0, 42}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { got = params{} result, err := tool.Execute(context.Background(), tc.args) if err != nil { t.Fatalf("execute failed: %v", err) } if result != "ok" { t.Errorf("expected 'ok', got %q", result) } if got.RelationshipChange != tc.wantInt { t.Errorf("RelationshipChange: want %d, got %d", tc.wantInt, got.RelationshipChange) } if tc.wantUint != 0 { if got.ReplaceMemoryID == nil || *got.ReplaceMemoryID != tc.wantUint { t.Errorf("ReplaceMemoryID: want %d, got %v", tc.wantUint, got.ReplaceMemoryID) } } }) } } func TestExecuteCoercesStringBoolAndFloat(t *testing.T) { type params struct { Enabled bool `json:"enabled"` Ratio float64 `json:"ratio"` } var got params tool := Define("cfg", "test", func(ctx context.Context, p params) (string, error) { got = p return "ok", nil }, ) if _, err := tool.Execute(context.Background(), `{"enabled":"true","ratio":"0.5"}`); err != nil { t.Fatalf("execute failed: %v", err) } if !got.Enabled { t.Errorf("expected enabled=true, got false") } if got.Ratio != 0.5 { t.Errorf("expected ratio=0.5, got %v", got.Ratio) } } func TestExecuteCoercesNestedAndSlices(t *testing.T) { type inner struct { N int `json:"n"` } type params struct { Items []inner `json:"items"` Tags []int `json:"tags"` } var got params tool := Define("nest", "test", func(ctx context.Context, p params) (string, error) { got = p return "ok", nil }, ) args := `{"items":[{"n":"1"},{"n":"2"}],"tags":["10","20"]}` if _, err := tool.Execute(context.Background(), args); err != nil { t.Fatalf("execute failed: %v", err) } if len(got.Items) != 2 || got.Items[0].N != 1 || got.Items[1].N != 2 { t.Errorf("nested struct coercion failed: %+v", got.Items) } if len(got.Tags) != 2 || got.Tags[0] != 10 || got.Tags[1] != 20 { t.Errorf("slice element coercion failed: %+v", got.Tags) } } func TestExecuteUnrecoverableArgsErrors(t *testing.T) { type params struct { N int `json:"n"` } tool := Define("bad", "test", func(ctx context.Context, p params) (string, error) { return "ok", nil }, ) if _, err := tool.Execute(context.Background(), `{"n":"not-a-number"}`); err == nil { t.Errorf("expected error for unparseable string") } if _, err := tool.Execute(context.Background(), `{not json`); err == nil { t.Errorf("expected error for malformed JSON") } }