diff --git a/pkg/agentic/commands.go b/pkg/agentic/commands.go index 8bfdd15..f83f111 100644 --- a/pkg/agentic/commands.go +++ b/pkg/agentic/commands.go @@ -154,13 +154,17 @@ func (s *PrepSubsystem) cmdPrep(options core.Options) core.Result { func (s *PrepSubsystem) cmdGenerate(options core.Options) core.Result { prompt := optionStringValue(options, "prompt", "_arg") - if prompt == "" { - core.Print(nil, "usage: core-agent generate --prompt=\"Draft a release note\" [--provider=claude] [--config='{\"max_tokens\":4000}']") - return core.Result{Value: core.E("agentic.cmdGenerate", "prompt is required", nil), OK: false} + briefID := optionStringValue(options, "brief_id", "brief-id") + template := optionStringValue(options, "template") + if prompt == "" && (briefID == "" || template == "") { + core.Print(nil, "usage: core-agent generate --prompt=\"Draft a release note\" [--brief-id=brief_1 --template=help-article] [--provider=claude] [--config='{\"max_tokens\":4000}']") + return core.Result{Value: core.E("agentic.cmdGenerate", "prompt or brief-id/template is required", nil), OK: false} } result := s.handleContentGenerate(s.commandContext(), core.NewOptions( core.Option{Key: "prompt", Value: prompt}, + core.Option{Key: "brief_id", Value: briefID}, + core.Option{Key: "template", Value: template}, core.Option{Key: "provider", Value: options.String("provider")}, core.Option{Key: "config", Value: options.String("config")}, )) diff --git a/pkg/agentic/commands_test.go b/pkg/agentic/commands_test.go index 962807f..25bb73d 100644 --- a/pkg/agentic/commands_test.go +++ b/pkg/agentic/commands_test.go @@ -14,6 +14,7 @@ import ( core "dappco.re/go/core" "dappco.re/go/core/forge" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // testPrepWithCore creates a PrepSubsystem backed by a real Core + Forge mock. @@ -719,6 +720,40 @@ func TestCommands_CmdGenerate_Good(t *testing.T) { assert.Contains(t, output, "content: Release notes draft") } +func TestCommands_CmdGenerate_Good_BriefTemplate(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/v1/content/generate", r.URL.Path) + assert.Equal(t, http.MethodPost, r.Method) + + bodyResult := core.ReadAll(r.Body) + require.True(t, bodyResult.OK) + + var payload map[string]any + parseResult := core.JSONUnmarshalString(bodyResult.Value.(string), &payload) + require.True(t, parseResult.OK) + assert.Equal(t, "brief_1", payload["brief_id"]) + assert.Equal(t, "help-article", payload["template"]) + + _, _ = w.Write([]byte(`{"data":{"id":"gen_2","provider":"claude","model":"claude-3.7-sonnet","content":"Template draft","status":"completed"}}`)) + })) + defer server.Close() + + s := testPrepWithPlatformServer(t, server, "secret-token") + output := captureStdout(t, func() { + r := s.cmdGenerate(core.NewOptions( + core.Option{Key: "brief_id", Value: "brief_1"}, + core.Option{Key: "template", Value: "help-article"}, + core.Option{Key: "provider", Value: "claude"}, + )) + assert.True(t, r.OK) + }) + + assert.Contains(t, output, "provider: claude") + assert.Contains(t, output, "model: claude-3.7-sonnet") + assert.Contains(t, output, "status: completed") + assert.Contains(t, output, "content: Template draft") +} + func TestCommands_CmdExtract_Good(t *testing.T) { s, _ := testPrepWithCore(t, nil) target := core.JoinPath(t.TempDir(), "extract-test") diff --git a/pkg/agentic/content.go b/pkg/agentic/content.go index da7c087..264df28 100644 --- a/pkg/agentic/content.go +++ b/pkg/agentic/content.go @@ -41,9 +41,15 @@ type ContentBrief struct { UpdatedAt string `json:"updated_at,omitempty"` } -// input := agentic.ContentGenerateInput{Prompt: "Draft a release note", Provider: "claude"} +// input := agentic.ContentGenerateInput{ +// BriefID: "brief_1", +// Template: "help-article", +// Provider: "claude", +// } type ContentGenerateInput struct { - Prompt string `json:"prompt"` + Prompt string `json:"prompt,omitempty"` + BriefID string `json:"brief_id,omitempty"` + Template string `json:"template,omitempty"` Provider string `json:"provider,omitempty"` Config map[string]any `json:"config,omitempty"` } @@ -151,6 +157,8 @@ type ContentFromPlanOutput struct { func (s *PrepSubsystem) handleContentGenerate(ctx context.Context, options core.Options) core.Result { _, output, err := s.contentGenerate(ctx, nil, ContentGenerateInput{ Prompt: optionStringValue(options, "prompt"), + BriefID: optionStringValue(options, "brief_id", "brief-id"), + Template: optionStringValue(options, "template"), Provider: optionStringValue(options, "provider"), Config: optionAnyMapValue(options, "config"), }) @@ -261,7 +269,7 @@ func (s *PrepSubsystem) handleContentFromPlan(ctx context.Context, options core. func (s *PrepSubsystem) registerContentTools(server *mcp.Server) { mcp.AddTool(server, &mcp.Tool{ Name: "content_generate", - Description: "Generate content using the platform AI provider abstraction.", + Description: "Generate content from a prompt or a brief/template pair using the platform AI provider abstraction.", }, s.contentGenerate) mcp.AddTool(server, &mcp.Tool{ @@ -306,12 +314,22 @@ func (s *PrepSubsystem) registerContentTools(server *mcp.Server) { } func (s *PrepSubsystem) contentGenerate(ctx context.Context, _ *mcp.CallToolRequest, input ContentGenerateInput) (*mcp.CallToolResult, ContentGenerateOutput, error) { - if core.Trim(input.Prompt) == "" { - return nil, ContentGenerateOutput{}, core.E("contentGenerate", "prompt is required", nil) + hasPrompt := core.Trim(input.Prompt) != "" + hasBrief := core.Trim(input.BriefID) != "" + hasTemplate := core.Trim(input.Template) != "" + if !hasPrompt && !(hasBrief && hasTemplate) { + return nil, ContentGenerateOutput{}, core.E("contentGenerate", "prompt or brief_id plus template is required", nil) } - body := map[string]any{ - "prompt": input.Prompt, + body := map[string]any{} + if hasPrompt { + body["prompt"] = input.Prompt + } + if input.BriefID != "" { + body["brief_id"] = input.BriefID + } + if input.Template != "" { + body["template"] = input.Template } if input.Provider != "" { body["provider"] = input.Provider diff --git a/pkg/agentic/content_test.go b/pkg/agentic/content_test.go index cd0574c..4c818e1 100644 --- a/pkg/agentic/content_test.go +++ b/pkg/agentic/content_test.go @@ -53,6 +53,39 @@ func TestContent_HandleContentGenerate_Good(t *testing.T) { assert.Equal(t, 48, output.Result.OutputTokens) } +func TestContent_HandleContentGenerate_Good_BriefTemplate(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, "/v1/content/generate", r.URL.Path) + require.Equal(t, http.MethodPost, r.Method) + + bodyResult := core.ReadAll(r.Body) + require.True(t, bodyResult.OK) + + var payload map[string]any + parseResult := core.JSONUnmarshalString(bodyResult.Value.(string), &payload) + require.True(t, parseResult.OK) + require.Equal(t, "brief_1", payload["brief_id"]) + require.Equal(t, "help-article", payload["template"]) + require.NotContains(t, payload, "prompt") + + _, _ = w.Write([]byte(`{"data":{"result":{"id":"gen_2","provider":"claude","model":"claude-3.7-sonnet","content":"Template draft","status":"completed"}}}`)) + })) + defer server.Close() + + subsystem := testPrepWithPlatformServer(t, server, "secret-token") + result := subsystem.handleContentGenerate(context.Background(), core.NewOptions( + core.Option{Key: "brief_id", Value: "brief_1"}, + core.Option{Key: "template", Value: "help-article"}, + core.Option{Key: "provider", Value: "claude"}, + )) + require.True(t, result.OK) + + output, ok := result.Value.(ContentGenerateOutput) + require.True(t, ok) + assert.Equal(t, "gen_2", output.Result.ID) + assert.Equal(t, "Template draft", output.Result.Content) +} + func TestContent_HandleContentGenerate_Bad(t *testing.T) { subsystem := testPrepWithPlatformServer(t, nil, "secret-token")