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:
parent
f5298306b1
commit
41eedb0c27
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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, "..") {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Reference in New Issue