bots-garden/mini-mepublic Fork 0
d72271127802973540c648bfb372176cdaaa8e4f
Commits
Clone
git clone https://git.rickub.com/bots-garden/mini-me.git
git clone ssh://git@rickub.com/bots-garden/mini-me.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

💾 Saved. d722711 · on d72271127802973540c648bfb372176cdaaa8e4f · k33g · 6h ago
mention.go · 114 lines · 3.5 KBGo Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// Package mention understands the @path notation of a prompt: "explain
// @internal/acp/acp.go" names a file the model should look at. There is no
// picker — the user types the path — and the file's content does not travel
// in the prompt: as for an editor's @-mention over ACP, the mention becomes an
// `[attached file: <absolute path>]` line and the model reads the file with
// its tools. Both front ends use it, so a typed @path means the same thing in
// the terminal and in an editor whose picker was bypassed.
package mention

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
)

// Attachment is one @path mention that named an existing file or directory.
type Attachment struct {
	Mention string // as typed, without the @ and the trailing punctuation
	Path    string // absolute, cleaned
	Dir     bool   // a directory rather than a file
}

// trailing is the sentence punctuation a mention may be glued to — "look at
// @main.go, then…" mentions main.go, not "main.go,".
const trailing = ",.;:!?)]}'\"`"

// Expand finds the @path mentions of input, resolves them against cwd (an
// absolute path starts with / or ~/, anything else is relative to cwd) and
// returns the input followed by one attachment line per existing path, in
// order of first appearance, each path once:
//
//	[attached file: /work/project/main.go]
//	[attached directory: /work/project/internal]
//
// A mention that names nothing on disk is left alone: "@bob" in a sentence is
// not a file. A @ glued to a word (an e-mail address) is not a mention either.
func Expand(input, cwd string) (string, []Attachment) {
	var out []Attachment
	seen := map[string]bool{}
	for _, m := range mentions(input) {
		path := resolve(m, cwd)
		if seen[path] {
			continue
		}
		info, err := os.Stat(path)
		if err != nil {
			continue
		}
		seen[path] = true
		out = append(out, Attachment{Mention: m, Path: path, Dir: info.IsDir()})
	}
	var b strings.Builder
	b.WriteString(input)
	for _, a := range out {
		fmt.Fprintf(&b, "\n%s", a.Line())
	}
	return b.String(), out
}

// Line is the attachment as the model sees it — the same shape an ACP
// resource_link takes, so the model meets one convention, not two.
func (a Attachment) Line() string {
	if a.Dir {
		return fmt.Sprintf("[attached directory: %s]", a.Path)
	}
	return fmt.Sprintf("[attached file: %s]", a.Path)
}

// mentions returns the candidate paths: every "@something" that starts the
// input or follows a space, an opening bracket or a quote, stripped of the
// punctuation it may end with. Existence is the caller's business.
func mentions(input string) []string {
	var out []string
	for i := 0; i < len(input); i++ {
		if input[i] != '@' || !startsMention(input, i) {
			continue
		}
		end := i + 1
		for end < len(input) && !isSpace(input[end]) {
			end++
		}
		m := strings.TrimRight(input[i+1:end], trailing)
		if m != "" {
			out = append(out, m)
		}
		i = end
	}
	return out
}

func startsMention(input string, i int) bool {
	if i == 0 {
		return true
	}
	return isSpace(input[i-1]) || strings.IndexByte("([{'\"`", input[i-1]) >= 0
}

func isSpace(c byte) bool {
	return c == ' ' || c == '\t' || c == '\n' || c == '\r'
}

// resolve turns a mention into an absolute, cleaned path.
func resolve(m, cwd string) string {
	switch {
	case filepath.IsAbs(m):
		return filepath.Clean(m)
	case m == "~" || strings.HasPrefix(m, "~/"):
		if home, err := os.UserHomeDir(); err == nil {
			return filepath.Clean(filepath.Join(home, m[1:]))
		}
	}
	return filepath.Clean(filepath.Join(cwd, m))
}