package basic import ( "context" "fmt" "strings" ) // markdown exposes Markdown building and parsing helpers: tables, lists, headings // and YAML front-matter metadata. type markdown struct{} const ( // minHeadingLevel and maxHeadingLevel bound the supported ATX heading depth. minHeadingLevel = 1 maxHeadingLevel = 6 ) // Name returns the tool identifier. func (markdown) Name() string { return "markdown" } // Description explains what the tool does for the model. func (markdown) Description() string { return "Build or parse Markdown: tables, lists, headings and YAML front-matter metadata." } // Parameters returns the tool input JSON Schema. Operation-specific inputs are // validated by Execute. func (markdown) Parameters() (map[string]any, error) { return map[string]any{ "type": "object", "properties": map[string]any{ "operation": map[string]any{ "type": "string", "enum": []any{"table", "list", "heading", "metadata"}, "description": "the operation to perform", }, "content": map[string]any{"type": "string", "description": "Markdown body for metadata parsing"}, "headers": map[string]any{"type": "array", "description": "table column headers"}, "rows": map[string]any{"type": "array", "description": "table rows (array of arrays of strings)"}, "items": map[string]any{"type": "array", "description": "list entries"}, "ordered": map[string]any{"type": "boolean", "description": "render a numbered list when true"}, "level": map[string]any{"type": "integer", "description": "heading level 1..6"}, "text": map[string]any{"type": "string", "description": "heading text"}, }, "required": []any{"operation"}, }, nil } // Execute runs the requested operation. func (markdown) 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 } switch strings.ToLower(strings.TrimSpace(op)) { case "table": return mdTable(m) case "list": return mdList(m) case "heading": return mdHeading(m) case "metadata": return mdMeta(m) default: return nil, fmt.Errorf("basic: unknown markdown operation %q: %w", op, ErrUnsupported) } } // mdTable builds a Markdown table from headers and rows. func mdTable(m map[string]any) (any, error) { headers, err := strSliceArg(m, "headers") if err != nil { return nil, err } rows, err := strSliceSliceArg(m, "rows") if err != nil { return nil, err } var b strings.Builder write := func(cells []string) { b.WriteString("| ") for i, c := range cells { if i > 0 { b.WriteString(" | ") } b.WriteString(escapeCell(c)) } b.WriteString(" |\n") } write(headers) b.WriteString(tableSeparator(headers)) for _, r := range rows { write(r) } return map[string]any{"result": b.String()}, nil } // tableSeparator returns the | --- | --- | header separator row for the column count. func tableSeparator(cols []string) string { parts := make([]string, len(cols)) for i := range parts { parts[i] = "---" } return "| " + strings.Join(parts, " | ") + " |\n" } // escapeCell neutralizes a cell so a pipe inside the value does not break the table. func escapeCell(s string) string { s = strings.ReplaceAll(s, "|", `\|`) s = strings.ReplaceAll(s, "\n", " ") return strings.ReplaceAll(s, "\r", "") } // mdList builds a bullet or numbered list from items. func mdList(m map[string]any) (any, error) { items, err := strSliceArg(m, "items") if err != nil { return nil, err } ordered, err := optBool(m, "ordered", false) if err != nil { return nil, err } var b strings.Builder for i, it := range items { if ordered { fmt.Fprintf(&b, "%d. %s\n", i+1, it) } else { b.WriteString("- " + it + "\n") } } return map[string]any{"result": b.String()}, nil } // mdHeading builds a Markdown ATX heading. func mdHeading(m map[string]any) (any, error) { level, err := optInt(m, "level", 1) if err != nil { return nil, err } text, err := reqString(m, "text") if err != nil { return nil, err } if level < minHeadingLevel || level > maxHeadingLevel { return nil, fmt.Errorf("basic: heading level must be between %d and %d: %w", minHeadingLevel, maxHeadingLevel, ErrInvalidArgument) } return map[string]any{"result": strings.Repeat("#", level) + " " + text + "\n"}, nil } // mdMeta parses YAML front matter from content and returns the metadata table plus // the remaining body. func mdMeta(m map[string]any) (any, error) { content, err := reqString(m, "content") if err != nil { return nil, err } meta, body := parseFrontMatter(content) return map[string]any{"meta": meta, "body": body}, nil } // parseFrontMatter splits leading `--- ... ---` front matter from a Markdown body. // When the document has no front matter it returns an empty table and the whole // content as the body. func parseFrontMatter(content string) (map[string]string, string) { lines := strings.Split(content, "\n") if len(lines) == 0 || strings.TrimSpace(lines[0]) != "---" { return map[string]string{}, content } meta := map[string]string{} i := 1 for i < len(lines) { line := strings.TrimRight(lines[i], "\r") if strings.TrimSpace(line) == "---" { i++ break } if key, val, ok := strings.Cut(line, ":"); ok { meta[strings.TrimSpace(key)] = strings.TrimSpace(val) } i++ } body := strings.Join(lines[i:], "\n") return meta, body }