mirror of
https://github.com/coder/coder.git
synced 2026-10-16 14:48:56 +08:00
feat: add skill action and automatic-use settings
Skills gain two independent settings. action marks a project skill as a one-click action above project composers; a check constraint keeps it on project skills, and personal and organization routes reject the field. model_invocable controls whether agents see and load a skill on their own: skills with it off are left out of <available-skills> and read_skill, but structured skill invocations still load them.
This commit is contained in:
Generated
+31
@@ -24171,9 +24171,17 @@ const docTemplate = `{
|
||||
"codersdk.CreateSkillRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {
|
||||
"description": "Action is only accepted for project skills and defaults to false.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"content": {
|
||||
"description": "Content must be SKILL.md-format Markdown with YAML frontmatter. The\nfrontmatter must include name, may include description, and must be\nfollowed by a non-empty body.",
|
||||
"type": "string"
|
||||
},
|
||||
"model_invocable": {
|
||||
"description": "ModelInvocable defaults to true.",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -30516,6 +30524,10 @@ const docTemplate = `{
|
||||
"codersdk.Skill": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {
|
||||
"description": "Action reports whether a project skill is offered as a one-click\naction above project composers. It is always false for personal and\norganization skills.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -30533,6 +30545,10 @@ const docTemplate = `{
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"model_invocable": {
|
||||
"description": "ModelInvocable reports whether agents can discover and load the skill\non their own. Users can still invoke a skill explicitly when it is\nfalse.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -30545,6 +30561,10 @@ const docTemplate = `{
|
||||
"codersdk.SkillMetadata": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {
|
||||
"description": "Action reports whether a project skill is offered as a one-click\naction above project composers. It is always false for personal and\norganization skills.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
@@ -30559,6 +30579,10 @@ const docTemplate = `{
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"model_invocable": {
|
||||
"description": "ModelInvocable reports whether agents can discover and load the skill\non their own. Users can still invoke a skill explicitly when it is\nfalse.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -32402,12 +32426,19 @@ const docTemplate = `{
|
||||
"codersdk.UpdateSkillRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {
|
||||
"description": "Action is only accepted for project skills.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"content": {
|
||||
"description": "Content must be SKILL.md-format Markdown with YAML frontmatter. The\nfrontmatter must include name, may include description, and must be\nfollowed by a non-empty body.",
|
||||
"type": "string"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"model_invocable": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Generated
+31
@@ -21962,9 +21962,17 @@
|
||||
"codersdk.CreateSkillRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {
|
||||
"description": "Action is only accepted for project skills and defaults to false.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"content": {
|
||||
"description": "Content must be SKILL.md-format Markdown with YAML frontmatter. The\nfrontmatter must include name, may include description, and must be\nfollowed by a non-empty body.",
|
||||
"type": "string"
|
||||
},
|
||||
"model_invocable": {
|
||||
"description": "ModelInvocable defaults to true.",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -28064,6 +28072,10 @@
|
||||
"codersdk.Skill": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {
|
||||
"description": "Action reports whether a project skill is offered as a one-click\naction above project composers. It is always false for personal and\norganization skills.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -28081,6 +28093,10 @@
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"model_invocable": {
|
||||
"description": "ModelInvocable reports whether agents can discover and load the skill\non their own. Users can still invoke a skill explicitly when it is\nfalse.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -28093,6 +28109,10 @@
|
||||
"codersdk.SkillMetadata": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {
|
||||
"description": "Action reports whether a project skill is offered as a one-click\naction above project composers. It is always false for personal and\norganization skills.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
@@ -28107,6 +28127,10 @@
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"model_invocable": {
|
||||
"description": "ModelInvocable reports whether agents can discover and load the skill\non their own. Users can still invoke a skill explicitly when it is\nfalse.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -29862,12 +29886,19 @@
|
||||
"codersdk.UpdateSkillRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {
|
||||
"description": "Action is only accepted for project skills.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"content": {
|
||||
"description": "Content must be SKILL.md-format Markdown with YAML frontmatter. The\nfrontmatter must include name, may include description, and must be\nfollowed by a non-empty body.",
|
||||
"type": "string"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"model_invocable": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -77,13 +77,15 @@ func (api *API) postChatProjectSkill(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
defer commitAudit()
|
||||
|
||||
skill, ok := createSkill(ctx, rw, r, chatProjectSkillCreateErrors, func(parsed skills.ParsedSkill, content string) (database.Skill, error) {
|
||||
skill, ok := createSkill(ctx, rw, r, chatProjectSkillCreateErrors, skillOwnerProject, func(parsed skills.ParsedSkill, create skillCreate) (database.Skill, error) {
|
||||
return api.Database.InsertProjectSkill(ctx, database.InsertProjectSkillParams{
|
||||
ID: uuid.New(),
|
||||
ProjectID: project.ID,
|
||||
Name: parsed.Name,
|
||||
Description: parsed.Description,
|
||||
Content: content,
|
||||
ID: uuid.New(),
|
||||
ProjectID: project.ID,
|
||||
Name: parsed.Name,
|
||||
Description: parsed.Description,
|
||||
Content: create.Content,
|
||||
Action: create.Action,
|
||||
ModelInvocable: create.ModelInvocable,
|
||||
})
|
||||
})
|
||||
if !ok {
|
||||
@@ -150,7 +152,7 @@ func (api *API) patchChatProjectSkill(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
defer commitAudit()
|
||||
|
||||
oldSkill, skill, ok := api.updateSkill(ctx, rw, r, name, skillWriteErrors{}, func(tx database.Store, update skillUpdate) (database.Skill, database.Skill, error) {
|
||||
oldSkill, skill, ok := api.updateSkill(ctx, rw, r, name, skillWriteErrors{}, skillOwnerProject, func(tx database.Store, update skillUpdate) (database.Skill, database.Skill, error) {
|
||||
// Update on the project was checked above and the update below
|
||||
// rechecks it, so reads use a system context for update-only roles.
|
||||
//nolint:gocritic // See above.
|
||||
@@ -168,11 +170,13 @@ func (api *API) patchChatProjectSkill(rw http.ResponseWriter, r *http.Request) {
|
||||
return database.Skill{}, database.Skill{}, xerrors.Errorf("lock project skill: %w", err)
|
||||
}
|
||||
updated, err := tx.UpdateProjectSkillByProjectIDAndName(ctx, database.UpdateProjectSkillByProjectIDAndNameParams{
|
||||
Description: update.Description,
|
||||
Content: update.Content,
|
||||
Enabled: update.Enabled,
|
||||
ProjectID: project.ID,
|
||||
Name: locked.Name,
|
||||
Description: update.Description,
|
||||
Content: update.Content,
|
||||
Enabled: update.Enabled,
|
||||
Action: update.Action,
|
||||
ModelInvocable: update.ModelInvocable,
|
||||
ProjectID: project.ID,
|
||||
Name: locked.Name,
|
||||
})
|
||||
if err != nil {
|
||||
return database.Skill{}, database.Skill{}, xerrors.Errorf("update project skill: %w", err)
|
||||
|
||||
@@ -25,9 +25,15 @@ func TestChatProjectSkills(t *testing.T) {
|
||||
member := codersdk.NewExperimentalClient(memberRaw)
|
||||
content := userSkillMarkdown("deploy", "Deploy the app", "Run make deploy.")
|
||||
|
||||
created, err := client.CreateChatProjectSkill(ctx, orgID, project.ID, codersdk.CreateSkillRequest{Content: content})
|
||||
created, err := client.CreateChatProjectSkill(ctx, orgID, project.ID, codersdk.CreateSkillRequest{
|
||||
Content: content,
|
||||
Action: ptr.Ref(true),
|
||||
ModelInvocable: ptr.Ref(false),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "Deploy the app", created.Description)
|
||||
require.True(t, created.Action)
|
||||
require.False(t, created.ModelInvocable)
|
||||
list, err := client.ChatProjectSkills(ctx, orgID, project.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []codersdk.SkillMetadata{created.SkillMetadata}, list)
|
||||
@@ -39,6 +45,17 @@ func TestChatProjectSkills(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "Ship the app", updated.Description)
|
||||
require.False(t, updated.Enabled)
|
||||
// Omitted settings keep their values, and set ones flip independently.
|
||||
require.True(t, updated.Action)
|
||||
require.False(t, updated.ModelInvocable)
|
||||
updated, err = client.UpdateChatProjectSkill(ctx, orgID, project.ID, "deploy", codersdk.UpdateSkillRequest{
|
||||
Action: ptr.Ref(false),
|
||||
ModelInvocable: ptr.Ref(true),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.False(t, updated.Action)
|
||||
require.True(t, updated.ModelInvocable)
|
||||
require.Equal(t, "Ship the app", updated.Description)
|
||||
|
||||
_, err = client.CreateChatProjectSkill(ctx, orgID, project.ID, codersdk.CreateSkillRequest{Content: content})
|
||||
requireSDKErrorStatus(t, err, http.StatusConflict)
|
||||
|
||||
Generated
+1
@@ -69,6 +69,7 @@ const (
|
||||
CheckMaxProvisionerLogsLength CheckConstraint = "max_provisioner_logs_length" // provisioner_jobs
|
||||
CheckNatsPortValidTcp CheckConstraint = "nats_port_valid_tcp" // replicas
|
||||
CheckSkillsAclOnlyOnOrganizationSkills CheckConstraint = "skills_acl_only_on_organization_skills" // skills
|
||||
CheckSkillsActionProjectOnly CheckConstraint = "skills_action_project_only" // skills
|
||||
CheckSkillsContentSize CheckConstraint = "skills_content_size" // skills
|
||||
CheckSkillsDescriptionSize CheckConstraint = "skills_description_size" // skills
|
||||
CheckSkillsGroupAclIsObject CheckConstraint = "skills_group_acl_is_object" // skills
|
||||
|
||||
@@ -2430,12 +2430,14 @@ func UserSecrets(secrets []database.ListUserSecretsRow) []codersdk.UserSecret {
|
||||
func Skill(skill database.Skill) codersdk.Skill {
|
||||
return codersdk.Skill{
|
||||
SkillMetadata: codersdk.SkillMetadata{
|
||||
ID: skill.ID,
|
||||
Name: skill.Name,
|
||||
Description: skill.Description,
|
||||
Enabled: skill.Enabled,
|
||||
CreatedAt: skill.CreatedAt,
|
||||
UpdatedAt: skill.UpdatedAt,
|
||||
ID: skill.ID,
|
||||
Name: skill.Name,
|
||||
Description: skill.Description,
|
||||
Enabled: skill.Enabled,
|
||||
Action: skill.Action,
|
||||
ModelInvocable: skill.ModelInvocable,
|
||||
CreatedAt: skill.CreatedAt,
|
||||
UpdatedAt: skill.UpdatedAt,
|
||||
},
|
||||
Content: skill.Content,
|
||||
}
|
||||
@@ -2447,12 +2449,14 @@ func ProjectSkillMetadataList(rows []database.ListProjectSkillMetadataByProjectI
|
||||
metadata := make([]codersdk.SkillMetadata, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
metadata = append(metadata, codersdk.SkillMetadata{
|
||||
ID: row.ID,
|
||||
Name: row.Name,
|
||||
Description: row.Description,
|
||||
Enabled: row.Enabled,
|
||||
CreatedAt: row.CreatedAt,
|
||||
UpdatedAt: row.UpdatedAt,
|
||||
ID: row.ID,
|
||||
Name: row.Name,
|
||||
Description: row.Description,
|
||||
Enabled: row.Enabled,
|
||||
Action: row.Action,
|
||||
ModelInvocable: row.ModelInvocable,
|
||||
CreatedAt: row.CreatedAt,
|
||||
UpdatedAt: row.UpdatedAt,
|
||||
})
|
||||
}
|
||||
return metadata
|
||||
@@ -2464,12 +2468,14 @@ func OrganizationSkillMetadataList(rows []database.ListOrganizationSkillMetadata
|
||||
metadata := make([]codersdk.SkillMetadata, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
metadata = append(metadata, codersdk.SkillMetadata{
|
||||
ID: row.ID,
|
||||
Name: row.Name,
|
||||
Description: row.Description,
|
||||
Enabled: row.Enabled,
|
||||
CreatedAt: row.CreatedAt,
|
||||
UpdatedAt: row.UpdatedAt,
|
||||
ID: row.ID,
|
||||
Name: row.Name,
|
||||
Description: row.Description,
|
||||
Enabled: row.Enabled,
|
||||
Action: row.Action,
|
||||
ModelInvocable: row.ModelInvocable,
|
||||
CreatedAt: row.CreatedAt,
|
||||
UpdatedAt: row.UpdatedAt,
|
||||
})
|
||||
}
|
||||
return metadata
|
||||
@@ -2480,12 +2486,14 @@ func UserSkillMetadataList(rows []database.ListUserSkillMetadataByUserIDRow) []c
|
||||
metadata := make([]codersdk.SkillMetadata, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
metadata = append(metadata, codersdk.SkillMetadata{
|
||||
ID: row.ID,
|
||||
Name: row.Name,
|
||||
Description: row.Description,
|
||||
Enabled: row.Enabled,
|
||||
CreatedAt: row.CreatedAt,
|
||||
UpdatedAt: row.UpdatedAt,
|
||||
ID: row.ID,
|
||||
Name: row.Name,
|
||||
Description: row.Description,
|
||||
Enabled: row.Enabled,
|
||||
Action: row.Action,
|
||||
ModelInvocable: row.ModelInvocable,
|
||||
CreatedAt: row.CreatedAt,
|
||||
UpdatedAt: row.UpdatedAt,
|
||||
})
|
||||
}
|
||||
return metadata
|
||||
|
||||
@@ -1177,7 +1177,7 @@ func Organization(t testing.TB, db database.Store, orig database.Organization) d
|
||||
}
|
||||
|
||||
// OrganizationSkill inserts a skill owned by seed.OrganizationID, which is
|
||||
// required. Inserted skills are enabled.
|
||||
// required. Inserted skills are enabled and model-invocable.
|
||||
func OrganizationSkill(t testing.TB, db database.Store, seed database.Skill) database.Skill {
|
||||
t.Helper()
|
||||
|
||||
@@ -1190,6 +1190,7 @@ func OrganizationSkill(t testing.TB, db database.Store, seed database.Skill) dat
|
||||
Name: name,
|
||||
Description: description,
|
||||
Content: takeFirst(seed.Content, fmt.Sprintf("---\nname: %s\ndescription: %s\n---\n\nFixture instructions.\n", name, description)),
|
||||
ModelInvocable: sql.NullBool{},
|
||||
GroupACL: seed.GroupACL,
|
||||
UserACL: seed.UserACL,
|
||||
})
|
||||
|
||||
Generated
+3
@@ -3194,7 +3194,10 @@ CREATE TABLE skills (
|
||||
enabled boolean DEFAULT true NOT NULL,
|
||||
group_acl jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
user_acl jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
action boolean DEFAULT false NOT NULL,
|
||||
model_invocable boolean DEFAULT true NOT NULL,
|
||||
CONSTRAINT skills_acl_only_on_organization_skills CHECK (((organization_id IS NOT NULL) OR ((group_acl = '{}'::jsonb) AND (user_acl = '{}'::jsonb)))),
|
||||
CONSTRAINT skills_action_project_only CHECK (((NOT action) OR (project_id IS NOT NULL))),
|
||||
CONSTRAINT skills_content_size CHECK ((octet_length(content) <= 65536)),
|
||||
CONSTRAINT skills_description_size CHECK ((octet_length(description) <= 4096)),
|
||||
CONSTRAINT skills_group_acl_is_object CHECK ((jsonb_typeof(group_acl) = 'object'::text)),
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
ALTER TABLE skills
|
||||
DROP CONSTRAINT skills_action_project_only,
|
||||
DROP COLUMN model_invocable,
|
||||
DROP COLUMN action;
|
||||
@@ -0,0 +1,7 @@
|
||||
-- action marks a project skill as a one-click action above project
|
||||
-- composers. model_invocable controls whether agents see and load the skill
|
||||
-- on their own; users can still invoke manual-only skills explicitly.
|
||||
ALTER TABLE skills
|
||||
ADD COLUMN action boolean NOT NULL DEFAULT false,
|
||||
ADD COLUMN model_invocable boolean NOT NULL DEFAULT true,
|
||||
ADD CONSTRAINT skills_action_project_only CHECK (NOT action OR project_id IS NOT NULL);
|
||||
@@ -1384,6 +1384,8 @@ func (q *sqlQuerier) GetAuthorizedOrganizationSkillMetadata(ctx context.Context,
|
||||
&i.Name,
|
||||
&i.Description,
|
||||
&i.Enabled,
|
||||
&i.Action,
|
||||
&i.ModelInvocable,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
|
||||
Generated
+2
@@ -6119,6 +6119,8 @@ type Skill struct {
|
||||
Enabled bool `db:"enabled" json:"enabled"`
|
||||
GroupACL ChatACL `db:"group_acl" json:"group_acl"`
|
||||
UserACL ChatACL `db:"user_acl" json:"user_acl"`
|
||||
Action bool `db:"action" json:"action"`
|
||||
ModelInvocable bool `db:"model_invocable" json:"model_invocable"`
|
||||
}
|
||||
|
||||
// We keep this separate from replicas in case we need to break the coordinator out into its own service
|
||||
|
||||
Generated
+130
-68
@@ -28667,7 +28667,7 @@ func (q *sqlQuerier) UpsertWebpushVAPIDKeys(ctx context.Context, arg UpsertWebpu
|
||||
const deleteOrganizationSkillByOrganizationIDAndName = `-- name: DeleteOrganizationSkillByOrganizationIDAndName :one
|
||||
DELETE FROM skills
|
||||
WHERE organization_id = $1::uuid AND name = $2
|
||||
RETURNING id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl
|
||||
RETURNING id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl, action, model_invocable
|
||||
`
|
||||
|
||||
type DeleteOrganizationSkillByOrganizationIDAndNameParams struct {
|
||||
@@ -28691,6 +28691,8 @@ func (q *sqlQuerier) DeleteOrganizationSkillByOrganizationIDAndName(ctx context.
|
||||
&i.Enabled,
|
||||
&i.GroupACL,
|
||||
&i.UserACL,
|
||||
&i.Action,
|
||||
&i.ModelInvocable,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -28698,7 +28700,7 @@ func (q *sqlQuerier) DeleteOrganizationSkillByOrganizationIDAndName(ctx context.
|
||||
const deleteProjectSkillByProjectIDAndName = `-- name: DeleteProjectSkillByProjectIDAndName :one
|
||||
DELETE FROM skills
|
||||
WHERE project_id = $1::uuid AND name = $2
|
||||
RETURNING id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl
|
||||
RETURNING id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl, action, model_invocable
|
||||
`
|
||||
|
||||
type DeleteProjectSkillByProjectIDAndNameParams struct {
|
||||
@@ -28722,6 +28724,8 @@ func (q *sqlQuerier) DeleteProjectSkillByProjectIDAndName(ctx context.Context, a
|
||||
&i.Enabled,
|
||||
&i.GroupACL,
|
||||
&i.UserACL,
|
||||
&i.Action,
|
||||
&i.ModelInvocable,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -28729,7 +28733,7 @@ func (q *sqlQuerier) DeleteProjectSkillByProjectIDAndName(ctx context.Context, a
|
||||
const deleteUserSkillByUserIDAndName = `-- name: DeleteUserSkillByUserIDAndName :one
|
||||
DELETE FROM skills
|
||||
WHERE user_id = $1::uuid AND name = $2
|
||||
RETURNING id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl
|
||||
RETURNING id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl, action, model_invocable
|
||||
`
|
||||
|
||||
type DeleteUserSkillByUserIDAndNameParams struct {
|
||||
@@ -28753,12 +28757,14 @@ func (q *sqlQuerier) DeleteUserSkillByUserIDAndName(ctx context.Context, arg Del
|
||||
&i.Enabled,
|
||||
&i.GroupACL,
|
||||
&i.UserACL,
|
||||
&i.Action,
|
||||
&i.ModelInvocable,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getOrganizationSkillByIDForUpdate = `-- name: GetOrganizationSkillByIDForUpdate :one
|
||||
SELECT id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl
|
||||
SELECT id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl, action, model_invocable
|
||||
FROM skills
|
||||
WHERE id = $1::uuid AND organization_id IS NOT NULL
|
||||
FOR UPDATE
|
||||
@@ -28780,12 +28786,14 @@ func (q *sqlQuerier) GetOrganizationSkillByIDForUpdate(ctx context.Context, id u
|
||||
&i.Enabled,
|
||||
&i.GroupACL,
|
||||
&i.UserACL,
|
||||
&i.Action,
|
||||
&i.ModelInvocable,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getOrganizationSkillByOrganizationIDAndName = `-- name: GetOrganizationSkillByOrganizationIDAndName :one
|
||||
SELECT id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl
|
||||
SELECT id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl, action, model_invocable
|
||||
FROM skills
|
||||
WHERE organization_id = $1::uuid AND name = $2
|
||||
`
|
||||
@@ -28811,12 +28819,14 @@ func (q *sqlQuerier) GetOrganizationSkillByOrganizationIDAndName(ctx context.Con
|
||||
&i.Enabled,
|
||||
&i.GroupACL,
|
||||
&i.UserACL,
|
||||
&i.Action,
|
||||
&i.ModelInvocable,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getProjectSkillByIDForUpdate = `-- name: GetProjectSkillByIDForUpdate :one
|
||||
SELECT id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl
|
||||
SELECT id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl, action, model_invocable
|
||||
FROM skills
|
||||
WHERE id = $1::uuid AND project_id IS NOT NULL
|
||||
FOR UPDATE
|
||||
@@ -28838,12 +28848,14 @@ func (q *sqlQuerier) GetProjectSkillByIDForUpdate(ctx context.Context, id uuid.U
|
||||
&i.Enabled,
|
||||
&i.GroupACL,
|
||||
&i.UserACL,
|
||||
&i.Action,
|
||||
&i.ModelInvocable,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getProjectSkillByProjectIDAndName = `-- name: GetProjectSkillByProjectIDAndName :one
|
||||
SELECT id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl
|
||||
SELECT id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl, action, model_invocable
|
||||
FROM skills
|
||||
WHERE project_id = $1::uuid AND name = $2
|
||||
`
|
||||
@@ -28869,12 +28881,14 @@ func (q *sqlQuerier) GetProjectSkillByProjectIDAndName(ctx context.Context, arg
|
||||
&i.Enabled,
|
||||
&i.GroupACL,
|
||||
&i.UserACL,
|
||||
&i.Action,
|
||||
&i.ModelInvocable,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getUserSkillByUserIDAndName = `-- name: GetUserSkillByUserIDAndName :one
|
||||
SELECT id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl
|
||||
SELECT id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl, action, model_invocable
|
||||
FROM skills
|
||||
WHERE user_id = $1::uuid AND name = $2
|
||||
`
|
||||
@@ -28900,24 +28914,27 @@ func (q *sqlQuerier) GetUserSkillByUserIDAndName(ctx context.Context, arg GetUse
|
||||
&i.Enabled,
|
||||
&i.GroupACL,
|
||||
&i.UserACL,
|
||||
&i.Action,
|
||||
&i.ModelInvocable,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const insertOrganizationSkill = `-- name: InsertOrganizationSkill :one
|
||||
INSERT INTO skills (id, organization_id, name, description, content, group_acl, user_acl)
|
||||
VALUES ($1::uuid, $2::uuid, $3::text, $4::text, $5::text, $6, $7)
|
||||
RETURNING id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl
|
||||
INSERT INTO skills (id, organization_id, name, description, content, model_invocable, group_acl, user_acl)
|
||||
VALUES ($1::uuid, $2::uuid, $3::text, $4::text, $5::text, COALESCE($6::boolean, true), $7, $8)
|
||||
RETURNING id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl, action, model_invocable
|
||||
`
|
||||
|
||||
type InsertOrganizationSkillParams struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Description string `db:"description" json:"description"`
|
||||
Content string `db:"content" json:"content"`
|
||||
GroupACL ChatACL `db:"group_acl" json:"group_acl"`
|
||||
UserACL ChatACL `db:"user_acl" json:"user_acl"`
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Description string `db:"description" json:"description"`
|
||||
Content string `db:"content" json:"content"`
|
||||
ModelInvocable sql.NullBool `db:"model_invocable" json:"model_invocable"`
|
||||
GroupACL ChatACL `db:"group_acl" json:"group_acl"`
|
||||
UserACL ChatACL `db:"user_acl" json:"user_acl"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertOrganizationSkill(ctx context.Context, arg InsertOrganizationSkillParams) (Skill, error) {
|
||||
@@ -28927,6 +28944,7 @@ func (q *sqlQuerier) InsertOrganizationSkill(ctx context.Context, arg InsertOrga
|
||||
arg.Name,
|
||||
arg.Description,
|
||||
arg.Content,
|
||||
arg.ModelInvocable,
|
||||
arg.GroupACL,
|
||||
arg.UserACL,
|
||||
)
|
||||
@@ -28944,22 +28962,26 @@ func (q *sqlQuerier) InsertOrganizationSkill(ctx context.Context, arg InsertOrga
|
||||
&i.Enabled,
|
||||
&i.GroupACL,
|
||||
&i.UserACL,
|
||||
&i.Action,
|
||||
&i.ModelInvocable,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const insertProjectSkill = `-- name: InsertProjectSkill :one
|
||||
INSERT INTO skills (id, project_id, name, description, content)
|
||||
VALUES ($1::uuid, $2::uuid, $3::text, $4::text, $5::text)
|
||||
RETURNING id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl
|
||||
INSERT INTO skills (id, project_id, name, description, content, action, model_invocable)
|
||||
VALUES ($1::uuid, $2::uuid, $3::text, $4::text, $5::text, COALESCE($6::boolean, false), COALESCE($7::boolean, true))
|
||||
RETURNING id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl, action, model_invocable
|
||||
`
|
||||
|
||||
type InsertProjectSkillParams struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
ProjectID uuid.UUID `db:"project_id" json:"project_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Description string `db:"description" json:"description"`
|
||||
Content string `db:"content" json:"content"`
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
ProjectID uuid.UUID `db:"project_id" json:"project_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Description string `db:"description" json:"description"`
|
||||
Content string `db:"content" json:"content"`
|
||||
Action sql.NullBool `db:"action" json:"action"`
|
||||
ModelInvocable sql.NullBool `db:"model_invocable" json:"model_invocable"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertProjectSkill(ctx context.Context, arg InsertProjectSkillParams) (Skill, error) {
|
||||
@@ -28969,6 +28991,8 @@ func (q *sqlQuerier) InsertProjectSkill(ctx context.Context, arg InsertProjectSk
|
||||
arg.Name,
|
||||
arg.Description,
|
||||
arg.Content,
|
||||
arg.Action,
|
||||
arg.ModelInvocable,
|
||||
)
|
||||
var i Skill
|
||||
err := row.Scan(
|
||||
@@ -28984,22 +29008,25 @@ func (q *sqlQuerier) InsertProjectSkill(ctx context.Context, arg InsertProjectSk
|
||||
&i.Enabled,
|
||||
&i.GroupACL,
|
||||
&i.UserACL,
|
||||
&i.Action,
|
||||
&i.ModelInvocable,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const insertUserSkill = `-- name: InsertUserSkill :one
|
||||
INSERT INTO skills (id, user_id, name, description, content)
|
||||
VALUES ($1::uuid, $2::uuid, $3::text, $4::text, $5::text)
|
||||
RETURNING id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl
|
||||
INSERT INTO skills (id, user_id, name, description, content, model_invocable)
|
||||
VALUES ($1::uuid, $2::uuid, $3::text, $4::text, $5::text, COALESCE($6::boolean, true))
|
||||
RETURNING id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl, action, model_invocable
|
||||
`
|
||||
|
||||
type InsertUserSkillParams struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
UserID uuid.UUID `db:"user_id" json:"user_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Description string `db:"description" json:"description"`
|
||||
Content string `db:"content" json:"content"`
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
UserID uuid.UUID `db:"user_id" json:"user_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Description string `db:"description" json:"description"`
|
||||
Content string `db:"content" json:"content"`
|
||||
ModelInvocable sql.NullBool `db:"model_invocable" json:"model_invocable"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertUserSkill(ctx context.Context, arg InsertUserSkillParams) (Skill, error) {
|
||||
@@ -29009,6 +29036,7 @@ func (q *sqlQuerier) InsertUserSkill(ctx context.Context, arg InsertUserSkillPar
|
||||
arg.Name,
|
||||
arg.Description,
|
||||
arg.Content,
|
||||
arg.ModelInvocable,
|
||||
)
|
||||
var i Skill
|
||||
err := row.Scan(
|
||||
@@ -29024,13 +29052,15 @@ func (q *sqlQuerier) InsertUserSkill(ctx context.Context, arg InsertUserSkillPar
|
||||
&i.Enabled,
|
||||
&i.GroupACL,
|
||||
&i.UserACL,
|
||||
&i.Action,
|
||||
&i.ModelInvocable,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const listOrganizationSkillMetadataByOrganizationID = `-- name: ListOrganizationSkillMetadataByOrganizationID :many
|
||||
SELECT
|
||||
id, organization_id, name, description, enabled, created_at, updated_at
|
||||
id, organization_id, name, description, enabled, action, model_invocable, created_at, updated_at
|
||||
FROM skills
|
||||
WHERE organization_id = $1::uuid
|
||||
-- Authorize Filter clause will be injected below in GetAuthorizedOrganizationSkillMetadata
|
||||
@@ -29044,6 +29074,8 @@ type ListOrganizationSkillMetadataByOrganizationIDRow struct {
|
||||
Name string `db:"name" json:"name"`
|
||||
Description string `db:"description" json:"description"`
|
||||
Enabled bool `db:"enabled" json:"enabled"`
|
||||
Action bool `db:"action" json:"action"`
|
||||
ModelInvocable bool `db:"model_invocable" json:"model_invocable"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
}
|
||||
@@ -29063,6 +29095,8 @@ func (q *sqlQuerier) ListOrganizationSkillMetadataByOrganizationID(ctx context.C
|
||||
&i.Name,
|
||||
&i.Description,
|
||||
&i.Enabled,
|
||||
&i.Action,
|
||||
&i.ModelInvocable,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
@@ -29081,20 +29115,22 @@ func (q *sqlQuerier) ListOrganizationSkillMetadataByOrganizationID(ctx context.C
|
||||
|
||||
const listProjectSkillMetadataByProjectID = `-- name: ListProjectSkillMetadataByProjectID :many
|
||||
SELECT
|
||||
id, project_id, name, description, enabled, created_at, updated_at
|
||||
id, project_id, name, description, enabled, action, model_invocable, created_at, updated_at
|
||||
FROM skills
|
||||
WHERE project_id = $1::uuid
|
||||
ORDER BY name ASC
|
||||
`
|
||||
|
||||
type ListProjectSkillMetadataByProjectIDRow struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
ProjectID uuid.NullUUID `db:"project_id" json:"project_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Description string `db:"description" json:"description"`
|
||||
Enabled bool `db:"enabled" json:"enabled"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
ProjectID uuid.NullUUID `db:"project_id" json:"project_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Description string `db:"description" json:"description"`
|
||||
Enabled bool `db:"enabled" json:"enabled"`
|
||||
Action bool `db:"action" json:"action"`
|
||||
ModelInvocable bool `db:"model_invocable" json:"model_invocable"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) ListProjectSkillMetadataByProjectID(ctx context.Context, projectID uuid.UUID) ([]ListProjectSkillMetadataByProjectIDRow, error) {
|
||||
@@ -29112,6 +29148,8 @@ func (q *sqlQuerier) ListProjectSkillMetadataByProjectID(ctx context.Context, pr
|
||||
&i.Name,
|
||||
&i.Description,
|
||||
&i.Enabled,
|
||||
&i.Action,
|
||||
&i.ModelInvocable,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
@@ -29130,20 +29168,22 @@ func (q *sqlQuerier) ListProjectSkillMetadataByProjectID(ctx context.Context, pr
|
||||
|
||||
const listUserSkillMetadataByUserID = `-- name: ListUserSkillMetadataByUserID :many
|
||||
SELECT
|
||||
id, user_id, name, description, enabled, created_at, updated_at
|
||||
id, user_id, name, description, enabled, action, model_invocable, created_at, updated_at
|
||||
FROM skills
|
||||
WHERE user_id = $1::uuid
|
||||
ORDER BY name ASC
|
||||
`
|
||||
|
||||
type ListUserSkillMetadataByUserIDRow struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
UserID uuid.NullUUID `db:"user_id" json:"user_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Description string `db:"description" json:"description"`
|
||||
Enabled bool `db:"enabled" json:"enabled"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
UserID uuid.NullUUID `db:"user_id" json:"user_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Description string `db:"description" json:"description"`
|
||||
Enabled bool `db:"enabled" json:"enabled"`
|
||||
Action bool `db:"action" json:"action"`
|
||||
ModelInvocable bool `db:"model_invocable" json:"model_invocable"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) ListUserSkillMetadataByUserID(ctx context.Context, userID uuid.UUID) ([]ListUserSkillMetadataByUserIDRow, error) {
|
||||
@@ -29161,6 +29201,8 @@ func (q *sqlQuerier) ListUserSkillMetadataByUserID(ctx context.Context, userID u
|
||||
&i.Name,
|
||||
&i.Description,
|
||||
&i.Enabled,
|
||||
&i.Action,
|
||||
&i.ModelInvocable,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
@@ -29184,7 +29226,7 @@ SET
|
||||
user_acl = $2,
|
||||
updated_at = now()
|
||||
WHERE id = $3::uuid AND organization_id IS NOT NULL
|
||||
RETURNING id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl
|
||||
RETURNING id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl, action, model_invocable
|
||||
`
|
||||
|
||||
type UpdateOrganizationSkillACLByIDParams struct {
|
||||
@@ -29209,6 +29251,8 @@ func (q *sqlQuerier) UpdateOrganizationSkillACLByID(ctx context.Context, arg Upd
|
||||
&i.Enabled,
|
||||
&i.GroupACL,
|
||||
&i.UserACL,
|
||||
&i.Action,
|
||||
&i.ModelInvocable,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -29219,15 +29263,17 @@ SET
|
||||
description = COALESCE($1::text, description),
|
||||
content = COALESCE($2::text, content),
|
||||
enabled = COALESCE($3::boolean, enabled),
|
||||
model_invocable = COALESCE($4::boolean, model_invocable),
|
||||
updated_at = now()
|
||||
WHERE organization_id = $4::uuid AND name = $5
|
||||
RETURNING id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl
|
||||
WHERE organization_id = $5::uuid AND name = $6
|
||||
RETURNING id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl, action, model_invocable
|
||||
`
|
||||
|
||||
type UpdateOrganizationSkillByOrganizationIDAndNameParams struct {
|
||||
Description sql.NullString `db:"description" json:"description"`
|
||||
Content sql.NullString `db:"content" json:"content"`
|
||||
Enabled sql.NullBool `db:"enabled" json:"enabled"`
|
||||
ModelInvocable sql.NullBool `db:"model_invocable" json:"model_invocable"`
|
||||
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
}
|
||||
@@ -29237,6 +29283,7 @@ func (q *sqlQuerier) UpdateOrganizationSkillByOrganizationIDAndName(ctx context.
|
||||
arg.Description,
|
||||
arg.Content,
|
||||
arg.Enabled,
|
||||
arg.ModelInvocable,
|
||||
arg.OrganizationID,
|
||||
arg.Name,
|
||||
)
|
||||
@@ -29254,6 +29301,8 @@ func (q *sqlQuerier) UpdateOrganizationSkillByOrganizationIDAndName(ctx context.
|
||||
&i.Enabled,
|
||||
&i.GroupACL,
|
||||
&i.UserACL,
|
||||
&i.Action,
|
||||
&i.ModelInvocable,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -29264,17 +29313,21 @@ SET
|
||||
description = COALESCE($1::text, description),
|
||||
content = COALESCE($2::text, content),
|
||||
enabled = COALESCE($3::boolean, enabled),
|
||||
model_invocable = COALESCE($4::boolean, model_invocable),
|
||||
action = COALESCE($5::boolean, action),
|
||||
updated_at = now()
|
||||
WHERE project_id = $4::uuid AND name = $5
|
||||
RETURNING id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl
|
||||
WHERE project_id = $6::uuid AND name = $7
|
||||
RETURNING id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl, action, model_invocable
|
||||
`
|
||||
|
||||
type UpdateProjectSkillByProjectIDAndNameParams struct {
|
||||
Description sql.NullString `db:"description" json:"description"`
|
||||
Content sql.NullString `db:"content" json:"content"`
|
||||
Enabled sql.NullBool `db:"enabled" json:"enabled"`
|
||||
ProjectID uuid.UUID `db:"project_id" json:"project_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Description sql.NullString `db:"description" json:"description"`
|
||||
Content sql.NullString `db:"content" json:"content"`
|
||||
Enabled sql.NullBool `db:"enabled" json:"enabled"`
|
||||
ModelInvocable sql.NullBool `db:"model_invocable" json:"model_invocable"`
|
||||
Action sql.NullBool `db:"action" json:"action"`
|
||||
ProjectID uuid.UUID `db:"project_id" json:"project_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateProjectSkillByProjectIDAndName(ctx context.Context, arg UpdateProjectSkillByProjectIDAndNameParams) (Skill, error) {
|
||||
@@ -29282,6 +29335,8 @@ func (q *sqlQuerier) UpdateProjectSkillByProjectIDAndName(ctx context.Context, a
|
||||
arg.Description,
|
||||
arg.Content,
|
||||
arg.Enabled,
|
||||
arg.ModelInvocable,
|
||||
arg.Action,
|
||||
arg.ProjectID,
|
||||
arg.Name,
|
||||
)
|
||||
@@ -29299,6 +29354,8 @@ func (q *sqlQuerier) UpdateProjectSkillByProjectIDAndName(ctx context.Context, a
|
||||
&i.Enabled,
|
||||
&i.GroupACL,
|
||||
&i.UserACL,
|
||||
&i.Action,
|
||||
&i.ModelInvocable,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -29309,17 +29366,19 @@ SET
|
||||
description = COALESCE($1::text, description),
|
||||
content = COALESCE($2::text, content),
|
||||
enabled = COALESCE($3::boolean, enabled),
|
||||
model_invocable = COALESCE($4::boolean, model_invocable),
|
||||
updated_at = now()
|
||||
WHERE user_id = $4::uuid AND name = $5
|
||||
RETURNING id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl
|
||||
WHERE user_id = $5::uuid AND name = $6
|
||||
RETURNING id, user_id, name, description, content, created_at, updated_at, organization_id, project_id, enabled, group_acl, user_acl, action, model_invocable
|
||||
`
|
||||
|
||||
type UpdateUserSkillByUserIDAndNameParams struct {
|
||||
Description sql.NullString `db:"description" json:"description"`
|
||||
Content sql.NullString `db:"content" json:"content"`
|
||||
Enabled sql.NullBool `db:"enabled" json:"enabled"`
|
||||
UserID uuid.UUID `db:"user_id" json:"user_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Description sql.NullString `db:"description" json:"description"`
|
||||
Content sql.NullString `db:"content" json:"content"`
|
||||
Enabled sql.NullBool `db:"enabled" json:"enabled"`
|
||||
ModelInvocable sql.NullBool `db:"model_invocable" json:"model_invocable"`
|
||||
UserID uuid.UUID `db:"user_id" json:"user_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateUserSkillByUserIDAndName(ctx context.Context, arg UpdateUserSkillByUserIDAndNameParams) (Skill, error) {
|
||||
@@ -29327,6 +29386,7 @@ func (q *sqlQuerier) UpdateUserSkillByUserIDAndName(ctx context.Context, arg Upd
|
||||
arg.Description,
|
||||
arg.Content,
|
||||
arg.Enabled,
|
||||
arg.ModelInvocable,
|
||||
arg.UserID,
|
||||
arg.Name,
|
||||
)
|
||||
@@ -29344,6 +29404,8 @@ func (q *sqlQuerier) UpdateUserSkillByUserIDAndName(ctx context.Context, arg Upd
|
||||
&i.Enabled,
|
||||
&i.GroupACL,
|
||||
&i.UserACL,
|
||||
&i.Action,
|
||||
&i.ModelInvocable,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- name: InsertUserSkill :one
|
||||
INSERT INTO skills (id, user_id, name, description, content)
|
||||
VALUES (@id::uuid, @user_id::uuid, @name::text, @description::text, @content::text)
|
||||
INSERT INTO skills (id, user_id, name, description, content, model_invocable)
|
||||
VALUES (@id::uuid, @user_id::uuid, @name::text, @description::text, @content::text, COALESCE(sqlc.narg('model_invocable')::boolean, true))
|
||||
RETURNING *;
|
||||
|
||||
-- name: GetUserSkillByUserIDAndName :one
|
||||
@@ -10,7 +10,7 @@ WHERE user_id = @user_id::uuid AND name = @name;
|
||||
|
||||
-- name: ListUserSkillMetadataByUserID :many
|
||||
SELECT
|
||||
id, user_id, name, description, enabled, created_at, updated_at
|
||||
id, user_id, name, description, enabled, action, model_invocable, created_at, updated_at
|
||||
FROM skills
|
||||
WHERE user_id = @user_id::uuid
|
||||
ORDER BY name ASC;
|
||||
@@ -21,6 +21,7 @@ SET
|
||||
description = COALESCE(sqlc.narg('description')::text, description),
|
||||
content = COALESCE(sqlc.narg('content')::text, content),
|
||||
enabled = COALESCE(sqlc.narg('enabled')::boolean, enabled),
|
||||
model_invocable = COALESCE(sqlc.narg('model_invocable')::boolean, model_invocable),
|
||||
updated_at = now()
|
||||
WHERE user_id = @user_id::uuid AND name = @name
|
||||
RETURNING *;
|
||||
@@ -31,8 +32,8 @@ WHERE user_id = @user_id::uuid AND name = @name
|
||||
RETURNING *;
|
||||
|
||||
-- name: InsertOrganizationSkill :one
|
||||
INSERT INTO skills (id, organization_id, name, description, content, group_acl, user_acl)
|
||||
VALUES (@id::uuid, @organization_id::uuid, @name::text, @description::text, @content::text, @group_acl, @user_acl)
|
||||
INSERT INTO skills (id, organization_id, name, description, content, model_invocable, group_acl, user_acl)
|
||||
VALUES (@id::uuid, @organization_id::uuid, @name::text, @description::text, @content::text, COALESCE(sqlc.narg('model_invocable')::boolean, true), @group_acl, @user_acl)
|
||||
RETURNING *;
|
||||
|
||||
-- name: GetOrganizationSkillByOrganizationIDAndName :one
|
||||
@@ -42,7 +43,7 @@ WHERE organization_id = @organization_id::uuid AND name = @name;
|
||||
|
||||
-- name: ListOrganizationSkillMetadataByOrganizationID :many
|
||||
SELECT
|
||||
id, organization_id, name, description, enabled, created_at, updated_at
|
||||
id, organization_id, name, description, enabled, action, model_invocable, created_at, updated_at
|
||||
FROM skills
|
||||
WHERE organization_id = @organization_id::uuid
|
||||
-- Authorize Filter clause will be injected below in GetAuthorizedOrganizationSkillMetadata
|
||||
@@ -55,6 +56,7 @@ SET
|
||||
description = COALESCE(sqlc.narg('description')::text, description),
|
||||
content = COALESCE(sqlc.narg('content')::text, content),
|
||||
enabled = COALESCE(sqlc.narg('enabled')::boolean, enabled),
|
||||
model_invocable = COALESCE(sqlc.narg('model_invocable')::boolean, model_invocable),
|
||||
updated_at = now()
|
||||
WHERE organization_id = @organization_id::uuid AND name = @name
|
||||
RETURNING *;
|
||||
@@ -80,8 +82,8 @@ WHERE id = @id::uuid AND organization_id IS NOT NULL
|
||||
RETURNING *;
|
||||
|
||||
-- name: InsertProjectSkill :one
|
||||
INSERT INTO skills (id, project_id, name, description, content)
|
||||
VALUES (@id::uuid, @project_id::uuid, @name::text, @description::text, @content::text)
|
||||
INSERT INTO skills (id, project_id, name, description, content, action, model_invocable)
|
||||
VALUES (@id::uuid, @project_id::uuid, @name::text, @description::text, @content::text, COALESCE(sqlc.narg('action')::boolean, false), COALESCE(sqlc.narg('model_invocable')::boolean, true))
|
||||
RETURNING *;
|
||||
|
||||
-- name: GetProjectSkillByProjectIDAndName :one
|
||||
@@ -91,7 +93,7 @@ WHERE project_id = @project_id::uuid AND name = @name;
|
||||
|
||||
-- name: ListProjectSkillMetadataByProjectID :many
|
||||
SELECT
|
||||
id, project_id, name, description, enabled, created_at, updated_at
|
||||
id, project_id, name, description, enabled, action, model_invocable, created_at, updated_at
|
||||
FROM skills
|
||||
WHERE project_id = @project_id::uuid
|
||||
ORDER BY name ASC;
|
||||
@@ -102,6 +104,8 @@ SET
|
||||
description = COALESCE(sqlc.narg('description')::text, description),
|
||||
content = COALESCE(sqlc.narg('content')::text, content),
|
||||
enabled = COALESCE(sqlc.narg('enabled')::boolean, enabled),
|
||||
model_invocable = COALESCE(sqlc.narg('model_invocable')::boolean, model_invocable),
|
||||
action = COALESCE(sqlc.narg('action')::boolean, action),
|
||||
updated_at = now()
|
||||
WHERE project_id = @project_id::uuid AND name = @name
|
||||
RETURNING *;
|
||||
|
||||
@@ -87,7 +87,7 @@ func TestSkillOwners(t *testing.T) {
|
||||
type skillRow struct {
|
||||
owners
|
||||
name string
|
||||
enabled bool
|
||||
enabled, action bool
|
||||
groupACL, userACL string
|
||||
}
|
||||
insertSkill := func(ctx context.Context, q interface {
|
||||
@@ -104,9 +104,9 @@ func TestSkillOwners(t *testing.T) {
|
||||
row.userACL = "{}"
|
||||
}
|
||||
_, err := q.ExecContext(ctx, `
|
||||
INSERT INTO skills (id, user_id, organization_id, project_id, name, description, content, enabled, group_acl, user_acl)
|
||||
VALUES ($1, $2, $3, $4, $5, 'desc', 'body', $6, $7::jsonb, $8::jsonb)`,
|
||||
uuid.New(), row.user, row.org, row.project, row.name, row.enabled, row.groupACL, row.userACL)
|
||||
INSERT INTO skills (id, user_id, organization_id, project_id, name, description, content, enabled, action, group_acl, user_acl)
|
||||
VALUES ($1, $2, $3, $4, $5, 'desc', 'body', $6, $7, $8::jsonb, $9::jsonb)`,
|
||||
uuid.New(), row.user, row.org, row.project, row.name, row.enabled, row.action, row.groupACL, row.userACL)
|
||||
return err
|
||||
}
|
||||
countSkills := func(ctx context.Context, column string, id uuid.UUID) int {
|
||||
@@ -158,6 +158,16 @@ func TestSkillOwners(t *testing.T) {
|
||||
require.True(t, database.IsCheckViolation(err, database.CheckSkillsUserAclIsObject), err)
|
||||
})
|
||||
|
||||
t.Run("ActionOnlyOnProjectSkills", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
for _, o := range []owners{{user: valid(newUser())}, {org: valid(newOrg())}} {
|
||||
err := insertSkill(ctx, sqlDB, skillRow{owners: o, action: true})
|
||||
require.True(t, database.IsCheckViolation(err, database.CheckSkillsActionProjectOnly), "owners %+v: %v", o, err)
|
||||
}
|
||||
require.NoError(t, insertSkill(ctx, sqlDB, skillRow{owners: owners{project: valid(newProject())}, action: true}))
|
||||
})
|
||||
|
||||
// TestUserSkillLimit and TestUserSkillLimitConcurrentCreates in coderd
|
||||
// cover the user branch of the cap trigger.
|
||||
t.Run("CapPerProject", func(t *testing.T) {
|
||||
|
||||
@@ -58,13 +58,14 @@ func (api *API) postOrganizationSkill(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
skill, ok := createSkill(ctx, rw, r, organizationSkillCreateErrors, func(parsed skills.ParsedSkill, content string) (database.Skill, error) {
|
||||
skill, ok := createSkill(ctx, rw, r, organizationSkillCreateErrors, skillOwnerOrganization, func(parsed skills.ParsedSkill, create skillCreate) (database.Skill, error) {
|
||||
return api.Database.InsertOrganizationSkill(ctx, database.InsertOrganizationSkillParams{
|
||||
ID: uuid.New(),
|
||||
OrganizationID: organization.ID,
|
||||
Name: parsed.Name,
|
||||
Description: parsed.Description,
|
||||
Content: content,
|
||||
Content: create.Content,
|
||||
ModelInvocable: create.ModelInvocable,
|
||||
// The Everyone group shares the organization's ID, so new skills
|
||||
// reach every member until an admin narrows the ACL.
|
||||
GroupACL: database.ChatACL{
|
||||
@@ -147,7 +148,7 @@ func (api *API) patchOrganizationSkill(rw http.ResponseWriter, r *http.Request)
|
||||
defer commitAudit()
|
||||
aReq.Old = database.AuditableOrganizationSkill{Skill: oldSkill}
|
||||
|
||||
current, skill, ok := api.updateSkill(ctx, rw, r, oldSkill.Name, organizationSkillUpdateErrors, func(tx database.Store, update skillUpdate) (database.Skill, database.Skill, error) {
|
||||
current, skill, ok := api.updateSkill(ctx, rw, r, oldSkill.Name, organizationSkillUpdateErrors, skillOwnerOrganization, func(tx database.Store, update skillUpdate) (database.Skill, database.Skill, error) {
|
||||
//nolint:gocritic // The update below reauthorizes the locked row for update.
|
||||
locked, err := tx.GetOrganizationSkillByIDForUpdate(dbauthz.AsSystemRestricted(ctx), oldSkill.ID)
|
||||
if err != nil {
|
||||
@@ -157,6 +158,7 @@ func (api *API) patchOrganizationSkill(rw http.ResponseWriter, r *http.Request)
|
||||
Description: update.Description,
|
||||
Content: update.Content,
|
||||
Enabled: update.Enabled,
|
||||
ModelInvocable: update.ModelInvocable,
|
||||
OrganizationID: locked.OrganizationID.UUID,
|
||||
Name: locked.Name,
|
||||
})
|
||||
|
||||
+83
-26
@@ -61,13 +61,14 @@ func (api *API) postUserSkill(rw http.ResponseWriter, r *http.Request) {
|
||||
)
|
||||
defer commitAudit()
|
||||
|
||||
skill, ok := createSkill(ctx, rw, r, userSkillCreateErrors, func(parsed skills.ParsedSkill, content string) (database.Skill, error) {
|
||||
skill, ok := createSkill(ctx, rw, r, userSkillCreateErrors, skillOwnerPersonal, func(parsed skills.ParsedSkill, create skillCreate) (database.Skill, error) {
|
||||
return api.Database.InsertUserSkill(ctx, database.InsertUserSkillParams{
|
||||
ID: uuid.New(),
|
||||
UserID: user.ID,
|
||||
Name: parsed.Name,
|
||||
Description: parsed.Description,
|
||||
Content: content,
|
||||
ID: uuid.New(),
|
||||
UserID: user.ID,
|
||||
Name: parsed.Name,
|
||||
Description: parsed.Description,
|
||||
Content: create.Content,
|
||||
ModelInvocable: create.ModelInvocable,
|
||||
})
|
||||
})
|
||||
if !ok {
|
||||
@@ -160,7 +161,7 @@ func (api *API) patchUserSkill(rw http.ResponseWriter, r *http.Request) {
|
||||
)
|
||||
defer commitAudit()
|
||||
|
||||
oldSkill, skill, ok := api.updateSkill(ctx, rw, r, name, userSkillUpdateErrors, func(tx database.Store, update skillUpdate) (database.Skill, database.Skill, error) {
|
||||
oldSkill, skill, ok := api.updateSkill(ctx, rw, r, name, userSkillUpdateErrors, skillOwnerPersonal, func(tx database.Store, update skillUpdate) (database.Skill, database.Skill, error) {
|
||||
fetched, err := tx.GetUserSkillByUserIDAndName(ctx, database.GetUserSkillByUserIDAndNameParams{
|
||||
UserID: user.ID,
|
||||
Name: name,
|
||||
@@ -169,11 +170,12 @@ func (api *API) patchUserSkill(rw http.ResponseWriter, r *http.Request) {
|
||||
return database.Skill{}, database.Skill{}, xerrors.Errorf("fetch user skill: %w", err)
|
||||
}
|
||||
updated, err := tx.UpdateUserSkillByUserIDAndName(ctx, database.UpdateUserSkillByUserIDAndNameParams{
|
||||
UserID: user.ID,
|
||||
Name: name,
|
||||
Description: update.Description,
|
||||
Content: update.Content,
|
||||
Enabled: update.Enabled,
|
||||
UserID: user.ID,
|
||||
Name: name,
|
||||
Description: update.Description,
|
||||
Content: update.Content,
|
||||
Enabled: update.Enabled,
|
||||
ModelInvocable: update.ModelInvocable,
|
||||
})
|
||||
if err != nil {
|
||||
return database.Skill{}, database.Skill{}, xerrors.Errorf("update user skill: %w", err)
|
||||
@@ -272,11 +274,35 @@ func (e skillWriteErrors) write(ctx context.Context, rw http.ResponseWriter, err
|
||||
httpapi.Write(ctx, rw, http.StatusConflict, e.limitReached)
|
||||
case e.nameIndex != "" && database.IsUniqueViolation(err, e.nameIndex):
|
||||
writeSkillNameConflict(ctx, rw)
|
||||
case database.IsCheckViolation(err, database.CheckSkillsActionProjectOnly):
|
||||
writeSkillActionNotAllowed(ctx, rw)
|
||||
default:
|
||||
httpapi.InternalServerError(rw, err)
|
||||
}
|
||||
}
|
||||
|
||||
// skillOwnerKind is the kind of owner a skill route manages.
|
||||
type skillOwnerKind int
|
||||
|
||||
const (
|
||||
skillOwnerPersonal skillOwnerKind = iota
|
||||
skillOwnerOrganization
|
||||
skillOwnerProject
|
||||
)
|
||||
|
||||
// allowsAction reports whether skills of this owner kind can be actions.
|
||||
func (k skillOwnerKind) allowsAction() bool {
|
||||
return k == skillOwnerProject
|
||||
}
|
||||
|
||||
// skillCreate holds the columns a skill create request writes. Unset
|
||||
// optional columns keep their database defaults.
|
||||
type skillCreate struct {
|
||||
Content string
|
||||
Action sql.NullBool
|
||||
ModelInvocable sql.NullBool
|
||||
}
|
||||
|
||||
// createSkill parses a skill create request, stores the skill with insert,
|
||||
// and writes the response. It returns false when it wrote an error.
|
||||
func createSkill(
|
||||
@@ -284,18 +310,27 @@ func createSkill(
|
||||
rw http.ResponseWriter,
|
||||
r *http.Request,
|
||||
errs skillWriteErrors,
|
||||
insert func(parsed skills.ParsedSkill, content string) (database.Skill, error),
|
||||
kind skillOwnerKind,
|
||||
insert func(parsed skills.ParsedSkill, create skillCreate) (database.Skill, error),
|
||||
) (database.Skill, bool) {
|
||||
var req codersdk.CreateSkillRequest
|
||||
if !httpapi.ReadLimit(ctx, rw, r, maxSkillRequestBytes, &req) {
|
||||
return database.Skill{}, false
|
||||
}
|
||||
if req.Action != nil && !kind.allowsAction() {
|
||||
writeSkillActionNotAllowed(ctx, rw)
|
||||
return database.Skill{}, false
|
||||
}
|
||||
parsed, err := skills.ParsePersonalSkillMarkdown([]byte(req.Content))
|
||||
if err != nil {
|
||||
writeInvalidSkillContent(ctx, rw, err)
|
||||
return database.Skill{}, false
|
||||
}
|
||||
skill, err := insert(parsed, req.Content)
|
||||
skill, err := insert(parsed, skillCreate{
|
||||
Content: req.Content,
|
||||
Action: nullBool(req.Action),
|
||||
ModelInvocable: nullBool(req.ModelInvocable),
|
||||
})
|
||||
if err != nil {
|
||||
errs.write(ctx, rw, err)
|
||||
return database.Skill{}, false
|
||||
@@ -314,9 +349,10 @@ func (api *API) updateSkill(
|
||||
r *http.Request,
|
||||
name string,
|
||||
errs skillWriteErrors,
|
||||
kind skillOwnerKind,
|
||||
apply func(tx database.Store, update skillUpdate) (old database.Skill, updated database.Skill, err error),
|
||||
) (old database.Skill, updated database.Skill, ok bool) {
|
||||
update, ok := readSkillUpdate(ctx, rw, r, name)
|
||||
update, ok := readSkillUpdate(ctx, rw, r, name, kind)
|
||||
if !ok {
|
||||
return database.Skill{}, database.Skill{}, false
|
||||
}
|
||||
@@ -335,30 +371,37 @@ func (api *API) updateSkill(
|
||||
|
||||
// skillUpdate holds the optional columns a skill PATCH writes.
|
||||
type skillUpdate struct {
|
||||
Description sql.NullString
|
||||
Content sql.NullString
|
||||
Enabled sql.NullBool
|
||||
Description sql.NullString
|
||||
Content sql.NullString
|
||||
Enabled sql.NullBool
|
||||
Action sql.NullBool
|
||||
ModelInvocable sql.NullBool
|
||||
}
|
||||
|
||||
// readSkillUpdate reads and validates a PATCH request for the skill named
|
||||
// name. It writes the error response and returns false when the request is
|
||||
// invalid.
|
||||
func readSkillUpdate(ctx context.Context, rw http.ResponseWriter, r *http.Request, name string) (skillUpdate, bool) {
|
||||
// name for an owner of the given kind. It writes the error response and
|
||||
// returns false when the request is invalid.
|
||||
func readSkillUpdate(ctx context.Context, rw http.ResponseWriter, r *http.Request, name string, kind skillOwnerKind) (skillUpdate, bool) {
|
||||
var req codersdk.UpdateSkillRequest
|
||||
if !httpapi.ReadLimit(ctx, rw, r, maxSkillRequestBytes, &req) {
|
||||
return skillUpdate{}, false
|
||||
}
|
||||
if req.Content == nil && req.Enabled == nil {
|
||||
if req.Action != nil && !kind.allowsAction() {
|
||||
writeSkillActionNotAllowed(ctx, rw)
|
||||
return skillUpdate{}, false
|
||||
}
|
||||
if req.Content == nil && req.Enabled == nil && req.Action == nil && req.ModelInvocable == nil {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "No skill fields to update.",
|
||||
Detail: "Set content, enabled, or both.",
|
||||
Detail: "Set at least one of content, enabled, action, or model_invocable.",
|
||||
})
|
||||
return skillUpdate{}, false
|
||||
}
|
||||
|
||||
var update skillUpdate
|
||||
if req.Enabled != nil {
|
||||
update.Enabled = sql.NullBool{Bool: *req.Enabled, Valid: true}
|
||||
update := skillUpdate{
|
||||
Enabled: nullBool(req.Enabled),
|
||||
Action: nullBool(req.Action),
|
||||
ModelInvocable: nullBool(req.ModelInvocable),
|
||||
}
|
||||
if req.Content == nil {
|
||||
return update, true
|
||||
@@ -380,6 +423,20 @@ func readSkillUpdate(ctx context.Context, rw http.ResponseWriter, r *http.Reques
|
||||
return update, true
|
||||
}
|
||||
|
||||
func nullBool(value *bool) sql.NullBool {
|
||||
if value == nil {
|
||||
return sql.NullBool{}
|
||||
}
|
||||
return sql.NullBool{Bool: *value, Valid: true}
|
||||
}
|
||||
|
||||
func writeSkillActionNotAllowed(ctx context.Context, rw http.ResponseWriter) {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Only project skills can be actions.",
|
||||
Detail: "Remove the action field; personal and organization skills cannot be shown as actions.",
|
||||
})
|
||||
}
|
||||
|
||||
func writeSkillNameConflict(ctx context.Context, rw http.ResponseWriter) {
|
||||
httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{
|
||||
Message: "A skill with that name already exists.",
|
||||
|
||||
@@ -148,6 +148,8 @@ func TestSkillsCRUD(t *testing.T) {
|
||||
assert.Equal(t, "Initial description", created.Description)
|
||||
assert.Equal(t, content, created.Content)
|
||||
assert.True(t, created.Enabled)
|
||||
assert.True(t, created.ModelInvocable)
|
||||
assert.False(t, created.Action)
|
||||
assert.NotZero(t, created.CreatedAt)
|
||||
assert.NotZero(t, created.UpdatedAt)
|
||||
|
||||
@@ -188,6 +190,15 @@ func TestSkillsCRUD(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.False(t, got.Enabled)
|
||||
|
||||
manual, err := owner.update(ctx, "crud-skill", codersdk.UpdateSkillRequest{ModelInvocable: ptr.Ref(false)})
|
||||
require.NoError(t, err)
|
||||
assert.False(t, manual.ModelInvocable)
|
||||
assert.False(t, manual.Enabled)
|
||||
list, err = owner.list(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list, 1)
|
||||
assert.False(t, list[0].ModelInvocable)
|
||||
|
||||
require.NoError(t, owner.delete(ctx, "crud-skill"))
|
||||
_, err = owner.get(ctx, "crud-skill")
|
||||
requireSDKErrorStatus(t, err, http.StatusNotFound)
|
||||
@@ -287,6 +298,25 @@ func TestSkillValidationAndConflicts(t *testing.T) {
|
||||
assert.Equal(t, "No skill fields to update.", sdkErr.Message)
|
||||
})
|
||||
|
||||
// Only project skills can be actions, so personal and
|
||||
// organization routes reject the field even when it is false.
|
||||
t.Run("ActionRejected", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
content := userSkillMarkdown("action-rejected", "Valid", "Body.")
|
||||
_, err := owner.create(ctx, codersdk.CreateSkillRequest{Content: content, Action: ptr.Ref(true)})
|
||||
sdkErr := requireSDKErrorStatus(t, err, http.StatusBadRequest)
|
||||
assert.Equal(t, "Only project skills can be actions.", sdkErr.Message)
|
||||
_, err = owner.create(ctx, codersdk.CreateSkillRequest{Content: content, ModelInvocable: ptr.Ref(false)})
|
||||
require.NoError(t, err)
|
||||
for _, action := range []bool{true, false} {
|
||||
_, err = owner.update(ctx, "action-rejected", codersdk.UpdateSkillRequest{Action: ptr.Ref(action)})
|
||||
sdkErr = requireSDKErrorStatus(t, err, http.StatusBadRequest)
|
||||
assert.Equal(t, "Only project skills can be actions.", sdkErr.Message)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("PatchNameMismatch", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -61,6 +61,8 @@ Project instructions are optional context injected into all chats in a project.
|
||||
|
||||
TODO (project-skills): chats in a project now also get the project's enabled skills as a `project` skill source next to personal, organization, and workspace skills. They are read each turn as the chat owner, subagents use the root chat's project, and a failed read continues the turn without them. Describe this here.
|
||||
|
||||
TODO (project-skills): stored skills now have a `model_invocable` setting, true by default. Skills with it off are left out of `<available-skills>`, `read_skill` reports them as not found, and they do not count toward enabling the skill tools. Workspace skills are always model-invocable. Describe this here.
|
||||
|
||||
If the distinction isn't completely clear to you at this point, don't worry. It should become clearer as you learn more about the core state machine.
|
||||
|
||||
## Execution states
|
||||
@@ -492,6 +494,8 @@ Setting `title` writes a `user` title. The write happens even when the text is u
|
||||
|
||||
TODO (project-skills): `skill-invocation` content parts are resolved when a message is created, sent, or edited: chatd loads each invoked skill body as the chat owner (personal, organization, project via the chat's project, workspace from the pinned context snapshot), stores it on the part, and rejects the request with a 400 when the skill is not available. The prompt renders the stored body inside `<invoked-skill>`. Describe this here.
|
||||
|
||||
TODO (project-skills): skill invocations also load skills whose `model_invocable` setting is off, since the user picked them explicitly. They still require the skill to be enabled and readable by the chat owner. Describe this here.
|
||||
|
||||
For `busy_behavior=queue`, `SendMessage(m, queue)` supports:
|
||||
|
||||
- `W -> SendMessage(m, queue) -> R0`
|
||||
|
||||
+29
-8
@@ -2836,6 +2836,23 @@ func userSkillContext(ctx context.Context, userID uuid.UUID) context.Context {
|
||||
return dbauthz.As(ctx, actor)
|
||||
}
|
||||
|
||||
// skillLoader says who asked for a stored skill body. Skills that are not
|
||||
// model-invocable stay hidden from the agent but load for users who invoke
|
||||
// them explicitly.
|
||||
type skillLoader int
|
||||
|
||||
const (
|
||||
// skillLoaderModel loads a skill the agent asked for with read_skill.
|
||||
skillLoaderModel skillLoader = iota
|
||||
// skillLoaderUser loads a skill the user invoked in a message.
|
||||
skillLoaderUser
|
||||
)
|
||||
|
||||
// hides reports whether the loader must treat a skill as missing.
|
||||
func (l skillLoader) hides(enabled, modelInvocable bool) bool {
|
||||
return !enabled || (l == skillLoaderModel && !modelInvocable)
|
||||
}
|
||||
|
||||
func (p *Server) fetchPersonalSkillMetadata(
|
||||
ctx context.Context,
|
||||
userID uuid.UUID,
|
||||
@@ -2847,7 +2864,7 @@ func (p *Server) fetchPersonalSkillMetadata(
|
||||
|
||||
personalSkills := make([]skillspkg.Skill, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
if !row.Enabled {
|
||||
if skillLoaderModel.hides(row.Enabled, row.ModelInvocable) {
|
||||
continue
|
||||
}
|
||||
personalSkills = append(personalSkills, skillspkg.Skill{
|
||||
@@ -2863,6 +2880,7 @@ func (p *Server) loadPersonalSkillBody(
|
||||
ctx context.Context,
|
||||
userID uuid.UUID,
|
||||
name string,
|
||||
loader skillLoader,
|
||||
) (skillspkg.ParsedSkill, error) {
|
||||
row, err := p.db.GetUserSkillByUserIDAndName(
|
||||
userSkillContext(ctx, userID),
|
||||
@@ -2882,7 +2900,7 @@ func (p *Server) loadPersonalSkillBody(
|
||||
)
|
||||
return skillspkg.ParsedSkill{}, xerrors.Errorf("load personal skill body: %w", err)
|
||||
}
|
||||
if !row.Enabled {
|
||||
if loader.hides(row.Enabled, row.ModelInvocable) {
|
||||
return skillspkg.ParsedSkill{}, skillspkg.ErrSkillNotFound
|
||||
}
|
||||
|
||||
@@ -2926,7 +2944,7 @@ func (p *Server) fetchOrganizationSkillMetadata(
|
||||
|
||||
organizationSkills := make([]skillspkg.Skill, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
if !row.Enabled {
|
||||
if skillLoaderModel.hides(row.Enabled, row.ModelInvocable) {
|
||||
continue
|
||||
}
|
||||
organizationSkills = append(organizationSkills, skillspkg.Skill{
|
||||
@@ -2943,6 +2961,7 @@ func (p *Server) loadOrganizationSkillBody(
|
||||
owner rbac.Subject,
|
||||
organizationID uuid.UUID,
|
||||
name string,
|
||||
loader skillLoader,
|
||||
) (skillspkg.ParsedSkill, error) {
|
||||
//nolint:gocritic // Org skill ACLs are evaluated against the chat owner.
|
||||
row, err := p.db.GetOrganizationSkillByOrganizationIDAndName(
|
||||
@@ -2964,7 +2983,7 @@ func (p *Server) loadOrganizationSkillBody(
|
||||
)
|
||||
return skillspkg.ParsedSkill{}, xerrors.Errorf("load organization skill body: %w", err)
|
||||
}
|
||||
if !row.Enabled {
|
||||
if loader.hides(row.Enabled, row.ModelInvocable) {
|
||||
return skillspkg.ParsedSkill{}, skillspkg.ErrSkillNotFound
|
||||
}
|
||||
|
||||
@@ -2981,7 +3000,8 @@ func (p *Server) loadOrganizationSkillBody(
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
// fetchProjectSkillMetadata lists the enabled skills of a chat project. Reads
|
||||
// fetchProjectSkillMetadata lists the enabled, model-invocable skills of a
|
||||
// chat project. Reads
|
||||
// run as the chat owner, so they follow the owner's access to the project.
|
||||
func (p *Server) fetchProjectSkillMetadata(
|
||||
ctx context.Context,
|
||||
@@ -2996,7 +3016,7 @@ func (p *Server) fetchProjectSkillMetadata(
|
||||
|
||||
projectSkills := make([]skillspkg.Skill, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
if !row.Enabled {
|
||||
if skillLoaderModel.hides(row.Enabled, row.ModelInvocable) {
|
||||
continue
|
||||
}
|
||||
projectSkills = append(projectSkills, skillspkg.Skill{
|
||||
@@ -3010,12 +3030,13 @@ func (p *Server) fetchProjectSkillMetadata(
|
||||
|
||||
// loadProjectSkillBody loads the current body of a chat project skill as the
|
||||
// chat owner. It returns skillspkg.ErrSkillNotFound when the skill is gone,
|
||||
// disabled, or the owner can no longer read the project.
|
||||
// disabled, hidden from loader, or the owner can no longer read the project.
|
||||
func (p *Server) loadProjectSkillBody(
|
||||
ctx context.Context,
|
||||
owner rbac.Subject,
|
||||
projectID uuid.UUID,
|
||||
name string,
|
||||
loader skillLoader,
|
||||
) (skillspkg.ParsedSkill, error) {
|
||||
//nolint:gocritic // Project access is evaluated against the chat owner.
|
||||
row, err := p.db.GetProjectSkillByProjectIDAndName(
|
||||
@@ -3036,7 +3057,7 @@ func (p *Server) loadProjectSkillBody(
|
||||
)
|
||||
return skillspkg.ParsedSkill{}, xerrors.Errorf("load project skill body: %w", err)
|
||||
}
|
||||
if !row.Enabled {
|
||||
if loader.hides(row.Enabled, row.ModelInvocable) {
|
||||
return skillspkg.ParsedSkill{}, skillspkg.ErrSkillNotFound
|
||||
}
|
||||
|
||||
|
||||
@@ -2084,9 +2084,15 @@ func TestFetchPersonalSkillMetadata(t *testing.T) {
|
||||
requireUserSkillContextActor(ctx, t, userID)
|
||||
require.Equal(t, userID, gotUserID)
|
||||
return []database.ListUserSkillMetadataByUserIDRow{{
|
||||
UserID: uuid.NullUUID{UUID: userID, Valid: true},
|
||||
Name: "personal-review",
|
||||
Description: "Personal review process",
|
||||
Enabled: true,
|
||||
ModelInvocable: true,
|
||||
}, {
|
||||
UserID: uuid.NullUUID{UUID: userID, Valid: true},
|
||||
Name: "personal-review",
|
||||
Description: "Personal review process",
|
||||
Name: "personal-manual",
|
||||
Description: "Only users invoke this",
|
||||
Enabled: true,
|
||||
}}, nil
|
||||
},
|
||||
@@ -2094,6 +2100,7 @@ func TestFetchPersonalSkillMetadata(t *testing.T) {
|
||||
|
||||
got, err := server.fetchPersonalSkillMetadata(context.Background(), userID)
|
||||
require.NoError(t, err)
|
||||
// Skills that are not model-invocable stay out of the agent's index.
|
||||
require.Equal(t, []skillspkg.Skill{{
|
||||
Name: "personal-review",
|
||||
Description: "Personal review process",
|
||||
@@ -2152,15 +2159,16 @@ func TestLoadPersonalSkillBody(t *testing.T) {
|
||||
requireUserSkillContextActor(ctx, t, userID)
|
||||
require.Equal(t, params, gotParams)
|
||||
return database.Skill{
|
||||
UserID: uuid.NullUUID{UUID: userID, Valid: true},
|
||||
Name: "personal-review",
|
||||
Content: "---\nname: personal-review\ndescription: Personal review process\n---\n\nUpdated instructions.\n",
|
||||
Enabled: true,
|
||||
UserID: uuid.NullUUID{UUID: userID, Valid: true},
|
||||
Name: "personal-review",
|
||||
Content: "---\nname: personal-review\ndescription: Personal review process\n---\n\nUpdated instructions.\n",
|
||||
Enabled: true,
|
||||
ModelInvocable: true,
|
||||
}, nil
|
||||
},
|
||||
)
|
||||
|
||||
got, err := server.loadPersonalSkillBody(context.Background(), userID, "personal-review")
|
||||
got, err := server.loadPersonalSkillBody(context.Background(), userID, "personal-review", skillLoaderModel)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "personal-review", got.Name)
|
||||
require.Equal(t, "Personal review process", got.Description)
|
||||
@@ -2188,7 +2196,7 @@ func TestLoadPersonalSkillBody(t *testing.T) {
|
||||
},
|
||||
)
|
||||
|
||||
_, err := server.loadPersonalSkillBody(context.Background(), userID, "missing-skill")
|
||||
_, err := server.loadPersonalSkillBody(context.Background(), userID, "missing-skill", skillLoaderModel)
|
||||
require.ErrorIs(t, err, skillspkg.ErrSkillNotFound)
|
||||
})
|
||||
|
||||
@@ -2206,7 +2214,7 @@ func TestLoadPersonalSkillBody(t *testing.T) {
|
||||
Content: "---\nname: disabled-skill\ndescription: Disabled\n---\n\nInstructions.\n",
|
||||
}, nil)
|
||||
|
||||
_, err := server.loadPersonalSkillBody(context.Background(), userID, "disabled-skill")
|
||||
_, err := server.loadPersonalSkillBody(context.Background(), userID, "disabled-skill", skillLoaderModel)
|
||||
require.ErrorIs(t, err, skillspkg.ErrSkillNotFound)
|
||||
})
|
||||
|
||||
@@ -2232,7 +2240,7 @@ func TestLoadPersonalSkillBody(t *testing.T) {
|
||||
},
|
||||
)
|
||||
|
||||
_, err := server.loadPersonalSkillBody(context.Background(), userID, "error-skill")
|
||||
_, err := server.loadPersonalSkillBody(context.Background(), userID, "error-skill", skillLoaderModel)
|
||||
|
||||
require.ErrorContains(t, err, "load personal skill body")
|
||||
require.ErrorIs(t, err, dbErr)
|
||||
@@ -2261,15 +2269,16 @@ func TestLoadPersonalSkillBody(t *testing.T) {
|
||||
requireUserSkillContextActor(ctx, t, userID)
|
||||
require.Equal(t, params, gotParams)
|
||||
return database.Skill{
|
||||
UserID: uuid.NullUUID{UUID: userID, Valid: true},
|
||||
Name: "broken-skill",
|
||||
Content: "---\nname: broken-skill\ndescription: Broken\n---\n\n \n",
|
||||
Enabled: true,
|
||||
UserID: uuid.NullUUID{UUID: userID, Valid: true},
|
||||
Name: "broken-skill",
|
||||
Content: "---\nname: broken-skill\ndescription: Broken\n---\n\n \n",
|
||||
Enabled: true,
|
||||
ModelInvocable: true,
|
||||
}, nil
|
||||
},
|
||||
)
|
||||
|
||||
_, err := server.loadPersonalSkillBody(context.Background(), userID, "broken-skill")
|
||||
_, err := server.loadPersonalSkillBody(context.Background(), userID, "broken-skill", skillLoaderModel)
|
||||
|
||||
require.ErrorContains(t, err, "parse personal skill body")
|
||||
require.ErrorIs(t, err, skillspkg.ErrSkillBodyRequired)
|
||||
@@ -2332,7 +2341,7 @@ func TestLoadOrganizationSkillBody(t *testing.T) {
|
||||
return got
|
||||
}
|
||||
|
||||
got, err := server.loadOrganizationSkillBody(ctx, subject(member.ID), org.ID, "team-review")
|
||||
got, err := server.loadOrganizationSkillBody(ctx, subject(member.ID), org.ID, "team-review", skillLoaderModel)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, skillspkg.SourceOrganization, got.Source)
|
||||
require.Equal(t, "Team instructions.", got.Body)
|
||||
@@ -2346,7 +2355,7 @@ func TestLoadOrganizationSkillBody(t *testing.T) {
|
||||
{name: "Disabled", reader: member.ID, skill: "disabled-review"},
|
||||
{name: "Missing", reader: member.ID, skill: "missing-review"},
|
||||
} {
|
||||
_, err := server.loadOrganizationSkillBody(ctx, subject(tc.reader), org.ID, tc.skill)
|
||||
_, err := server.loadOrganizationSkillBody(ctx, subject(tc.reader), org.ID, tc.skill, skillLoaderModel)
|
||||
require.ErrorIs(t, err, skillspkg.ErrSkillNotFound, tc.name)
|
||||
}
|
||||
}
|
||||
@@ -2375,12 +2384,13 @@ func TestLoadProjectSkillBody(t *testing.T) {
|
||||
dbgen.OrganizationMember(t, db, database.OrganizationMember{UserID: user.ID, OrganizationID: org.ID})
|
||||
}
|
||||
project := dbgen.ChatProject(t, db, database.ChatProject{OrganizationID: org.ID, OwnerID: owner.ID})
|
||||
for _, name := range []string{"project-review", "disabled-review"} {
|
||||
for _, name := range []string{"project-review", "disabled-review", "manual-review"} {
|
||||
_, err := db.InsertProjectSkill(ctx, database.InsertProjectSkillParams{
|
||||
ID: uuid.New(),
|
||||
ProjectID: project.ID,
|
||||
Name: name,
|
||||
Content: "---\nname: " + name + "\ndescription: Review\n---\n\nProject instructions.\n",
|
||||
ID: uuid.New(),
|
||||
ProjectID: project.ID,
|
||||
Name: name,
|
||||
Content: "---\nname: " + name + "\ndescription: Review\n---\n\nProject instructions.\n",
|
||||
ModelInvocable: sql.NullBool{Bool: name != "manual-review", Valid: true},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
@@ -2398,11 +2408,18 @@ func TestLoadProjectSkillBody(t *testing.T) {
|
||||
return got
|
||||
}
|
||||
|
||||
got, err := server.loadProjectSkillBody(ctx, subject(owner.ID), project.ID, "project-review")
|
||||
got, err := server.loadProjectSkillBody(ctx, subject(owner.ID), project.ID, "project-review", skillLoaderModel)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, skillspkg.SourceProject, got.Source)
|
||||
require.Equal(t, "Project instructions.", got.Body)
|
||||
|
||||
// A skill that is not model-invocable loads only for user invocations.
|
||||
_, err = server.loadProjectSkillBody(ctx, subject(owner.ID), project.ID, "manual-review", skillLoaderModel)
|
||||
require.ErrorIs(t, err, skillspkg.ErrSkillNotFound)
|
||||
got, err = server.loadProjectSkillBody(ctx, subject(owner.ID), project.ID, "manual-review", skillLoaderUser)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "Project instructions.", got.Body)
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
reader uuid.UUID
|
||||
@@ -2412,7 +2429,7 @@ func TestLoadProjectSkillBody(t *testing.T) {
|
||||
{name: "Disabled", reader: owner.ID, skill: "disabled-review"},
|
||||
{name: "Missing", reader: owner.ID, skill: "missing-review"},
|
||||
} {
|
||||
_, err := server.loadProjectSkillBody(ctx, subject(tc.reader), project.ID, tc.skill)
|
||||
_, err := server.loadProjectSkillBody(ctx, subject(tc.reader), project.ID, tc.skill, skillLoaderUser)
|
||||
require.ErrorIs(t, err, skillspkg.ErrSkillNotFound, tc.name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -684,11 +684,11 @@ func (server *Server) prepareGeneration(
|
||||
LoadStoredSkillBody: func(ctx context.Context, skill skillspkg.Skill) (skillspkg.ParsedSkill, error) {
|
||||
switch skill.Source {
|
||||
case skillspkg.SourceOrganization:
|
||||
return server.loadOrganizationSkillBody(ctx, skillOwner, chat.OrganizationID, skill.Name)
|
||||
return server.loadOrganizationSkillBody(ctx, skillOwner, chat.OrganizationID, skill.Name, skillLoaderModel)
|
||||
case skillspkg.SourceProject:
|
||||
return server.loadProjectSkillBody(ctx, skillOwner, skillProjectID.UUID, skill.Name)
|
||||
return server.loadProjectSkillBody(ctx, skillOwner, skillProjectID.UUID, skill.Name, skillLoaderModel)
|
||||
default:
|
||||
return server.loadPersonalSkillBody(ctx, chat.OwnerID, skill.Name)
|
||||
return server.loadPersonalSkillBody(ctx, chat.OwnerID, skill.Name, skillLoaderModel)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -122,19 +122,33 @@ func TestOrganizationSkillsFollowChatOwnerAccess(t *testing.T) {
|
||||
GroupACL: everyone,
|
||||
UserACL: database.ChatACL{},
|
||||
})
|
||||
dbgen.OrganizationSkill(t, db, database.Skill{
|
||||
OrganizationID: uuid.NullUUID{UUID: org.ID, Valid: true},
|
||||
Name: "org-manual",
|
||||
Description: "Manual org process",
|
||||
GroupACL: everyone,
|
||||
UserACL: database.ChatACL{},
|
||||
})
|
||||
_, err := db.UpdateOrganizationSkillByOrganizationIDAndName(ctx, database.UpdateOrganizationSkillByOrganizationIDAndNameParams{
|
||||
Enabled: sql.NullBool{Bool: false, Valid: true},
|
||||
OrganizationID: org.ID,
|
||||
Name: "org-disabled",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
for _, name := range []string{"personal-on", "personal-off"} {
|
||||
_, err = db.UpdateOrganizationSkillByOrganizationIDAndName(ctx, database.UpdateOrganizationSkillByOrganizationIDAndNameParams{
|
||||
ModelInvocable: sql.NullBool{Bool: false, Valid: true},
|
||||
OrganizationID: org.ID,
|
||||
Name: "org-manual",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
for _, name := range []string{"personal-on", "personal-off", "personal-manual"} {
|
||||
_, err := db.InsertUserSkill(ctx, database.InsertUserSkillParams{
|
||||
ID: uuid.New(),
|
||||
UserID: member.ID,
|
||||
Name: name,
|
||||
Description: "Personal process " + name,
|
||||
Content: "---\nname: " + name + "\ndescription: Personal process " + name + "\n---\n\nPersonal instructions.\n",
|
||||
ID: uuid.New(),
|
||||
UserID: member.ID,
|
||||
Name: name,
|
||||
Description: "Personal process " + name,
|
||||
Content: "---\nname: " + name + "\ndescription: Personal process " + name + "\n---\n\nPersonal instructions.\n",
|
||||
ModelInvocable: sql.NullBool{Bool: name != "personal-manual", Valid: true},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
@@ -189,18 +203,24 @@ func TestOrganizationSkillsFollowChatOwnerAccess(t *testing.T) {
|
||||
}
|
||||
|
||||
// A team member sees enabled personal and org skills, including the
|
||||
// team-only one, and can read it. Disabled skills are not offered.
|
||||
memberChatID, calls := runTurn(t, member.ID, uuid.Nil, readSkillsDirective+"team-review,org-disabled,personal-off")
|
||||
// team-only one, and can read it. Disabled skills and skills that are
|
||||
// not model-invocable are not offered.
|
||||
memberChatID, calls := runTurn(t, member.ID, uuid.Nil, readSkillsDirective+"team-review,org-disabled,personal-off,org-manual,personal-manual")
|
||||
require.Len(t, calls, 2)
|
||||
require.Contains(t, calls[0].system, "- team-review: Team review process\n")
|
||||
require.Contains(t, calls[0].system, "- org-wide: Org-wide process\n")
|
||||
require.Contains(t, calls[0].system, "- personal-on: Personal process personal-on\n")
|
||||
require.NotContains(t, calls[0].system, "org-disabled")
|
||||
require.NotContains(t, calls[0].system, "personal-off")
|
||||
require.Len(t, calls[1].toolResults, 3)
|
||||
for _, hidden := range []string{"org-disabled", "personal-off", "org-manual", "personal-manual"} {
|
||||
require.NotContains(t, calls[0].system, hidden)
|
||||
}
|
||||
require.Len(t, calls[1].toolResults, 5)
|
||||
require.Contains(t, calls[1].toolResults[0], "Team review instructions.")
|
||||
require.Equal(t, `skill "org-disabled" not found`, calls[1].toolResults[1])
|
||||
require.Equal(t, `skill "personal-off" not found`, calls[1].toolResults[2])
|
||||
require.Equal(t, []string{
|
||||
`skill "org-disabled" not found`,
|
||||
`skill "personal-off" not found`,
|
||||
`skill "org-manual" not found`,
|
||||
`skill "personal-manual" not found`,
|
||||
}, calls[1].toolResults[1:])
|
||||
|
||||
// An org member outside the team, with no personal or workspace skills,
|
||||
// gets read_skill for the org-wide skill only.
|
||||
|
||||
@@ -45,13 +45,15 @@ func TestProjectSkillsFollowProjectAccess(t *testing.T) {
|
||||
{"project-review", "Project review process"},
|
||||
{"shared", "Project shared process"},
|
||||
{"project-disabled", "Disabled project process"},
|
||||
{"project-manual", "Manual project process"},
|
||||
} {
|
||||
_, err := db.InsertProjectSkill(ctx, database.InsertProjectSkillParams{
|
||||
ID: uuid.New(),
|
||||
ProjectID: project.ID,
|
||||
Name: skill.name,
|
||||
Description: skill.description,
|
||||
Content: skillMarkdown(skill.name, skill.description, "Instructions for "+skill.name+"."),
|
||||
ID: uuid.New(),
|
||||
ProjectID: project.ID,
|
||||
Name: skill.name,
|
||||
Description: skill.description,
|
||||
Content: skillMarkdown(skill.name, skill.description, "Instructions for "+skill.name+"."),
|
||||
ModelInvocable: sql.NullBool{Bool: skill.name != "project-manual", Valid: true},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
@@ -126,16 +128,19 @@ func TestProjectSkillsFollowProjectAccess(t *testing.T) {
|
||||
inProject := chatd.CreateOptions{ProjectID: uuid.NullUUID{UUID: project.ID, Valid: true}}
|
||||
|
||||
// A project chat lists enabled project skills. A name shared with a
|
||||
// personal skill gets qualified aliases, and disabled skills are hidden.
|
||||
projectChatID, calls := runTurn(t, owner.ID, uuid.Nil, inProject, readSkillsDirective+"project-review,project/shared,project-disabled")
|
||||
// personal skill gets qualified aliases, and disabled skills and skills
|
||||
// that are not model-invocable are hidden.
|
||||
projectChatID, calls := runTurn(t, owner.ID, uuid.Nil, inProject, readSkillsDirective+"project-review,project/shared,project-disabled,project-manual")
|
||||
require.Contains(t, calls[0].system, "- project-review: Project review process\n")
|
||||
require.Contains(t, calls[0].system, "- project/shared: Project shared process\n")
|
||||
require.Contains(t, calls[0].system, "- personal/shared: Personal shared process\n")
|
||||
require.NotContains(t, calls[0].system, "project-disabled")
|
||||
results := lastToolResults(calls[1], 3)
|
||||
require.NotContains(t, calls[0].system, "project-manual")
|
||||
results := lastToolResults(calls[1], 4)
|
||||
require.Contains(t, results[0], "Instructions for project-review.")
|
||||
require.Contains(t, results[1], "Instructions for shared.")
|
||||
require.Equal(t, `skill "project-disabled" not found`, results[2])
|
||||
require.Equal(t, `skill "project-manual" not found`, results[3])
|
||||
|
||||
// A chat outside the project does not see project skills.
|
||||
_, calls = runTurn(t, owner.ID, uuid.Nil, chatd.CreateOptions{}, readSkillsDirective+"project-review")
|
||||
|
||||
@@ -57,7 +57,8 @@ func (p *Server) resolveChatSkillInvocations(
|
||||
// resolveSkillInvocations stores the current body of every invoked skill on
|
||||
// its part, read with the chat owner's permissions through the same loaders
|
||||
// as read_skill. Skills load by source and name, so an invocation does not
|
||||
// depend on the skill being listed in the turn's skill index. Workspace
|
||||
// depend on the skill being listed in the turn's skill index, and skills
|
||||
// that are not model-invocable still load. Workspace
|
||||
// skills come from the chat's pinned context snapshot, so a chat without
|
||||
// one cannot invoke them.
|
||||
func (p *Server) resolveSkillInvocations(
|
||||
@@ -82,14 +83,14 @@ func (p *Server) resolveSkillInvocations(
|
||||
loadBody := func(part codersdk.ChatMessagePart) (string, error) {
|
||||
switch part.SkillSource {
|
||||
case codersdk.ChatSkillSourcePersonal:
|
||||
parsed, err := p.loadPersonalSkillBody(ctx, chat.OwnerID, part.SkillName)
|
||||
parsed, err := p.loadPersonalSkillBody(ctx, chat.OwnerID, part.SkillName, skillLoaderUser)
|
||||
return parsed.Body, err
|
||||
case codersdk.ChatSkillSourceOrganization:
|
||||
owner, err := loadOwner()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
parsed, err := p.loadOrganizationSkillBody(ctx, owner, chat.OrganizationID, part.SkillName)
|
||||
parsed, err := p.loadOrganizationSkillBody(ctx, owner, chat.OrganizationID, part.SkillName, skillLoaderUser)
|
||||
return parsed.Body, err
|
||||
case codersdk.ChatSkillSourceProject:
|
||||
projectID := p.resolveChatProjectID(ctx, p.logger, chat)
|
||||
@@ -100,7 +101,7 @@ func (p *Server) resolveSkillInvocations(
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
parsed, err := p.loadProjectSkillBody(ctx, owner, projectID.UUID, part.SkillName)
|
||||
parsed, err := p.loadProjectSkillBody(ctx, owner, projectID.UUID, part.SkillName, skillLoaderUser)
|
||||
return parsed.Body, err
|
||||
case codersdk.ChatSkillSourceWorkspace:
|
||||
skills, err := loadWorkspaceSkills()
|
||||
|
||||
@@ -2,6 +2,7 @@ package chatd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"slices"
|
||||
"testing"
|
||||
@@ -52,6 +53,9 @@ func TestResolveSkillInvocations(t *testing.T) {
|
||||
skillMarkdown := func(name string) string {
|
||||
return "---\nname: " + name + "\ndescription: Test\n---\n\nInstructions for " + name + ".\n"
|
||||
}
|
||||
// Every invoked stored skill is hidden from the agent: users can
|
||||
// invoke skills the agent cannot use on its own.
|
||||
manualOnly := sql.NullBool{Bool: false, Valid: true}
|
||||
org := dbgen.Organization(t, db, database.Organization{})
|
||||
owner := dbgen.User(t, db, database.User{})
|
||||
other := dbgen.User(t, db, database.User{})
|
||||
@@ -63,7 +67,7 @@ func TestResolveSkillInvocations(t *testing.T) {
|
||||
name string
|
||||
}{{owner.ID, "notes"}, {other.ID, "secret"}} {
|
||||
_, err := db.InsertUserSkill(ctx, database.InsertUserSkillParams{
|
||||
ID: uuid.New(), UserID: skill.userID, Name: skill.name, Content: skillMarkdown(skill.name),
|
||||
ID: uuid.New(), UserID: skill.userID, Name: skill.name, Content: skillMarkdown(skill.name), ModelInvocable: manualOnly,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
@@ -74,9 +78,13 @@ func TestResolveSkillInvocations(t *testing.T) {
|
||||
GroupACL: database.ChatACL{org.ID.String(): {Permissions: []policy.Action{policy.ActionRead}}},
|
||||
UserACL: database.ChatACL{},
|
||||
})
|
||||
_, err := db.UpdateOrganizationSkillByOrganizationIDAndName(ctx, database.UpdateOrganizationSkillByOrganizationIDAndNameParams{
|
||||
ModelInvocable: manualOnly, OrganizationID: org.ID, Name: "team-review",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
project := dbgen.ChatProject(t, db, database.ChatProject{OrganizationID: org.ID, OwnerID: owner.ID})
|
||||
_, err := db.InsertProjectSkill(ctx, database.InsertProjectSkillParams{
|
||||
ID: uuid.New(), ProjectID: project.ID, Name: "triage", Content: skillMarkdown("triage"),
|
||||
_, err = db.InsertProjectSkill(ctx, database.InsertProjectSkillParams{
|
||||
ID: uuid.New(), ProjectID: project.ID, Name: "triage", Content: skillMarkdown("triage"), ModelInvocable: manualOnly,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
+17
-2
@@ -17,8 +17,16 @@ type SkillMetadata struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Enabled bool `json:"enabled"`
|
||||
CreatedAt time.Time `json:"created_at" format:"date-time"`
|
||||
UpdatedAt time.Time `json:"updated_at" format:"date-time"`
|
||||
// Action reports whether a project skill is offered as a one-click
|
||||
// action above project composers. It is always false for personal and
|
||||
// organization skills.
|
||||
Action bool `json:"action"`
|
||||
// ModelInvocable reports whether agents can discover and load the skill
|
||||
// on their own. Users can still invoke a skill explicitly when it is
|
||||
// false.
|
||||
ModelInvocable bool `json:"model_invocable"`
|
||||
CreatedAt time.Time `json:"created_at" format:"date-time"`
|
||||
UpdatedAt time.Time `json:"updated_at" format:"date-time"`
|
||||
}
|
||||
|
||||
// Skill represents a personal or organization skill with its raw Markdown
|
||||
@@ -34,6 +42,10 @@ type CreateSkillRequest struct {
|
||||
// frontmatter must include name, may include description, and must be
|
||||
// followed by a non-empty body.
|
||||
Content string `json:"content"`
|
||||
// Action is only accepted for project skills and defaults to false.
|
||||
Action *bool `json:"action,omitempty"`
|
||||
// ModelInvocable defaults to true.
|
||||
ModelInvocable *bool `json:"model_invocable,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateSkillRequest is the payload for updating a skill. At least one field
|
||||
@@ -44,6 +56,9 @@ type UpdateSkillRequest struct {
|
||||
// followed by a non-empty body.
|
||||
Content *string `json:"content,omitempty"`
|
||||
Enabled *bool `json:"enabled,omitempty"`
|
||||
// Action is only accepted for project skills.
|
||||
Action *bool `json:"action,omitempty"`
|
||||
ModelInvocable *bool `json:"model_invocable,omitempty"`
|
||||
}
|
||||
|
||||
// OrganizationSkillRole is a role a user or group holds in an organization
|
||||
|
||||
@@ -147,12 +147,14 @@ Actions: `create`, `write`, `delete`
|
||||
<table width="100%">
|
||||
<thead><tr><th width="75%">Field</th><th width="25%">Tracked</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>action</code></td><td>Yes</td></tr>
|
||||
<tr><td><code>content</code></td><td>Yes</td></tr>
|
||||
<tr><td><code>created_at</code></td><td>No</td></tr>
|
||||
<tr><td><code>description</code></td><td>Yes</td></tr>
|
||||
<tr><td><code>enabled</code></td><td>Yes</td></tr>
|
||||
<tr><td><code>group_acl</code></td><td>Yes</td></tr>
|
||||
<tr><td><code>id</code></td><td>Yes</td></tr>
|
||||
<tr><td><code>model_invocable</code></td><td>Yes</td></tr>
|
||||
<tr><td><code>name</code></td><td>Yes</td></tr>
|
||||
<tr><td><code>organization_id</code></td><td>Yes</td></tr>
|
||||
<tr><td><code>project_id</code></td><td>Yes</td></tr>
|
||||
@@ -218,12 +220,14 @@ Actions: `create`, `write`, `delete`
|
||||
<table width="100%">
|
||||
<thead><tr><th width="75%">Field</th><th width="25%">Tracked</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>action</code></td><td>Yes</td></tr>
|
||||
<tr><td><code>content</code></td><td>Yes</td></tr>
|
||||
<tr><td><code>created_at</code></td><td>No</td></tr>
|
||||
<tr><td><code>description</code></td><td>Yes</td></tr>
|
||||
<tr><td><code>enabled</code></td><td>Yes</td></tr>
|
||||
<tr><td><code>group_acl</code></td><td>Yes</td></tr>
|
||||
<tr><td><code>id</code></td><td>Yes</td></tr>
|
||||
<tr><td><code>model_invocable</code></td><td>Yes</td></tr>
|
||||
<tr><td><code>name</code></td><td>Yes</td></tr>
|
||||
<tr><td><code>organization_id</code></td><td>Yes</td></tr>
|
||||
<tr><td><code>project_id</code></td><td>Yes</td></tr>
|
||||
@@ -703,12 +707,14 @@ Actions: `create`, `write`, `delete`
|
||||
<table width="100%">
|
||||
<thead><tr><th width="75%">Field</th><th width="25%">Tracked</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>action</code></td><td>Yes</td></tr>
|
||||
<tr><td><code>content</code></td><td>Yes</td></tr>
|
||||
<tr><td><code>created_at</code></td><td>No</td></tr>
|
||||
<tr><td><code>description</code></td><td>Yes</td></tr>
|
||||
<tr><td><code>enabled</code></td><td>Yes</td></tr>
|
||||
<tr><td><code>group_acl</code></td><td>Yes</td></tr>
|
||||
<tr><td><code>id</code></td><td>Yes</td></tr>
|
||||
<tr><td><code>model_invocable</code></td><td>Yes</td></tr>
|
||||
<tr><td><code>name</code></td><td>Yes</td></tr>
|
||||
<tr><td><code>organization_id</code></td><td>Yes</td></tr>
|
||||
<tr><td><code>project_id</code></td><td>Yes</td></tr>
|
||||
|
||||
Generated
+42
-26
@@ -7069,15 +7069,19 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
|
||||
```json
|
||||
{
|
||||
"content": "string"
|
||||
"action": true,
|
||||
"content": "string",
|
||||
"model_invocable": true
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|-----------|--------|----------|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `content` | string | false | | Content must be SKILL.md-format Markdown with YAML frontmatter. The frontmatter must include name, may include description, and must be followed by a non-empty body. |
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|-------------------|---------|----------|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | boolean | false | | Action is only accepted for project skills and defaults to false. |
|
||||
| `content` | string | false | | Content must be SKILL.md-format Markdown with YAML frontmatter. The frontmatter must include name, may include description, and must be followed by a non-empty body. |
|
||||
| `model_invocable` | boolean | false | | Model invocable defaults to true. |
|
||||
|
||||
## codersdk.CreateTemplateRequest
|
||||
|
||||
@@ -14728,11 +14732,13 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith
|
||||
|
||||
```json
|
||||
{
|
||||
"action": true,
|
||||
"content": "string",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"description": "string",
|
||||
"enabled": true,
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"model_invocable": true,
|
||||
"name": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z"
|
||||
}
|
||||
@@ -14740,24 +14746,28 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|---------------|---------|----------|--------------|-------------|
|
||||
| `content` | string | false | | |
|
||||
| `created_at` | string | false | | |
|
||||
| `description` | string | false | | |
|
||||
| `enabled` | boolean | false | | |
|
||||
| `id` | string | false | | |
|
||||
| `name` | string | false | | |
|
||||
| `updated_at` | string | false | | |
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|-------------------|---------|----------|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | boolean | false | | Action reports whether a project skill is offered as a one-click action above project composers. It is always false for personal and organization skills. |
|
||||
| `content` | string | false | | |
|
||||
| `created_at` | string | false | | |
|
||||
| `description` | string | false | | |
|
||||
| `enabled` | boolean | false | | |
|
||||
| `id` | string | false | | |
|
||||
| `model_invocable` | boolean | false | | Model invocable reports whether agents can discover and load the skill on their own. Users can still invoke a skill explicitly when it is false. |
|
||||
| `name` | string | false | | |
|
||||
| `updated_at` | string | false | | |
|
||||
|
||||
## codersdk.SkillMetadata
|
||||
|
||||
```json
|
||||
{
|
||||
"action": true,
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"description": "string",
|
||||
"enabled": true,
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"model_invocable": true,
|
||||
"name": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z"
|
||||
}
|
||||
@@ -14765,14 +14775,16 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|---------------|---------|----------|--------------|-------------|
|
||||
| `created_at` | string | false | | |
|
||||
| `description` | string | false | | |
|
||||
| `enabled` | boolean | false | | |
|
||||
| `id` | string | false | | |
|
||||
| `name` | string | false | | |
|
||||
| `updated_at` | string | false | | |
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|-------------------|---------|----------|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | boolean | false | | Action reports whether a project skill is offered as a one-click action above project composers. It is always false for personal and organization skills. |
|
||||
| `created_at` | string | false | | |
|
||||
| `description` | string | false | | |
|
||||
| `enabled` | boolean | false | | |
|
||||
| `id` | string | false | | |
|
||||
| `model_invocable` | boolean | false | | Model invocable reports whether agents can discover and load the skill on their own. Users can still invoke a skill explicitly when it is false. |
|
||||
| `name` | string | false | | |
|
||||
| `updated_at` | string | false | | |
|
||||
|
||||
## codersdk.SlimRole
|
||||
|
||||
@@ -17135,17 +17147,21 @@ The cursor is owner-scoped, so only the chat owner may set this. Opening a chat'
|
||||
|
||||
```json
|
||||
{
|
||||
"action": true,
|
||||
"content": "string",
|
||||
"enabled": true
|
||||
"enabled": true,
|
||||
"model_invocable": true
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|-----------|---------|----------|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `content` | string | false | | Content must be SKILL.md-format Markdown with YAML frontmatter. The frontmatter must include name, may include description, and must be followed by a non-empty body. |
|
||||
| `enabled` | boolean | false | | |
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|-------------------|---------|----------|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | boolean | false | | Action is only accepted for project skills. |
|
||||
| `content` | string | false | | Content must be SKILL.md-format Markdown with YAML frontmatter. The frontmatter must include name, may include description, and must be followed by a non-empty body. |
|
||||
| `enabled` | boolean | false | | |
|
||||
| `model_invocable` | boolean | false | | |
|
||||
|
||||
## codersdk.UpdateTemplateACL
|
||||
|
||||
|
||||
@@ -604,6 +604,8 @@ var auditableResourcesTypes = map[any]map[string]Action{
|
||||
"description": ActionTrack,
|
||||
"content": ActionTrack,
|
||||
"enabled": ActionTrack,
|
||||
"action": ActionTrack,
|
||||
"model_invocable": ActionTrack,
|
||||
"group_acl": ActionTrack,
|
||||
"user_acl": ActionTrack,
|
||||
"created_at": ActionIgnore,
|
||||
@@ -618,6 +620,8 @@ var auditableResourcesTypes = map[any]map[string]Action{
|
||||
"description": ActionTrack,
|
||||
"content": ActionTrack,
|
||||
"enabled": ActionTrack,
|
||||
"action": ActionTrack,
|
||||
"model_invocable": ActionTrack,
|
||||
"group_acl": ActionTrack,
|
||||
"user_acl": ActionTrack,
|
||||
"created_at": ActionIgnore,
|
||||
@@ -632,6 +636,8 @@ var auditableResourcesTypes = map[any]map[string]Action{
|
||||
"description": ActionTrack,
|
||||
"content": ActionTrack,
|
||||
"enabled": ActionTrack,
|
||||
"action": ActionTrack,
|
||||
"model_invocable": ActionTrack,
|
||||
"group_acl": ActionTrack,
|
||||
"user_acl": ActionTrack,
|
||||
"created_at": ActionIgnore,
|
||||
|
||||
@@ -27,6 +27,8 @@ const mockAlphaSkill: Skill = {
|
||||
name: "alpha",
|
||||
description: "alpha description",
|
||||
enabled: true,
|
||||
action: false,
|
||||
model_invocable: true,
|
||||
content: "---\nname: alpha\n---\nBody\n",
|
||||
created_at: "2026-05-21T00:00:00Z",
|
||||
updated_at: "2026-05-21T00:00:00Z",
|
||||
|
||||
@@ -26,6 +26,8 @@ const makeSkill = (name: string, overrides: Partial<Skill> = {}): Skill => ({
|
||||
name,
|
||||
description: `${name} description`,
|
||||
enabled: true,
|
||||
action: false,
|
||||
model_invocable: true,
|
||||
content: `---\nname: ${name}\n---\nBody\n`,
|
||||
created_at: "2026-05-21T00:00:00Z",
|
||||
updated_at: "2026-05-21T00:00:00Z",
|
||||
@@ -37,6 +39,8 @@ const toMetadata = (skill: Skill): SkillMetadata => ({
|
||||
name: skill.name,
|
||||
description: skill.description,
|
||||
enabled: skill.enabled,
|
||||
action: skill.action,
|
||||
model_invocable: skill.model_invocable,
|
||||
created_at: skill.created_at,
|
||||
updated_at: skill.updated_at,
|
||||
});
|
||||
|
||||
Generated
+25
@@ -4486,6 +4486,14 @@ export interface CreateSkillRequest {
|
||||
* followed by a non-empty body.
|
||||
*/
|
||||
readonly content: string;
|
||||
/**
|
||||
* Action is only accepted for project skills and defaults to false.
|
||||
*/
|
||||
readonly action?: boolean;
|
||||
/**
|
||||
* ModelInvocable defaults to true.
|
||||
*/
|
||||
readonly model_invocable?: boolean;
|
||||
}
|
||||
|
||||
// From codersdk/organizations.go
|
||||
@@ -9714,6 +9722,18 @@ export interface SkillMetadata {
|
||||
readonly name: string;
|
||||
readonly description: string;
|
||||
readonly enabled: boolean;
|
||||
/**
|
||||
* Action reports whether a project skill is offered as a one-click
|
||||
* action above project composers. It is always false for personal and
|
||||
* organization skills.
|
||||
*/
|
||||
readonly action: boolean;
|
||||
/**
|
||||
* ModelInvocable reports whether agents can discover and load the skill
|
||||
* on their own. Users can still invoke a skill explicitly when it is
|
||||
* false.
|
||||
*/
|
||||
readonly model_invocable: boolean;
|
||||
readonly created_at: string;
|
||||
readonly updated_at: string;
|
||||
}
|
||||
@@ -10978,6 +10998,11 @@ export interface UpdateSkillRequest {
|
||||
*/
|
||||
readonly content?: string;
|
||||
readonly enabled?: boolean;
|
||||
/**
|
||||
* Action is only accepted for project skills.
|
||||
*/
|
||||
readonly action?: boolean;
|
||||
readonly model_invocable?: boolean;
|
||||
}
|
||||
|
||||
// From codersdk/templates.go
|
||||
|
||||
@@ -3054,6 +3054,8 @@ export const SlashCompactYieldsToPersonalSkill: Story = {
|
||||
name: "compact",
|
||||
description: "Personal compact skill",
|
||||
enabled: true,
|
||||
action: false,
|
||||
model_invocable: true,
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
updated_at: "2024-01-01T00:00:00Z",
|
||||
},
|
||||
|
||||
@@ -24,6 +24,8 @@ const skill = (
|
||||
name,
|
||||
description,
|
||||
enabled: true,
|
||||
action: false,
|
||||
model_invocable: true,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
});
|
||||
|
||||
@@ -6,6 +6,8 @@ export const MockSkill: SkillMetadata = {
|
||||
name: "skill",
|
||||
description: "",
|
||||
enabled: true,
|
||||
action: false,
|
||||
model_invocable: true,
|
||||
created_at: MOCK_TIMESTAMP,
|
||||
updated_at: MOCK_TIMESTAMP,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user