Files
amcs/internal/store/skills.go
Hein (Warky) 1ceb317f4b
Some checks failed
CI / build-and-test (push) Failing after -29m56s
feat(skills): enhance agent skills with additional tags and commands
- Added language_tags, library_tags, framework_tags, and domain_tags to agent skills.
- Introduced new commands: get_skill and get_guardrail for fetching specific skills and guardrails.
- Updated database schema and migration scripts to accommodate new fields.
- Enhanced skill listing functionality to support filtering by new tags.
- Improved error handling and response structures for skill-related operations.
2026-05-05 09:24:58 +02:00

394 lines
12 KiB
Go

package store
import (
"context"
"fmt"
"strings"
"git.warky.dev/wdevs/amcs/internal/generatedmodels"
ext "git.warky.dev/wdevs/amcs/internal/types"
)
// Agent Skills
func (db *DB) AddSkill(ctx context.Context, skill ext.AgentSkill) (ext.AgentSkill, error) {
if skill.Tags == nil {
skill.Tags = []string{}
}
if skill.LanguageTags == nil {
skill.LanguageTags = []string{}
}
if skill.LibraryTags == nil {
skill.LibraryTags = []string{}
}
if skill.FrameworkTags == nil {
skill.FrameworkTags = []string{}
}
if skill.DomainTags == nil {
skill.DomainTags = []string{}
}
row := db.pool.QueryRow(ctx, `
insert into agent_skills (name, description, content, tags, language_tags, library_tags, framework_tags, domain_tags)
values ($1, $2, $3, $4, $5, $6, $7, $8)
returning id, guid, created_at, updated_at
`, skill.Name, skill.Description, skill.Content, skill.Tags,
skill.LanguageTags, skill.LibraryTags, skill.FrameworkTags, skill.DomainTags)
created := skill
var model generatedmodels.ModelPublicAgentSkills
if err := row.Scan(&model.ID, &model.GUID, &model.CreatedAt, &model.UpdatedAt); err != nil {
return ext.AgentSkill{}, fmt.Errorf("insert agent skill: %w", err)
}
created.ID = model.ID.Int64()
created.GUID = model.GUID.UUID()
created.CreatedAt = model.CreatedAt.Time()
created.UpdatedAt = model.UpdatedAt.Time()
return created, nil
}
func (db *DB) RemoveSkill(ctx context.Context, id int64) error {
tag, err := db.pool.Exec(ctx, `delete from agent_skills where id = $1`, id)
if err != nil {
return fmt.Errorf("delete agent skill: %w", err)
}
if tag.RowsAffected() == 0 {
return fmt.Errorf("skill not found")
}
return nil
}
func (db *DB) ListSkills(ctx context.Context, tag string) ([]ext.AgentSkill, error) {
q := `select id, name, description, content, tags::text[], language_tags::text[], library_tags::text[], framework_tags::text[], domain_tags::text[], created_at, updated_at from agent_skills`
args := []any{}
if t := strings.TrimSpace(tag); t != "" {
args = append(args, t)
q += fmt.Sprintf(" where $%d = any(tags) or $%d = any(language_tags) or $%d = any(library_tags) or $%d = any(framework_tags) or $%d = any(domain_tags)", len(args), len(args), len(args), len(args), len(args))
}
q += " order by name"
rows, err := db.pool.Query(ctx, q, args...)
if err != nil {
return nil, fmt.Errorf("list agent skills: %w", err)
}
defer rows.Close()
var skills []ext.AgentSkill
for rows.Next() {
s, err := scanSkill(rows)
if err != nil {
return nil, fmt.Errorf("scan agent skill: %w", err)
}
skills = append(skills, s)
}
return skills, rows.Err()
}
const skillSelectCols = `id, name, description, content, tags::text[], language_tags::text[], library_tags::text[], framework_tags::text[], domain_tags::text[], created_at, updated_at`
type skillScanner interface {
Scan(dest ...any) error
}
func scanSkill(row skillScanner) (ext.AgentSkill, error) {
var model generatedmodels.ModelPublicAgentSkills
var tags, langTags, libTags, fwTags, domTags []string
if err := row.Scan(&model.ID, &model.Name, &model.Description, &model.Content, &tags, &langTags, &libTags, &fwTags, &domTags, &model.CreatedAt, &model.UpdatedAt); err != nil {
return ext.AgentSkill{}, err
}
nilToEmpty := func(s []string) []string {
if s == nil {
return []string{}
}
return s
}
return ext.AgentSkill{
ID: model.ID.Int64(),
Name: model.Name.String(),
Description: model.Description.String(),
Content: model.Content.String(),
Tags: nilToEmpty(tags),
LanguageTags: nilToEmpty(langTags),
LibraryTags: nilToEmpty(libTags),
FrameworkTags: nilToEmpty(fwTags),
DomainTags: nilToEmpty(domTags),
CreatedAt: model.CreatedAt.Time(),
UpdatedAt: model.UpdatedAt.Time(),
}, nil
}
func (db *DB) GetSkill(ctx context.Context, id int64) (ext.AgentSkill, error) {
row := db.pool.QueryRow(ctx, `select `+skillSelectCols+` from agent_skills where id = $1`, id)
s, err := scanSkill(row)
if err != nil {
return ext.AgentSkill{}, fmt.Errorf("get agent skill: %w", err)
}
return s, nil
}
func (db *DB) GetSkillByName(ctx context.Context, name string) (ext.AgentSkill, error) {
row := db.pool.QueryRow(ctx, `select `+skillSelectCols+` from agent_skills where name = $1`, name)
s, err := scanSkill(row)
if err != nil {
return ext.AgentSkill{}, fmt.Errorf("get agent skill by name: %w", err)
}
return s, nil
}
func (db *DB) GetGuardrailByName(ctx context.Context, name string) (ext.AgentGuardrail, error) {
row := db.pool.QueryRow(ctx, `
select id, name, description, content, severity, tags::text[], created_at, updated_at
from agent_guardrails where name = $1
`, name)
var model generatedmodels.ModelPublicAgentGuardrails
var tags []string
if err := row.Scan(&model.ID, &model.Name, &model.Description, &model.Content, &model.Severity, &tags, &model.CreatedAt, &model.UpdatedAt); err != nil {
return ext.AgentGuardrail{}, fmt.Errorf("get agent guardrail by name: %w", err)
}
g := ext.AgentGuardrail{
ID: model.ID.Int64(),
Name: model.Name.String(),
Description: model.Description.String(),
Content: model.Content.String(),
Severity: model.Severity.String(),
Tags: tags,
CreatedAt: model.CreatedAt.Time(),
UpdatedAt: model.UpdatedAt.Time(),
}
if g.Tags == nil {
g.Tags = []string{}
}
return g, nil
}
// Agent Guardrails
func (db *DB) AddGuardrail(ctx context.Context, g ext.AgentGuardrail) (ext.AgentGuardrail, error) {
if g.Tags == nil {
g.Tags = []string{}
}
if g.Severity == "" {
g.Severity = "medium"
}
row := db.pool.QueryRow(ctx, `
insert into agent_guardrails (name, description, content, severity, tags)
values ($1, $2, $3, $4, $5)
returning id, guid, created_at, updated_at
`, g.Name, g.Description, g.Content, g.Severity, g.Tags)
created := g
var model generatedmodels.ModelPublicAgentGuardrails
if err := row.Scan(&model.ID, &model.GUID, &model.CreatedAt, &model.UpdatedAt); err != nil {
return ext.AgentGuardrail{}, fmt.Errorf("insert agent guardrail: %w", err)
}
created.ID = model.ID.Int64()
created.GUID = model.GUID.UUID()
created.CreatedAt = model.CreatedAt.Time()
created.UpdatedAt = model.UpdatedAt.Time()
return created, nil
}
func (db *DB) RemoveGuardrail(ctx context.Context, id int64) error {
tag, err := db.pool.Exec(ctx, `delete from agent_guardrails where id = $1`, id)
if err != nil {
return fmt.Errorf("delete agent guardrail: %w", err)
}
if tag.RowsAffected() == 0 {
return fmt.Errorf("guardrail not found")
}
return nil
}
func (db *DB) ListGuardrails(ctx context.Context, tag, severity string) ([]ext.AgentGuardrail, error) {
args := []any{}
conditions := []string{}
if t := strings.TrimSpace(tag); t != "" {
args = append(args, t)
conditions = append(conditions, fmt.Sprintf("$%d = any(tags)", len(args)))
}
if s := strings.TrimSpace(severity); s != "" {
args = append(args, s)
conditions = append(conditions, fmt.Sprintf("severity = $%d", len(args)))
}
q := `select id, name, description, content, severity, tags::text[], created_at, updated_at from agent_guardrails`
if len(conditions) > 0 {
q += " where " + strings.Join(conditions, " and ")
}
q += " order by name"
rows, err := db.pool.Query(ctx, q, args...)
if err != nil {
return nil, fmt.Errorf("list agent guardrails: %w", err)
}
defer rows.Close()
var guardrails []ext.AgentGuardrail
for rows.Next() {
var model generatedmodels.ModelPublicAgentGuardrails
var tags []string
if err := rows.Scan(&model.ID, &model.Name, &model.Description, &model.Content, &model.Severity, &tags, &model.CreatedAt, &model.UpdatedAt); err != nil {
return nil, fmt.Errorf("scan agent guardrail: %w", err)
}
g := ext.AgentGuardrail{
ID: model.ID.Int64(),
Name: model.Name.String(),
Description: model.Description.String(),
Content: model.Content.String(),
Severity: model.Severity.String(),
Tags: tags,
CreatedAt: model.CreatedAt.Time(),
UpdatedAt: model.UpdatedAt.Time(),
}
if g.Tags == nil {
g.Tags = []string{}
}
guardrails = append(guardrails, g)
}
return guardrails, rows.Err()
}
func (db *DB) GetGuardrail(ctx context.Context, id int64) (ext.AgentGuardrail, error) {
row := db.pool.QueryRow(ctx, `
select id, name, description, content, severity, tags::text[], created_at, updated_at
from agent_guardrails where id = $1
`, id)
var model generatedmodels.ModelPublicAgentGuardrails
var tags []string
if err := row.Scan(&model.ID, &model.Name, &model.Description, &model.Content, &model.Severity, &tags, &model.CreatedAt, &model.UpdatedAt); err != nil {
return ext.AgentGuardrail{}, fmt.Errorf("get agent guardrail: %w", err)
}
g := ext.AgentGuardrail{
ID: model.ID.Int64(),
Name: model.Name.String(),
Description: model.Description.String(),
Content: model.Content.String(),
Severity: model.Severity.String(),
Tags: tags,
CreatedAt: model.CreatedAt.Time(),
UpdatedAt: model.UpdatedAt.Time(),
}
if g.Tags == nil {
g.Tags = []string{}
}
return g, nil
}
// Project Skills
func (db *DB) AddProjectSkill(ctx context.Context, projectID, skillID int64) error {
_, err := db.pool.Exec(ctx, `
insert into project_skills (project_id, skill_id)
values ($1, $2)
on conflict do nothing
`, projectID, skillID)
if err != nil {
return fmt.Errorf("add project skill: %w", err)
}
return nil
}
func (db *DB) RemoveProjectSkill(ctx context.Context, projectID, skillID int64) error {
tag, err := db.pool.Exec(ctx, `
delete from project_skills where project_id = $1 and skill_id = $2
`, projectID, skillID)
if err != nil {
return fmt.Errorf("remove project skill: %w", err)
}
if tag.RowsAffected() == 0 {
return fmt.Errorf("project skill link not found")
}
return nil
}
func (db *DB) ListProjectSkills(ctx context.Context, projectID int64) ([]ext.AgentSkill, error) {
rows, err := db.pool.Query(ctx, `
select s.`+skillSelectCols+`
from agent_skills s
join project_skills ps on ps.skill_id = s.id
where ps.project_id = $1
order by s.name
`, projectID)
if err != nil {
return nil, fmt.Errorf("list project skills: %w", err)
}
defer rows.Close()
var skills []ext.AgentSkill
for rows.Next() {
s, err := scanSkill(rows)
if err != nil {
return nil, fmt.Errorf("scan project skill: %w", err)
}
skills = append(skills, s)
}
return skills, rows.Err()
}
// Project Guardrails
func (db *DB) AddProjectGuardrail(ctx context.Context, projectID, guardrailID int64) error {
_, err := db.pool.Exec(ctx, `
insert into project_guardrails (project_id, guardrail_id)
values ($1, $2)
on conflict do nothing
`, projectID, guardrailID)
if err != nil {
return fmt.Errorf("add project guardrail: %w", err)
}
return nil
}
func (db *DB) RemoveProjectGuardrail(ctx context.Context, projectID, guardrailID int64) error {
tag, err := db.pool.Exec(ctx, `
delete from project_guardrails where project_id = $1 and guardrail_id = $2
`, projectID, guardrailID)
if err != nil {
return fmt.Errorf("remove project guardrail: %w", err)
}
if tag.RowsAffected() == 0 {
return fmt.Errorf("project guardrail link not found")
}
return nil
}
func (db *DB) ListProjectGuardrails(ctx context.Context, projectID int64) ([]ext.AgentGuardrail, error) {
rows, err := db.pool.Query(ctx, `
select g.id, g.name, g.description, g.content, g.severity, g.tags::text[], g.created_at, g.updated_at
from agent_guardrails g
join project_guardrails pg on pg.guardrail_id = g.id
where pg.project_id = $1
order by g.name
`, projectID)
if err != nil {
return nil, fmt.Errorf("list project guardrails: %w", err)
}
defer rows.Close()
var guardrails []ext.AgentGuardrail
for rows.Next() {
var model generatedmodels.ModelPublicAgentGuardrails
var tags []string
if err := rows.Scan(&model.ID, &model.Name, &model.Description, &model.Content, &model.Severity, &tags, &model.CreatedAt, &model.UpdatedAt); err != nil {
return nil, fmt.Errorf("scan project guardrail: %w", err)
}
g := ext.AgentGuardrail{
ID: model.ID.Int64(),
Name: model.Name.String(),
Description: model.Description.String(),
Content: model.Content.String(),
Severity: model.Severity.String(),
Tags: tags,
CreatedAt: model.CreatedAt.Time(),
UpdatedAt: model.UpdatedAt.Time(),
}
if g.Tags == nil {
g.Tags = []string{}
}
guardrails = append(guardrails, g)
}
return guardrails, rows.Err()
}