Files
kandev/apps/backend/internal/office/runtime/context_builder.go
T
2026-05-16 20:07:57 +01:00

123 lines
3.1 KiB
Go

package runtime
import (
"context"
"encoding/json"
"fmt"
"github.com/kandev/kandev/internal/office/models"
"github.com/kandev/kandev/internal/office/shared"
)
// RunSnapshotStore persists runtime metadata on a run row.
type RunSnapshotStore interface {
UpdateRunRuntimeSnapshot(
ctx context.Context,
id string,
capabilities string,
inputSnapshot string,
sessionID string,
) error
}
// ContextBuilder builds the runtime context for a claimed run.
type ContextBuilder struct {
Agents shared.AgentReader
Runs RunSnapshotStore
}
// Build resolves the agent identity and capabilities for a run.
func (b *ContextBuilder) Build(ctx context.Context, run *models.Run) (RunContext, error) {
if run == nil {
return RunContext{}, fmt.Errorf("run is required")
}
if b.Agents == nil {
return RunContext{}, fmt.Errorf("%w: agents", ErrRuntimeDependencyMissing)
}
agent, err := b.Agents.GetAgentInstance(ctx, run.AgentProfileID)
if err != nil {
return RunContext{}, fmt.Errorf("resolve runtime agent: %w", err)
}
payload := parsePayload(run.Payload)
taskID := payload["task_id"]
sessionID := firstNonEmpty(run.SessionID, payload["session_id"])
caps := FromAgent(agent).WithTaskScope(taskID)
runCtx := RunContext{
WorkspaceID: agent.WorkspaceID,
AgentID: agent.ID,
TaskID: taskID,
RunID: run.ID,
SessionID: sessionID,
Reason: run.Reason,
Capabilities: caps,
}
return runCtx, nil
}
// BuildAndPersist builds context and stores its serialized snapshot on the run.
func (b *ContextBuilder) BuildAndPersist(ctx context.Context, run *models.Run) (RunContext, error) {
runCtx, err := b.Build(ctx, run)
if err != nil {
return RunContext{}, err
}
if b.Runs == nil {
return runCtx, nil
}
caps, err := MarshalCapabilities(runCtx.Capabilities)
if err != nil {
return RunContext{}, err
}
input, err := MarshalRunContext(runCtx)
if err != nil {
return RunContext{}, err
}
if err := b.Runs.UpdateRunRuntimeSnapshot(ctx, run.ID, caps, input, runCtx.SessionID); err != nil {
return RunContext{}, fmt.Errorf("persist runtime snapshot: %w", err)
}
run.Capabilities = caps
run.InputSnapshot = input
run.SessionID = runCtx.SessionID
return runCtx, nil
}
// MarshalCapabilities serializes capabilities for run snapshots and JWT claims.
func MarshalCapabilities(caps Capabilities) (string, error) {
body, err := json.Marshal(caps)
if err != nil {
return "", fmt.Errorf("marshal capabilities: %w", err)
}
return string(body), nil
}
// MarshalRunContext serializes run context for run input snapshots.
func MarshalRunContext(runCtx RunContext) (string, error) {
body, err := json.Marshal(runCtx)
if err != nil {
return "", fmt.Errorf("marshal run context: %w", err)
}
return string(body), nil
}
func parsePayload(payload string) map[string]string {
raw := map[string]any{}
if payload != "" {
_ = json.Unmarshal([]byte(payload), &raw)
}
out := make(map[string]string, len(raw))
for k, v := range raw {
if s, ok := v.(string); ok {
out[k] = s
}
}
return out
}
func firstNonEmpty(values ...string) string {
for _, value := range values {
if value != "" {
return value
}
}
return ""
}