tools: discover prompts from ollie:prompt script headers

- Add Prompt field to tools.ToolInfo
- Add ExtractPrompt parser for ollie:prompt...ollie:end blocks
- Add DiscoverTools to scan tool scripts directory
- BuildRuntime appends discovered tool prompts to preamble

Tool scripts can now embed their usage documentation directly in
the script header. This replaces the separate tools-*.md prompt
files and ensures documentation stays co-located with implementation.
This commit is contained in:
ollie 2026-07-21 13:18:38 +02:00
parent f5298306b1
commit 41eedb0c27
3 changed files with 92 additions and 0 deletions

View File

@ -24,6 +24,7 @@ import (
olog "ollie/pkg/log"
"ollie/pkg/paths"
"ollie/pkg/tools"
"ollie/pkg/tools/execute"
)
// toolClassifier reports whether a named tool is safe to run concurrently
@ -51,6 +52,8 @@ func BuildRuntime(cfg *config.Config, d tools.Dispatcher, cwd string, env []stri
if listErr != nil {
messages = append(messages, fmt.Sprintf("list tools: %v", listErr))
}
// Append named tool scripts with their prompts.
allToolInfos = append(allToolInfos, execute.DiscoverTools()...)
for _, t := range allToolInfos {
serverOf[t.Name] = t.Server
}
@ -160,6 +163,18 @@ func BuildRuntime(cfg *config.Config, d tools.Dispatcher, cwd string, env []stri
}
}
// Append tool prompts discovered from script headers.
var toolPrompts strings.Builder
for _, ti := range allToolInfos {
if ti.Prompt != "" {
toolPrompts.WriteString(ti.Prompt)
toolPrompts.WriteByte('\n')
}
}
if toolPrompts.Len() > 0 {
preamble += "\n" + toolPrompts.String()
}
return &Runtime{
Dispatcher: d,
Tools: allTools,

View File

@ -7,6 +7,7 @@ import (
"strings"
"ollie/pkg/paths"
"ollie/pkg/tools"
)
// ToolsPath returns the directory to search for named tool scripts.
@ -162,6 +163,79 @@ func ansiCEscape(s string) string {
return b.String()
}
// ExtractPrompt parses the ollie:prompt ... ollie:end block from a tool
// script's header comments. Returns the prompt text with comment prefixes
// stripped, or "" if no prompt block is found.
func ExtractPrompt(script string) string {
var lines []string
var inPrompt bool
for _, line := range strings.Split(script, "\n") {
trimmed := strings.TrimSpace(line)
// Strip comment prefix (# or //)
bare := trimmed
if strings.HasPrefix(bare, "# ") {
bare = bare[2:]
} else if strings.HasPrefix(bare, "#") {
bare = bare[1:]
} else if strings.HasPrefix(bare, "// ") {
bare = bare[3:]
} else if strings.HasPrefix(bare, "//") {
bare = bare[2:]
} else if inPrompt {
// Non-comment line while in prompt block — end of header
break
} else {
// Non-comment line before prompt block found
if trimmed != "" && !strings.HasPrefix(trimmed, "#!") {
break
}
continue
}
if strings.Contains(bare, "ollie:prompt") {
inPrompt = true
continue
}
if inPrompt && strings.Contains(bare, "ollie:end") {
break
}
if inPrompt {
lines = append(lines, bare)
}
}
if len(lines) == 0 {
return ""
}
return strings.Join(lines, "\n")
}
// DiscoverTools scans the tools directory and returns metadata for all scripts,
// including their ollie:prompt blocks. Used to populate the /tools file and
// inject tool prompts into the system prompt.
func DiscoverTools() []tools.ToolInfo {
dir := ToolsPath()
entries, err := os.ReadDir(dir)
if err != nil {
return nil
}
var infos []tools.ToolInfo
for _, e := range entries {
if e.IsDir() || e.Name() == "idx" || strings.HasPrefix(e.Name(), ".") {
continue
}
data, err := os.ReadFile(filepath.Join(dir, e.Name()))
if err != nil {
continue
}
prompt := ExtractPrompt(string(data))
infos = append(infos, tools.ToolInfo{
Name: e.Name(),
Prompt: prompt,
})
}
return infos
}
// ReadTool reads a named tool script from the tools directory.
func ReadTool(name string) (string, error) {
if strings.Contains(name, "/") || strings.Contains(name, "..") {

View File

@ -14,6 +14,9 @@ type ToolInfo struct {
Name string
Description string
InputSchema json.RawMessage
// Prompt is the usage documentation for this tool, extracted from
// the script's ollie:prompt block. Included in the system prompt.
Prompt string
}
// Server is the interface satisfied by any tool server.