package basic import ( "context" "fmt" "strings" "unicode" "unicode/utf8" ) // text exposes common string transformations: case folding, trimming, length, // splitting, replacement and template placeholder substitution. type text struct{} // Name returns the tool identifier. func (text) Name() string { return "text" } // Description explains what the tool does for the model. func (text) Description() string { return "Transform or measure text: upper, lower, title, trim, length, split, replace and template placeholder substitution." } // Parameters returns the tool input JSON Schema. The operation-specific inputs // (delimiter, old/new for replace, values for template) are validated by Execute // because they only apply to their operation. func (text) Parameters() (map[string]any, error) { return map[string]any{ "type": "object", "properties": map[string]any{ "operation": map[string]any{ "type": "string", "enum": []any{"upper", "lower", "title", "trim", "length", "split", "replace", "template"}, "description": "the transformation to apply", }, "input": map[string]any{"type": "string", "description": "the input string"}, "delimiter": map[string]any{"type": "string", "description": "split delimiter"}, "old": map[string]any{"type": "string", "description": "substring to replace"}, "new": map[string]any{"type": "string", "description": "replacement for old"}, "values": map[string]any{"type": "object", "description": "template placeholder substitutions"}, }, "required": []any{"operation", "input"}, }, nil } // Execute applies the requested operation. func (text) Execute(ctx context.Context, args any) (any, error) { if err := ctx.Err(); err != nil { return nil, err } m, err := asMap(args) if err != nil { return nil, err } op, err := reqString(m, "operation") if err != nil { return nil, err } in, err := reqString(m, "input") if err != nil { return nil, err } switch strings.ToLower(strings.TrimSpace(op)) { case "upper": return map[string]any{"result": strings.ToUpper(in)}, nil case "lower": return map[string]any{"result": strings.ToLower(in)}, nil case "title": return map[string]any{"result": title(in)}, nil case "trim": return map[string]any{"result": strings.TrimSpace(in)}, nil case "length": return map[string]any{"result": utf8.RuneCountInString(in)}, nil case "split": delim, err := reqString(m, "delimiter") if err != nil { return nil, err } parts := strings.Split(in, delim) out := make([]any, len(parts)) for i, p := range parts { out[i] = p } return map[string]any{"result": out}, nil case "replace": old, err := reqString(m, "old") if err != nil { return nil, err } repl, err := reqString(m, "new") if err != nil { return nil, err } return map[string]any{"result": strings.ReplaceAll(in, old, repl)}, nil case "template": vals, err := reqStringMap(m, "values") if err != nil { return nil, err } out, err := renderTemplate(in, vals) if err != nil { return nil, err } return map[string]any{"result": out}, nil default: return nil, fmt.Errorf("basic: unknown text operation %q: %w", op, ErrUnsupported) } } // title returns the title case of s: the first letter of each whitespace-delimited // word upper-cased and the rest lower-cased, preserving the original layout. func title(s string) string { var b strings.Builder b.Grow(len(s)) atStart := true for _, r := range s { if unicode.IsLetter(r) { if atStart { b.WriteRune(unicode.ToUpper(r)) } else { b.WriteRune(unicode.ToLower(r)) } atStart = false continue } b.WriteRune(r) if unicode.IsSpace(r) { atStart = true } } return b.String() } // renderTemplate substitutes {key} placeholders in tpl using vals, leaving any // placeholder whose key is absent unchanged. It scans once so a value that itself // contains a placeholder is not re-substituted. func renderTemplate(tpl string, vals map[string]string) (string, error) { var b strings.Builder b.Grow(len(tpl)) for i := 0; i < len(tpl); { open := strings.IndexByte(tpl[i:], '{') if open < 0 { b.WriteString(tpl[i:]) break } b.WriteString(tpl[i : i+open]) close := strings.IndexByte(tpl[i+open:], '}') if close < 0 { return "", fmt.Errorf("basic: unbalanced placeholder in template: %w", ErrInvalidArgument) } start := i + open + 1 end := i + open + close key := tpl[start:end] if val, ok := vals[key]; ok { b.WriteString(val) } else { b.WriteString(tpl[i+open : end+1]) } i = end + 1 } return b.String(), nil }