Files
multica/server/pkg/db/generated/skill.sql.go
T
54027ba763 MUL-6639: fix(skills): metadata-only skill listings and stall-based timeouts (#7574)
* MUL-6639: feat(skills): list skill files as metadata, not content

`multica skill get` and `multica skill files list` both downloaded every file
body to render output that shows none of them — `skill get`'s table view prints
four columns, and the file listing prints paths and timestamps. Past a few
hundred KB that stopped being waste and became a hard failure: a 599KB skill
could not be fetched at all, and the listing that would have shown which file
was oversized required downloading all of them first, so the diagnostic died
with the thing it was meant to diagnose (GH #7498).

Both endpoints now take `?include=content|metadata`. The metadata shape carries
path, byte size and content hash per file, plus the size and hash of the
SKILL.md body — computed in Postgres, so the bodies never leave the database.
Response size now tracks file count, not content size.

`GET /api/skills/{id}/files` defaults to metadata: it is a list endpoint, the
CLI is its only caller, and `GET /api/skills` already dropped content for this
exact reason (GH #2174). `GET /api/skills/{id}` keeps returning content by
default — installed web and desktop builds call it for the skill editor and
cannot be retrofitted with a query parameter. The CLI asks for metadata on both
and takes `--with-content` when the bodies are actually wanted.

The file listing gains a SIZE column, which is what makes an oversized skill
diagnosable without downloading it.

Co-authored-by: multica-agent <[email protected]>

* MUL-6639: fix(cli): fail skill transfers on stall, not elapsed time

`http.Client.Timeout` is a wall clock on the whole request, body included, so
it punishes exactly the transfer that is working: a 599KB skill arriving
steadily over a slow link was cut off at 30s mid-body, while a genuinely dead
connection was held open for the same 30s (GH #7498). Raising the constant
moves the cliff without removing it — the honest test is not "has this taken
too long?" but "is it still making progress?".

internal/cli/stall.go adds a client that asks the second question: bounded
connect, TLS and response-header phases, and a response body that fails after
15s with no new bytes. The wall clock stays as a loose 10-minute backstop —
demoted, not deleted, because dropping it to 0 would have left a stalled
request upload with no deadline at all.

Failures now say which thing went wrong. A new KindNetworkStalled reports "no
data received for 15s after N bytes" rather than a timeout, because only one of
those is fixed by waiting longer. Body-read errors are classified at all now:
wrapTransport only ever saw errors from http.Client.Do, which returns once the
headers arrive, so the failure #7498 actually reports reached users as a raw
"context deadline exceeded ... while reading body" out of the JSON decoder.

MULTICA_HTTP_STALL_TIMEOUT sets the no-progress budget. An explicitly set
MULTICA_HTTP_TIMEOUT still applies on this path, keeping its plain meaning —
the longest you will wait for a server — instead of silently reverting to a
total-elapsed limit.

Adoption starts with the skill commands, where the failure was reported; the
mechanism itself is not skill-specific. Graduation is one edit: point
NewAPIClient at NewStallAwareHTTPClient, fold StallAwareContext into
APIContext, drop newSkillAPIClient. A permanent second client would leave the
CLI with two timeout personalities, which is worse than either alone.

Co-authored-by: multica-agent <[email protected]>

* MUL-6639: fix(skills): hash raw UTF-8, keep the no-include default at content

Review found two problems with the previous two commits.

**The file hash was not over the file's bytes.** `sha256(content::bytea)` runs
the bytea *input* parser over the text rather than taking its bytes, so it
reads backslash escapes: a file containing `\x41` hashed as the single byte
`A`, disagreeing silently with the Go-side hash of the SKILL.md body.

Worse than the mismatch, and not caught earlier because the first check used
accented text with no backslash in it: `content::bytea` *errors* on a bare
backslash. `C:\Users\...`, a regex `\d+`, a LaTeX snippet — ordinary contents
of a skill's supporting files — fail with "invalid input syntax for type
bytea", making the listing 500 on the skills most likely to need it.
`convert_to(content, 'UTF8')` takes the bytes, and matches Go exactly.

**`/api/skills/{id}/files` must not change what a bare request returns.** The
previous commit flipped its default to metadata on the grounds that the CLI is
its only caller. That was the wrong reading of "only caller": the CLI is
installed software too, and an older `skill files list --output json` reads
`content`. It is the same argument that kept the detail endpoint's default —
applied inconsistently to a client that upgrades separately from the server.

Both endpoints now return content when `?include=` is absent. The shrink
travels with the caller instead: the CLI sends `include=metadata` itself, so
GH #7498 is fixed with no synchronized release, and the default can flip once
clients that send `include=content` have aged in. The default is now uniform,
so resolveSkillInclude no longer takes a per-endpoint parameter.

Tests: TestSkillFileHashCoversRawUTF8Bytes covers `\x41`, invalid hex, bare
backslashes, Unicode and empty bodies against the Go hash — verified to fail on
the previous SQL. TestSkillEndpointsWithoutIncludeStillReturnContent pins the
compatibility guarantee on both endpoints.

Not addressed, and noted as follow-up: the metadata path still loads the full
SKILL.md into Go, because loadSkillForUser needs the row for its tenant check.
The HTTP response no longer grows with content; the database read still does.

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-08-26 15:45:06 +08:00

672 lines
18 KiB
Go

// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: skill.sql
package db
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const addAgentSkill = `-- name: AddAgentSkill :exec
INSERT INTO agent_skill (agent_id, skill_id)
VALUES ($1, $2)
ON CONFLICT DO NOTHING
`
type AddAgentSkillParams struct {
AgentID pgtype.UUID `json:"agent_id"`
SkillID pgtype.UUID `json:"skill_id"`
}
func (q *Queries) AddAgentSkill(ctx context.Context, arg AddAgentSkillParams) error {
_, err := q.db.Exec(ctx, addAgentSkill, arg.AgentID, arg.SkillID)
return err
}
const createSkill = `-- name: CreateSkill :one
INSERT INTO skill (workspace_id, name, description, content, config, created_by)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id, workspace_id, name, description, content, config, created_by, created_at, updated_at, plugin_installation_id
`
type CreateSkillParams struct {
WorkspaceID pgtype.UUID `json:"workspace_id"`
Name string `json:"name"`
Description string `json:"description"`
Content string `json:"content"`
Config []byte `json:"config"`
CreatedBy pgtype.UUID `json:"created_by"`
}
func (q *Queries) CreateSkill(ctx context.Context, arg CreateSkillParams) (Skill, error) {
row := q.db.QueryRow(ctx, createSkill,
arg.WorkspaceID,
arg.Name,
arg.Description,
arg.Content,
arg.Config,
arg.CreatedBy,
)
var i Skill
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.Content,
&i.Config,
&i.CreatedBy,
&i.CreatedAt,
&i.UpdatedAt,
&i.PluginInstallationID,
)
return i, err
}
const deleteSkill = `-- name: DeleteSkill :exec
DELETE FROM skill WHERE id = $1 AND workspace_id = $2
`
type DeleteSkillParams struct {
ID pgtype.UUID `json:"id"`
WorkspaceID pgtype.UUID `json:"workspace_id"`
}
// Defense-in-depth: workspace_id is a SQL-layer tenant guard. See DeleteIssue.
func (q *Queries) DeleteSkill(ctx context.Context, arg DeleteSkillParams) error {
_, err := q.db.Exec(ctx, deleteSkill, arg.ID, arg.WorkspaceID)
return err
}
const deleteSkillFile = `-- name: DeleteSkillFile :exec
DELETE FROM skill_file WHERE id = $1
`
func (q *Queries) DeleteSkillFile(ctx context.Context, id pgtype.UUID) error {
_, err := q.db.Exec(ctx, deleteSkillFile, id)
return err
}
const deleteSkillFilesBySkill = `-- name: DeleteSkillFilesBySkill :exec
DELETE FROM skill_file WHERE skill_id = $1
`
func (q *Queries) DeleteSkillFilesBySkill(ctx context.Context, skillID pgtype.UUID) error {
_, err := q.db.Exec(ctx, deleteSkillFilesBySkill, skillID)
return err
}
const getSkill = `-- name: GetSkill :one
SELECT id, workspace_id, name, description, content, config, created_by, created_at, updated_at, plugin_installation_id FROM skill
WHERE id = $1
`
func (q *Queries) GetSkill(ctx context.Context, id pgtype.UUID) (Skill, error) {
row := q.db.QueryRow(ctx, getSkill, id)
var i Skill
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.Content,
&i.Config,
&i.CreatedBy,
&i.CreatedAt,
&i.UpdatedAt,
&i.PluginInstallationID,
)
return i, err
}
const getSkillByWorkspaceAndName = `-- name: GetSkillByWorkspaceAndName :one
SELECT id, workspace_id, name, description, content, config, created_by, created_at, updated_at, plugin_installation_id FROM skill
WHERE workspace_id = $1 AND name = $2
`
type GetSkillByWorkspaceAndNameParams struct {
WorkspaceID pgtype.UUID `json:"workspace_id"`
Name string `json:"name"`
}
// Used by skill import and runtime-local skill discovery to reuse a workspace
// skill by name rather than violating UNIQUE(workspace_id, name).
func (q *Queries) GetSkillByWorkspaceAndName(ctx context.Context, arg GetSkillByWorkspaceAndNameParams) (Skill, error) {
row := q.db.QueryRow(ctx, getSkillByWorkspaceAndName, arg.WorkspaceID, arg.Name)
var i Skill
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.Content,
&i.Config,
&i.CreatedBy,
&i.CreatedAt,
&i.UpdatedAt,
&i.PluginInstallationID,
)
return i, err
}
const getSkillFile = `-- name: GetSkillFile :one
SELECT id, skill_id, path, content, created_at, updated_at FROM skill_file
WHERE id = $1
`
func (q *Queries) GetSkillFile(ctx context.Context, id pgtype.UUID) (SkillFile, error) {
row := q.db.QueryRow(ctx, getSkillFile, id)
var i SkillFile
err := row.Scan(
&i.ID,
&i.SkillID,
&i.Path,
&i.Content,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const getSkillInWorkspace = `-- name: GetSkillInWorkspace :one
SELECT id, workspace_id, name, description, content, config, created_by, created_at, updated_at, plugin_installation_id FROM skill
WHERE id = $1 AND workspace_id = $2
`
type GetSkillInWorkspaceParams struct {
ID pgtype.UUID `json:"id"`
WorkspaceID pgtype.UUID `json:"workspace_id"`
}
func (q *Queries) GetSkillInWorkspace(ctx context.Context, arg GetSkillInWorkspaceParams) (Skill, error) {
row := q.db.QueryRow(ctx, getSkillInWorkspace, arg.ID, arg.WorkspaceID)
var i Skill
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.Content,
&i.Config,
&i.CreatedBy,
&i.CreatedAt,
&i.UpdatedAt,
&i.PluginInstallationID,
)
return i, err
}
const listAgentSkillNamesByAgentIDs = `-- name: ListAgentSkillNamesByAgentIDs :many
SELECT ask.agent_id, s.name
FROM agent_skill ask
JOIN skill s ON s.id = ask.skill_id
WHERE ask.agent_id = ANY($1::uuid[])
AND ask.enabled = TRUE
ORDER BY ask.agent_id, s.name ASC
`
type ListAgentSkillNamesByAgentIDsRow struct {
AgentID pgtype.UUID `json:"agent_id"`
Name string `json:"name"`
}
func (q *Queries) ListAgentSkillNamesByAgentIDs(ctx context.Context, agentIds []pgtype.UUID) ([]ListAgentSkillNamesByAgentIDsRow, error) {
rows, err := q.db.Query(ctx, listAgentSkillNamesByAgentIDs, agentIds)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListAgentSkillNamesByAgentIDsRow{}
for rows.Next() {
var i ListAgentSkillNamesByAgentIDsRow
if err := rows.Scan(&i.AgentID, &i.Name); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listAgentSkillSummaries = `-- name: ListAgentSkillSummaries :many
SELECT s.id, s.workspace_id, s.name, s.description, s.config, s.created_by, s.created_at, s.updated_at, ask.enabled
FROM skill s
JOIN agent_skill ask ON ask.skill_id = s.id
WHERE ask.agent_id = $1
ORDER BY s.name ASC
`
type ListAgentSkillSummariesRow struct {
ID pgtype.UUID `json:"id"`
WorkspaceID pgtype.UUID `json:"workspace_id"`
Name string `json:"name"`
Description string `json:"description"`
Config []byte `json:"config"`
CreatedBy pgtype.UUID `json:"created_by"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
Enabled bool `json:"enabled"`
}
// Summary variant for the agent skills list endpoint — omits `content` for
// the same reason as ListSkillSummariesByWorkspace.
func (q *Queries) ListAgentSkillSummaries(ctx context.Context, agentID pgtype.UUID) ([]ListAgentSkillSummariesRow, error) {
rows, err := q.db.Query(ctx, listAgentSkillSummaries, agentID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListAgentSkillSummariesRow{}
for rows.Next() {
var i ListAgentSkillSummariesRow
if err := rows.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.Config,
&i.CreatedBy,
&i.CreatedAt,
&i.UpdatedAt,
&i.Enabled,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listAgentSkills = `-- name: ListAgentSkills :many
SELECT s.id, s.workspace_id, s.name, s.description, s.content, s.config, s.created_by, s.created_at, s.updated_at, s.plugin_installation_id FROM skill s
JOIN agent_skill ask ON ask.skill_id = s.id
WHERE ask.agent_id = $1 AND ask.enabled = TRUE
ORDER BY s.name ASC
`
// Agent-Skill junction
func (q *Queries) ListAgentSkills(ctx context.Context, agentID pgtype.UUID) ([]Skill, error) {
rows, err := q.db.Query(ctx, listAgentSkills, agentID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Skill{}
for rows.Next() {
var i Skill
if err := rows.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.Content,
&i.Config,
&i.CreatedBy,
&i.CreatedAt,
&i.UpdatedAt,
&i.PluginInstallationID,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listAgentSkillsByWorkspace = `-- name: ListAgentSkillsByWorkspace :many
SELECT ask.agent_id, s.id, s.name, s.description, ask.enabled
FROM agent_skill ask
JOIN skill s ON s.id = ask.skill_id
WHERE s.workspace_id = $1
ORDER BY s.name ASC
`
type ListAgentSkillsByWorkspaceRow struct {
AgentID pgtype.UUID `json:"agent_id"`
ID pgtype.UUID `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Enabled bool `json:"enabled"`
}
func (q *Queries) ListAgentSkillsByWorkspace(ctx context.Context, workspaceID pgtype.UUID) ([]ListAgentSkillsByWorkspaceRow, error) {
rows, err := q.db.Query(ctx, listAgentSkillsByWorkspace, workspaceID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListAgentSkillsByWorkspaceRow{}
for rows.Next() {
var i ListAgentSkillsByWorkspaceRow
if err := rows.Scan(
&i.AgentID,
&i.ID,
&i.Name,
&i.Description,
&i.Enabled,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listSkillFileMetadata = `-- name: ListSkillFileMetadata :many
SELECT id, skill_id, path,
octet_length(content)::bigint AS size,
encode(sha256(convert_to(content, 'UTF8')), 'hex') AS content_hash,
created_at, updated_at
FROM skill_file
WHERE skill_id = $1
ORDER BY path ASC
`
type ListSkillFileMetadataRow struct {
ID pgtype.UUID `json:"id"`
SkillID pgtype.UUID `json:"skill_id"`
Path string `json:"path"`
Size int64 `json:"size"`
ContentHash string `json:"content_hash"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
}
// Metadata-only variant of ListSkillFiles: path, byte size and content hash
// without the body. Same reason as ListSkillSummariesByWorkspace — a skill
// whose supporting files total ~600KB cannot be listed at all when every row
// carries its full content, and the one command that would show which file is
// oversized was the command that timed out (GH multica-ai/multica#7498).
// size/hash are computed in Postgres so the file bodies never leave it.
//
// convert_to(content, 'UTF8'), never content::bytea: the cast runs the bytea
// INPUT parser over the text, so it reads backslash escapes instead of taking
// the bytes. A file containing `\x41` would hash as the single byte `A`, and
// one containing a bare backslash — a regex `\d+`, a Windows path, a LaTeX
// snippet — fails outright with "invalid input syntax for type bytea",
// turning an ordinary skill into a 500 on this endpoint.
func (q *Queries) ListSkillFileMetadata(ctx context.Context, skillID pgtype.UUID) ([]ListSkillFileMetadataRow, error) {
rows, err := q.db.Query(ctx, listSkillFileMetadata, skillID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListSkillFileMetadataRow{}
for rows.Next() {
var i ListSkillFileMetadataRow
if err := rows.Scan(
&i.ID,
&i.SkillID,
&i.Path,
&i.Size,
&i.ContentHash,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listSkillFiles = `-- name: ListSkillFiles :many
SELECT id, skill_id, path, content, created_at, updated_at FROM skill_file
WHERE skill_id = $1
ORDER BY path ASC
`
// Skill File CRUD
func (q *Queries) ListSkillFiles(ctx context.Context, skillID pgtype.UUID) ([]SkillFile, error) {
rows, err := q.db.Query(ctx, listSkillFiles, skillID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []SkillFile{}
for rows.Next() {
var i SkillFile
if err := rows.Scan(
&i.ID,
&i.SkillID,
&i.Path,
&i.Content,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listSkillSummariesByWorkspace = `-- name: ListSkillSummariesByWorkspace :many
SELECT id, workspace_id, name, description, config, created_by, created_at, updated_at
FROM skill
WHERE workspace_id = $1
ORDER BY name ASC
`
type ListSkillSummariesByWorkspaceRow struct {
ID pgtype.UUID `json:"id"`
WorkspaceID pgtype.UUID `json:"workspace_id"`
Name string `json:"name"`
Description string `json:"description"`
Config []byte `json:"config"`
CreatedBy pgtype.UUID `json:"created_by"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
}
// Same as ListSkillsByWorkspace but omits the SKILL.md `content` column. Used
// by list endpoints (CLI table, web list page) where the body is never read;
// shipping it everywhere blew up payload size on workspaces with many skills
// and caused 15s CLI timeouts from high-latency regions (GH multica-ai/multica#2174).
func (q *Queries) ListSkillSummariesByWorkspace(ctx context.Context, workspaceID pgtype.UUID) ([]ListSkillSummariesByWorkspaceRow, error) {
rows, err := q.db.Query(ctx, listSkillSummariesByWorkspace, workspaceID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListSkillSummariesByWorkspaceRow{}
for rows.Next() {
var i ListSkillSummariesByWorkspaceRow
if err := rows.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.Config,
&i.CreatedBy,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listSkillsByWorkspace = `-- name: ListSkillsByWorkspace :many
SELECT id, workspace_id, name, description, content, config, created_by, created_at, updated_at, plugin_installation_id FROM skill
WHERE workspace_id = $1
ORDER BY name ASC
`
// Skill CRUD
func (q *Queries) ListSkillsByWorkspace(ctx context.Context, workspaceID pgtype.UUID) ([]Skill, error) {
rows, err := q.db.Query(ctx, listSkillsByWorkspace, workspaceID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Skill{}
for rows.Next() {
var i Skill
if err := rows.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.Content,
&i.Config,
&i.CreatedBy,
&i.CreatedAt,
&i.UpdatedAt,
&i.PluginInstallationID,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const removeAgentSkill = `-- name: RemoveAgentSkill :exec
DELETE FROM agent_skill
WHERE agent_id = $1 AND skill_id = $2
`
type RemoveAgentSkillParams struct {
AgentID pgtype.UUID `json:"agent_id"`
SkillID pgtype.UUID `json:"skill_id"`
}
func (q *Queries) RemoveAgentSkill(ctx context.Context, arg RemoveAgentSkillParams) error {
_, err := q.db.Exec(ctx, removeAgentSkill, arg.AgentID, arg.SkillID)
return err
}
const removeAllAgentSkills = `-- name: RemoveAllAgentSkills :exec
DELETE FROM agent_skill WHERE agent_id = $1
`
func (q *Queries) RemoveAllAgentSkills(ctx context.Context, agentID pgtype.UUID) error {
_, err := q.db.Exec(ctx, removeAllAgentSkills, agentID)
return err
}
const setAgentSkillEnabled = `-- name: SetAgentSkillEnabled :execrows
UPDATE agent_skill
SET enabled = $3
WHERE agent_id = $1 AND skill_id = $2
`
type SetAgentSkillEnabledParams struct {
AgentID pgtype.UUID `json:"agent_id"`
SkillID pgtype.UUID `json:"skill_id"`
Enabled bool `json:"enabled"`
}
func (q *Queries) SetAgentSkillEnabled(ctx context.Context, arg SetAgentSkillEnabledParams) (int64, error) {
result, err := q.db.Exec(ctx, setAgentSkillEnabled, arg.AgentID, arg.SkillID, arg.Enabled)
if err != nil {
return 0, err
}
return result.RowsAffected(), nil
}
const updateSkill = `-- name: UpdateSkill :one
UPDATE skill SET
name = COALESCE($2, name),
description = COALESCE($3, description),
content = COALESCE($4, content),
config = COALESCE($5, config),
updated_at = now()
WHERE id = $1
RETURNING id, workspace_id, name, description, content, config, created_by, created_at, updated_at, plugin_installation_id
`
type UpdateSkillParams struct {
ID pgtype.UUID `json:"id"`
Name pgtype.Text `json:"name"`
Description pgtype.Text `json:"description"`
Content pgtype.Text `json:"content"`
Config []byte `json:"config"`
}
func (q *Queries) UpdateSkill(ctx context.Context, arg UpdateSkillParams) (Skill, error) {
row := q.db.QueryRow(ctx, updateSkill,
arg.ID,
arg.Name,
arg.Description,
arg.Content,
arg.Config,
)
var i Skill
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.Content,
&i.Config,
&i.CreatedBy,
&i.CreatedAt,
&i.UpdatedAt,
&i.PluginInstallationID,
)
return i, err
}
const upsertSkillFile = `-- name: UpsertSkillFile :one
INSERT INTO skill_file (skill_id, path, content)
VALUES ($1, $2, $3)
ON CONFLICT (skill_id, path) DO UPDATE SET
content = EXCLUDED.content,
updated_at = now()
RETURNING id, skill_id, path, content, created_at, updated_at
`
type UpsertSkillFileParams struct {
SkillID pgtype.UUID `json:"skill_id"`
Path string `json:"path"`
Content string `json:"content"`
}
func (q *Queries) UpsertSkillFile(ctx context.Context, arg UpsertSkillFileParams) (SkillFile, error) {
row := q.db.QueryRow(ctx, upsertSkillFile, arg.SkillID, arg.Path, arg.Content)
var i SkillFile
err := row.Scan(
&i.ID,
&i.SkillID,
&i.Path,
&i.Content,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}