From 8acb4a901ec261ccff8d31eeb4808b6cbb60018a Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Wed, 29 Jul 2026 22:19:19 +0200 Subject: [PATCH] agent: add Complete (one-shot ephemeral agent) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TRANSITIONAL: stateless code completion as a package function. Intent is to unify with Agent struct — a full Agent composes a one-shot. --- agent/complete.go | 101 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 agent/complete.go diff --git a/agent/complete.go b/agent/complete.go new file mode 100644 index 0000000..3199ece --- /dev/null +++ b/agent/complete.go @@ -0,0 +1,101 @@ +package agent + +import ( + "context" + "fmt" + "os" + "strings" + + "ollie/backend" + "ollie/paths" +) + +// Complete performs a stateless one-shot code completion. +// +// TRANSITIONAL: This is an ephemeral agent — no history, no lifecycle, just +// prompt→completion. The intent is to unify this with the Agent struct such +// that one composes the other (a full Agent IS-A one-shot with state layered on). +func Complete(cwd, filePath, prefix, suffix, extraContext string) (string, error) { + if cwd == "" { + cwd, _ = os.Getwd() + } + cwd = paths.ExpandHome(os.ExpandEnv(cwd)) + + model := os.Getenv("OLLIE_COMPLETE_MODEL") + backendName := os.Getenv("OLLIE_COMPLETE_BACKEND") + if model == "" || backendName == "" { + return "", fmt.Errorf("OLLIE_COMPLETE_MODEL and OLLIE_COMPLETE_BACKEND must be set") + } + + const prefixMax = 12000 + const suffixMax = 1000 + if len(prefix) > prefixMax { + prefix = prefix[len(prefix)-prefixMax:] + } + if len(suffix) > suffixMax { + suffix = suffix[:suffixMax] + } + + fileHint := "" + if filePath != "" { + fileHint = " in " + filePath + } + contextBlock := "" + if extraContext != "" { + contextBlock = "\n" + extraContext + } + prompt := fmt.Sprintf(`Implement the code at the cursor%s. The prefix ends at the point where new code is needed. Write the implementation — do not echo stubs, TODOs, or placeholder returns from the prefix. Output ONLY raw code. No reasoning, no shell commands, no explanations, no markdown fences, no backticks, no preamble. Your entire response must be valid code that can be inserted directly into the file. +%s + +%s + + +%s +`, fileHint, contextBlock, prefix, suffix) + + result, err := backend.Generate(context.Background(), backend.GenerateRequest{ + Prompt: prompt, + Backend: backendName, + Model: model, + }) + if err != nil { + return "", err + } + + result = stripCompletionNoise(result) + result = stripPrefixEcho(prefix, result) + return result, nil +} + +func stripCompletionNoise(s string) string { + var lines []string + for _, line := range strings.Split(s, "\n") { + trimmed := strings.TrimSpace(line) + if strings.HasPrefix(trimmed, "```") { + continue + } + if trimmed == "" || trimmed == "" || + trimmed == "" || trimmed == "" { + continue + } + if strings.HasPrefix(trimmed, ":: ") { + continue + } + lines = append(lines, line) + } + return strings.Join(lines, "\n") +} + +func stripPrefixEcho(prefix, result string) string { + tailMax := 200 + if len(prefix) < tailMax { + tailMax = len(prefix) + } + for i := tailMax; i > 0; i-- { + tail := prefix[len(prefix)-i:] + if strings.HasPrefix(result, tail) { + return result[len(tail):] + } + } + return result +}