- Add authoring adapter for openai integration - Move 02+01_iop_authoring_bridge to archive - Update scheduler jobs and workflow lifecycle - Enhance openai client and model definitions
131 lines
4.7 KiB
Go
131 lines
4.7 KiB
Go
package authoring
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
|
|
"github.com/nomadcode/nomadcode-core/internal/model"
|
|
"github.com/nomadcode/nomadcode-core/internal/storage"
|
|
"github.com/nomadcode/nomadcode-core/internal/workitempipeline"
|
|
)
|
|
|
|
// pipelinePayload is the actual task.Payload shape produced by
|
|
// workitem.BuildCreateTaskInput. "message" carries the Plane ticket body
|
|
// (comment > description > title), and "work_item" carries the provider
|
|
// identity map (provider, tenant, project, id, state_id, external_url).
|
|
// "prompt" and "instructions" are accepted as a generic override shape.
|
|
type pipelinePayload struct {
|
|
// pipeline shape (set by workitem.BuildCreateTaskInput)
|
|
Message string `json:"message"`
|
|
WorkItem map[string]string `json:"work_item"`
|
|
// generic override shape
|
|
Prompt string `json:"prompt"`
|
|
Instructions string `json:"instructions"`
|
|
}
|
|
|
|
// isAuthoringTask returns true when the task is a Plane-origin authoring task
|
|
// that should be routed through the IOP workspace authoring bridge. The criteria
|
|
// are: source=="plane" and task metadata contains a "checkout" key with a
|
|
// non-empty slot path.
|
|
func isAuthoringTask(task storage.Task) (workitempipeline.CheckoutMetadata, bool) {
|
|
if task.Source != "plane" {
|
|
return workitempipeline.CheckoutMetadata{}, false
|
|
}
|
|
if len(task.Metadata) == 0 || string(task.Metadata) == "null" {
|
|
return workitempipeline.CheckoutMetadata{}, false
|
|
}
|
|
var meta struct {
|
|
Checkout *workitempipeline.CheckoutMetadata `json:"checkout"`
|
|
}
|
|
if err := json.Unmarshal(task.Metadata, &meta); err != nil || meta.Checkout == nil {
|
|
return workitempipeline.CheckoutMetadata{}, false
|
|
}
|
|
if strings.TrimSpace(meta.Checkout.SlotPath) == "" {
|
|
return workitempipeline.CheckoutMetadata{}, false
|
|
}
|
|
return *meta.Checkout, true
|
|
}
|
|
|
|
// providerIdentity resolves the Plane provider and work item ID for a task.
|
|
// Priority: storage.Task.ExternalProvider/ExternalID (set by workitem pipeline)
|
|
// → work_item payload map → task.Source / task.ID fallback.
|
|
func providerIdentity(task storage.Task, payload pipelinePayload) (provider, workItemID string) {
|
|
provider = task.Source
|
|
workItemID = task.ID
|
|
|
|
if task.ExternalProvider != nil && *task.ExternalProvider != "" {
|
|
provider = *task.ExternalProvider
|
|
}
|
|
if task.ExternalID != nil && *task.ExternalID != "" {
|
|
workItemID = *task.ExternalID
|
|
return provider, workItemID
|
|
}
|
|
|
|
// fall back to work_item map in payload (also set by workitem pipeline)
|
|
if id := strings.TrimSpace(payload.WorkItem["id"]); id != "" {
|
|
workItemID = id
|
|
}
|
|
if p := strings.TrimSpace(payload.WorkItem["provider"]); p != "" {
|
|
provider = p
|
|
}
|
|
return provider, workItemID
|
|
}
|
|
|
|
// BuildAuthoringGenerateInput constructs a workspace-bound model.GenerateInput
|
|
// for Plane-origin authoring tasks. Returns false when the task does not qualify
|
|
// as an authoring task (non-Plane source or missing checkout metadata).
|
|
func BuildAuthoringGenerateInput(task storage.Task) (model.GenerateInput, bool) {
|
|
checkout, ok := isAuthoringTask(task)
|
|
if !ok {
|
|
return model.GenerateInput{}, false
|
|
}
|
|
|
|
// input priority: prompt override > pipeline message > task title
|
|
input := strings.TrimSpace(task.Title)
|
|
instructions := ""
|
|
|
|
var p pipelinePayload
|
|
if len(task.Payload) > 0 && string(task.Payload) != "null" && string(task.Payload) != "{}" {
|
|
_ = json.Unmarshal(task.Payload, &p)
|
|
}
|
|
|
|
if body := strings.TrimSpace(p.Message); body != "" {
|
|
input = body
|
|
}
|
|
if override := strings.TrimSpace(p.Prompt); override != "" {
|
|
input = override
|
|
}
|
|
instructions = strings.TrimSpace(p.Instructions)
|
|
|
|
if instructions == "" {
|
|
instructions = buildAuthoringInstructions(task, checkout)
|
|
}
|
|
|
|
provider, workItemID := providerIdentity(task, p)
|
|
|
|
return model.GenerateInput{
|
|
Input: input,
|
|
Instructions: instructions,
|
|
Metadata: map[string]string{
|
|
"task_id": task.ID,
|
|
"source": task.Source,
|
|
},
|
|
WorkspaceMetadata: &model.WorkspaceMetadata{
|
|
Path: checkout.SlotPath,
|
|
SourceBranch: checkout.SourceBranch,
|
|
Provider: provider,
|
|
WorkItemID: workItemID,
|
|
},
|
|
}, true
|
|
}
|
|
|
|
func buildAuthoringInstructions(task storage.Task, checkout workitempipeline.CheckoutMetadata) string {
|
|
var b strings.Builder
|
|
b.WriteString("You are a workspace agent running inside a NomadCode slot checkout.\n")
|
|
b.WriteString("Your task is to author or update the agent-roadmap Milestone file for the following Plane work item.\n\n")
|
|
b.WriteString("Use the roadmap skill to create or update the Milestone under the active Phase, then commit and push to the develop branch.\n\n")
|
|
b.WriteString("Work item title: " + task.Title + "\n")
|
|
b.WriteString("Workspace slot path: " + checkout.SlotPath + "\n")
|
|
b.WriteString("Source branch: " + checkout.SourceBranch + "\n")
|
|
return b.String()
|
|
}
|