feat(agentic): extract structured agent output
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
52a431867a
commit
a136c04aa1
2 changed files with 111 additions and 1 deletions
|
|
@ -81,7 +81,7 @@ func (s *PrepSubsystem) registerCommands(ctx context.Context) {
|
|||
c.Command("agentic:prompt_version", core.Command{Description: "Read the current prompt snapshot for a workspace", Action: s.cmdPromptVersion})
|
||||
c.Command("prompt/version", core.Command{Description: "Read the current prompt snapshot for a workspace", Action: s.cmdPromptVersion})
|
||||
c.Command("agentic:prompt/version", core.Command{Description: "Read the current prompt snapshot for a workspace", Action: s.cmdPromptVersion})
|
||||
c.Command("extract", core.Command{Description: "Extract a workspace template to a directory", Action: s.cmdExtract})
|
||||
c.Command("extract", core.Command{Description: "Extract data from agent output or scaffold a workspace template", Action: s.cmdExtract})
|
||||
s.registerPlanCommands()
|
||||
s.registerCommitCommands()
|
||||
s.registerSessionCommands()
|
||||
|
|
@ -887,11 +887,53 @@ func (s *PrepSubsystem) cmdPromptVersion(options core.Options) core.Result {
|
|||
}
|
||||
|
||||
func (s *PrepSubsystem) cmdExtract(options core.Options) core.Result {
|
||||
sourcePath := optionStringValue(options, "source", "input", "file")
|
||||
templateName := options.String("_arg")
|
||||
if templateName == "" {
|
||||
templateName = "default"
|
||||
}
|
||||
target := options.String("target")
|
||||
|
||||
if sourcePath == "" && fs.Exists(templateName) && fs.IsFile(templateName) {
|
||||
sourcePath = templateName
|
||||
templateName = ""
|
||||
}
|
||||
|
||||
if sourcePath != "" {
|
||||
readResult := fs.Read(sourcePath)
|
||||
if !readResult.OK {
|
||||
err, _ := readResult.Value.(error)
|
||||
if err == nil {
|
||||
err = core.E("agentic.cmdExtract", core.Concat("read agent output ", sourcePath), nil)
|
||||
}
|
||||
core.Print(nil, "error: %v", err)
|
||||
return core.Result{Value: err, OK: false}
|
||||
}
|
||||
|
||||
extracted := extractAgentOutputContent(readResult.Value.(string))
|
||||
if extracted == "" {
|
||||
err := core.E("agentic.cmdExtract", "agent output did not contain extractable content", nil)
|
||||
core.Print(nil, "error: %v", err)
|
||||
return core.Result{Value: err, OK: false}
|
||||
}
|
||||
|
||||
if target != "" {
|
||||
if writeResult := fs.WriteMode(target, extracted, 0644); !writeResult.OK {
|
||||
err, _ := writeResult.Value.(error)
|
||||
if err == nil {
|
||||
err = core.E("agentic.cmdExtract", core.Concat("write extracted output ", target), nil)
|
||||
}
|
||||
core.Print(nil, "error: %v", err)
|
||||
return core.Result{Value: err, OK: false}
|
||||
}
|
||||
core.Print(nil, "written: %s", target)
|
||||
} else {
|
||||
core.Print(nil, "%s", extracted)
|
||||
}
|
||||
|
||||
return core.Result{Value: extracted, OK: true}
|
||||
}
|
||||
|
||||
if target == "" {
|
||||
target = core.JoinPath(WorkspaceRoot(), "test-extract")
|
||||
}
|
||||
|
|
@ -926,6 +968,41 @@ func (s *PrepSubsystem) cmdExtract(options core.Options) core.Result {
|
|||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
func extractAgentOutputContent(content string) string {
|
||||
trimmed := core.Trim(content)
|
||||
if trimmed == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
if core.HasPrefix(trimmed, "{") || core.HasPrefix(trimmed, "[") {
|
||||
return trimmed
|
||||
}
|
||||
|
||||
blocks := core.Split(content, "```")
|
||||
for index := 1; index < len(blocks); index += 2 {
|
||||
block := core.Trim(blocks[index])
|
||||
if block == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
lines := core.SplitN(block, "\n", 2)
|
||||
if len(lines) == 2 {
|
||||
language := core.Trim(lines[0])
|
||||
body := core.Trim(lines[1])
|
||||
if language != "" && !core.Contains(language, " ") && body != "" {
|
||||
block = body
|
||||
}
|
||||
}
|
||||
|
||||
block = core.Trim(block)
|
||||
if block != "" {
|
||||
return block
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// parseIntString("issue-42") // 42
|
||||
func parseIntString(s string) int {
|
||||
n := 0
|
||||
|
|
|
|||
|
|
@ -1364,6 +1364,39 @@ func TestCommands_CmdExtract_Good(t *testing.T) {
|
|||
assert.True(t, r.OK)
|
||||
}
|
||||
|
||||
func TestCommands_CmdExtract_Good_FromAgentOutput(t *testing.T) {
|
||||
s, _ := testPrepWithCore(t, nil)
|
||||
dir := t.TempDir()
|
||||
source := core.JoinPath(dir, "agent-output.md")
|
||||
target := core.JoinPath(dir, "extracted.json")
|
||||
require.True(t, fs.Write(source, "Agent run complete.\n\n```json\n{\"summary\":\"done\",\"findings\":2}\n```\n").OK)
|
||||
|
||||
output := captureStdout(t, func() {
|
||||
r := s.cmdExtract(core.NewOptions(
|
||||
core.Option{Key: "source", Value: source},
|
||||
core.Option{Key: "target", Value: target},
|
||||
))
|
||||
assert.True(t, r.OK)
|
||||
assert.Equal(t, "{\"summary\":\"done\",\"findings\":2}", r.Value)
|
||||
})
|
||||
|
||||
assert.Contains(t, output, "written: ")
|
||||
assert.True(t, fs.Exists(target))
|
||||
written := fs.Read(target)
|
||||
require.True(t, written.OK)
|
||||
assert.Equal(t, "{\"summary\":\"done\",\"findings\":2}", written.Value)
|
||||
}
|
||||
|
||||
func TestCommands_CmdExtract_Bad_NoExtractableContent(t *testing.T) {
|
||||
s, _ := testPrepWithCore(t, nil)
|
||||
dir := t.TempDir()
|
||||
source := core.JoinPath(dir, "agent-output.md")
|
||||
require.True(t, fs.Write(source, "Agent run complete.\nNothing structured here.\n").OK)
|
||||
|
||||
r := s.cmdExtract(core.NewOptions(core.Option{Key: "source", Value: source}))
|
||||
assert.False(t, r.OK)
|
||||
}
|
||||
|
||||
func TestCommands_CmdRunTask_Bad_MissingArgs(t *testing.T) {
|
||||
s, _ := testPrepWithCore(t, nil)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue