chore(repo): reinitialize repository
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
package runtimeconfig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"inbox/internal/domain/role"
|
||||
)
|
||||
|
||||
// RenderInstructions flattens the resolved system prompt and bound skills into
|
||||
// the markdown instructions that should be given to the runtime.
|
||||
func RenderInstructions(resolved ResolvedRole, fallbackSystemPrompt string) string {
|
||||
var builder strings.Builder
|
||||
|
||||
systemPrompt := strings.TrimSpace(fallbackSystemPrompt)
|
||||
if prompt, ok := resolved.Prompts[role.PromptSystem]; ok && strings.TrimSpace(prompt.ContentMarkdown) != "" {
|
||||
systemPrompt = strings.TrimSpace(prompt.ContentMarkdown)
|
||||
}
|
||||
if systemPrompt != "" {
|
||||
builder.WriteString(systemPrompt)
|
||||
}
|
||||
|
||||
if len(resolved.Skills) > 0 {
|
||||
if builder.Len() > 0 {
|
||||
builder.WriteString("\n\n")
|
||||
}
|
||||
builder.WriteString("## Skills\n")
|
||||
for _, item := range resolved.Skills {
|
||||
name := strings.TrimSpace(item.Skill.Name)
|
||||
if name == "" {
|
||||
name = strings.TrimSpace(item.Skill.SkillKey)
|
||||
}
|
||||
if name == "" {
|
||||
name = strings.TrimSpace(item.Skill.ID)
|
||||
}
|
||||
builder.WriteString(fmt.Sprintf("### %s\n%s\n", name, strings.TrimSpace(item.Skill.ContentMarkdown)))
|
||||
}
|
||||
}
|
||||
|
||||
return strings.TrimSpace(builder.String())
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package runtimeconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"inbox/internal/base/timeutil"
|
||||
"inbox/internal/domain/role"
|
||||
"inbox/internal/domain/skill"
|
||||
)
|
||||
|
||||
type Repository interface {
|
||||
GetRole(ctx context.Context, name string) (role.Definition, error)
|
||||
GetRoleConfig(ctx context.Context, roleName string) (role.Config, error)
|
||||
ListRolePrompts(ctx context.Context, roleName string) ([]role.Prompt, error)
|
||||
ListRoleSkillBindings(ctx context.Context, roleName string) ([]role.SkillBinding, error)
|
||||
}
|
||||
|
||||
type SkillRepository interface {
|
||||
ListSkillsByIDs(ctx context.Context, ids []string) (map[string]skill.Definition, error)
|
||||
}
|
||||
|
||||
type ResolvedSkill struct {
|
||||
Binding role.SkillBinding `json:"binding"`
|
||||
Skill skill.Definition `json:"skill"`
|
||||
}
|
||||
|
||||
type ResolvedRole struct {
|
||||
Role role.Definition `json:"role"`
|
||||
WorkspaceID string `json:"workspace_id,omitempty"`
|
||||
Prompts map[role.PromptKind]role.Prompt `json:"prompts"`
|
||||
Config role.Config `json:"config"`
|
||||
Skills []ResolvedSkill `json:"skills"`
|
||||
ResolvedAt string `json:"resolved_at"`
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
repo Repository
|
||||
skills SkillRepository
|
||||
clock timeutil.Clock
|
||||
}
|
||||
|
||||
func NewService(repo Repository, skills SkillRepository, clock timeutil.Clock) *Service {
|
||||
if clock == nil {
|
||||
clock = timeutil.SystemClock{}
|
||||
}
|
||||
return &Service{
|
||||
repo: repo,
|
||||
skills: skills,
|
||||
clock: clock,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) ResolveRole(ctx context.Context, workspaceID, roleName string) (ResolvedRole, error) {
|
||||
definition, err := s.repo.GetRole(ctx, roleName)
|
||||
if err != nil {
|
||||
return ResolvedRole{}, fmt.Errorf("get role %q: %w", roleName, err)
|
||||
}
|
||||
|
||||
prompts, err := s.repo.ListRolePrompts(ctx, roleName)
|
||||
if err != nil {
|
||||
return ResolvedRole{}, fmt.Errorf("list role prompts for %q: %w", roleName, err)
|
||||
}
|
||||
bindings, err := s.repo.ListRoleSkillBindings(ctx, roleName)
|
||||
if err != nil {
|
||||
return ResolvedRole{}, fmt.Errorf("list role skill bindings for %q: %w", roleName, err)
|
||||
}
|
||||
|
||||
resolved := ResolvedRole{
|
||||
Role: definition,
|
||||
WorkspaceID: workspaceID,
|
||||
Prompts: pickPrompts(prompts, workspaceID),
|
||||
ResolvedAt: timeutil.FormatRFC3339(s.clock.Now()),
|
||||
}
|
||||
config, err := s.repo.GetRoleConfig(ctx, roleName)
|
||||
if err != nil {
|
||||
return ResolvedRole{}, fmt.Errorf("get role config for %q: %w", roleName, err)
|
||||
}
|
||||
resolved.Config = config
|
||||
resolved.Config.RoleName = roleName
|
||||
|
||||
resolvedBindings := pickBindings(bindings, workspaceID)
|
||||
if len(resolvedBindings) == 0 {
|
||||
return resolved, nil
|
||||
}
|
||||
|
||||
skillIDs := make([]string, 0, len(resolvedBindings))
|
||||
for _, binding := range resolvedBindings {
|
||||
if binding.IsEnabled {
|
||||
skillIDs = append(skillIDs, binding.SkillID)
|
||||
}
|
||||
}
|
||||
skillsByID, err := s.skills.ListSkillsByIDs(ctx, skillIDs)
|
||||
if err != nil {
|
||||
return ResolvedRole{}, fmt.Errorf("list skills for %q: %w", roleName, err)
|
||||
}
|
||||
|
||||
resolved.Skills = make([]ResolvedSkill, 0, len(skillIDs))
|
||||
for _, binding := range resolvedBindings {
|
||||
if !binding.IsEnabled {
|
||||
continue
|
||||
}
|
||||
skillDef, ok := skillsByID[binding.SkillID]
|
||||
if !ok {
|
||||
return ResolvedRole{}, fmt.Errorf("missing skill %q for role %q", binding.SkillID, roleName)
|
||||
}
|
||||
resolved.Skills = append(resolved.Skills, ResolvedSkill{
|
||||
Binding: binding,
|
||||
Skill: skillDef,
|
||||
})
|
||||
}
|
||||
sort.Slice(resolved.Skills, func(i, j int) bool {
|
||||
if resolved.Skills[i].Binding.SortOrder == resolved.Skills[j].Binding.SortOrder {
|
||||
if resolved.Skills[i].Skill.Name == resolved.Skills[j].Skill.Name {
|
||||
return resolved.Skills[i].Skill.ID < resolved.Skills[j].Skill.ID
|
||||
}
|
||||
return resolved.Skills[i].Skill.Name < resolved.Skills[j].Skill.Name
|
||||
}
|
||||
return resolved.Skills[i].Binding.SortOrder < resolved.Skills[j].Binding.SortOrder
|
||||
})
|
||||
|
||||
return resolved, nil
|
||||
}
|
||||
|
||||
func (r ResolvedRole) SnapshotJSON() (string, error) {
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("marshal runtime config snapshot: %w", err)
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func pickPrompts(items []role.Prompt, workspaceID string) map[role.PromptKind]role.Prompt {
|
||||
out := make(map[role.PromptKind]role.Prompt)
|
||||
for _, item := range items {
|
||||
if item.WorkspaceID != "" && item.WorkspaceID != workspaceID {
|
||||
continue
|
||||
}
|
||||
current, exists := out[item.PromptKind]
|
||||
if !exists {
|
||||
out[item.PromptKind] = item
|
||||
continue
|
||||
}
|
||||
if current.WorkspaceID == "" && item.WorkspaceID == workspaceID {
|
||||
out[item.PromptKind] = item
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func pickBindings(items []role.SkillBinding, workspaceID string) []role.SkillBinding {
|
||||
out := make(map[string]role.SkillBinding, len(items))
|
||||
for _, item := range items {
|
||||
if item.WorkspaceID != "" && item.WorkspaceID != workspaceID {
|
||||
continue
|
||||
}
|
||||
current, exists := out[item.SkillID]
|
||||
if !exists {
|
||||
out[item.SkillID] = item
|
||||
continue
|
||||
}
|
||||
if current.WorkspaceID == "" && item.WorkspaceID == workspaceID {
|
||||
out[item.SkillID] = item
|
||||
}
|
||||
}
|
||||
result := make([]role.SkillBinding, 0, len(out))
|
||||
for _, item := range out {
|
||||
result = append(result, item)
|
||||
}
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user