Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
200 lines
5.2 KiB
Go
200 lines
5.2 KiB
Go
package storesqlite
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"time"
|
|
)
|
|
|
|
// ErrAgentTargetNotFound is returned when a target ID has no row.
|
|
var ErrAgentTargetNotFound = errors.New("agent target not found")
|
|
|
|
// Target is the stored representation of an agent target. Validation and
|
|
// canonicalization semantics belong to the host via Options.NormalizeTarget.
|
|
type Target struct {
|
|
ID string
|
|
Provider string
|
|
LaunchRefJSON string
|
|
Name string
|
|
IconKey string
|
|
IconURL string
|
|
MaskIconURL string
|
|
HeroImageURL string
|
|
Enabled bool
|
|
Source string
|
|
SortOrder int
|
|
CreatedAtUnixMS int64
|
|
UpdatedAtUnixMS int64
|
|
}
|
|
|
|
func (s *Store) normalizeTarget(target Target) (Target, error) {
|
|
if s.opts.NormalizeTarget == nil {
|
|
return target, nil
|
|
}
|
|
return s.opts.NormalizeTarget(target)
|
|
}
|
|
|
|
func (s *Store) isSkippableTargetRowError(err error) bool {
|
|
return s.opts.IsSkippableTargetError != nil && s.opts.IsSkippableTargetError(err)
|
|
}
|
|
|
|
func (s *Store) ListAgentTargets(ctx context.Context) ([]Target, error) {
|
|
if s == nil || s.db == nil {
|
|
return nil, errors.New("workspace database is not initialized")
|
|
}
|
|
|
|
rows, err := s.db.QueryContext(ctx, `
|
|
SELECT id, provider, launch_ref_json, name, icon_key, icon_url, mask_icon_url, hero_image_url, enabled, source, sort_order, created_at_ms, updated_at_ms
|
|
FROM agent_targets
|
|
ORDER BY sort_order ASC, name ASC, id ASC
|
|
`)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list agent targets: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var result []Target
|
|
for rows.Next() {
|
|
target, err := s.scanAgentTarget(rows)
|
|
if err != nil {
|
|
if s.isSkippableTargetRowError(err) {
|
|
slog.Warn("skipping invalid agent target row", "error", err)
|
|
continue
|
|
}
|
|
return nil, err
|
|
}
|
|
result = append(result, target)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("iterate agent targets: %w", err)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (s *Store) GetAgentTarget(ctx context.Context, id string) (Target, error) {
|
|
if s == nil || s.db == nil {
|
|
return Target{}, errors.New("workspace database is not initialized")
|
|
}
|
|
|
|
row := s.db.QueryRowContext(ctx, `
|
|
SELECT id, provider, launch_ref_json, name, icon_key, icon_url, mask_icon_url, hero_image_url, enabled, source, sort_order, created_at_ms, updated_at_ms
|
|
FROM agent_targets
|
|
WHERE id = ?
|
|
`, id)
|
|
target, err := s.scanAgentTarget(row)
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return Target{}, ErrAgentTargetNotFound
|
|
}
|
|
return Target{}, err
|
|
}
|
|
return target, nil
|
|
}
|
|
|
|
func (s *Store) PutAgentTarget(ctx context.Context, target Target) (Target, error) {
|
|
if s == nil || s.db == nil {
|
|
return Target{}, errors.New("workspace database is not initialized")
|
|
}
|
|
normalized, err := s.normalizeTarget(target)
|
|
if err != nil {
|
|
return Target{}, err
|
|
}
|
|
now := unixMs(time.Now().UTC())
|
|
if normalized.CreatedAtUnixMS <= 0 {
|
|
normalized.CreatedAtUnixMS = now
|
|
}
|
|
normalized.UpdatedAtUnixMS = now
|
|
|
|
if _, err := s.db.ExecContext(ctx, `
|
|
INSERT INTO agent_targets (
|
|
id,
|
|
provider,
|
|
launch_ref_json,
|
|
name,
|
|
icon_key,
|
|
icon_url,
|
|
mask_icon_url,
|
|
hero_image_url,
|
|
enabled,
|
|
source,
|
|
sort_order,
|
|
created_at_ms,
|
|
updated_at_ms
|
|
)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
ON CONFLICT(id) DO UPDATE SET
|
|
provider = excluded.provider,
|
|
launch_ref_json = excluded.launch_ref_json,
|
|
name = excluded.name,
|
|
icon_key = excluded.icon_key,
|
|
icon_url = excluded.icon_url,
|
|
mask_icon_url = excluded.mask_icon_url,
|
|
hero_image_url = excluded.hero_image_url,
|
|
enabled = excluded.enabled,
|
|
source = excluded.source,
|
|
sort_order = excluded.sort_order,
|
|
updated_at_ms = excluded.updated_at_ms
|
|
`, normalized.ID, normalized.Provider, normalized.LaunchRefJSON, normalized.Name, normalized.IconKey, normalized.IconURL, normalized.MaskIconURL, normalized.HeroImageURL, normalized.Enabled, normalized.Source, normalized.SortOrder, normalized.CreatedAtUnixMS, normalized.UpdatedAtUnixMS); err != nil {
|
|
return Target{}, fmt.Errorf("put agent target: %w", err)
|
|
}
|
|
return s.GetAgentTarget(ctx, normalized.ID)
|
|
}
|
|
|
|
func (s *Store) DeleteAgentTarget(ctx context.Context, id string) error {
|
|
if s == nil || s.db == nil {
|
|
return errors.New("workspace database is not initialized")
|
|
}
|
|
if _, err := s.db.ExecContext(ctx, `
|
|
DELETE FROM agent_targets
|
|
WHERE id = ?
|
|
`, id); err != nil {
|
|
return fmt.Errorf("delete agent target: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) scanAgentTarget(scanner rowScanner) (Target, error) {
|
|
var target Target
|
|
var iconKey sql.NullString
|
|
var iconURL sql.NullString
|
|
var maskIconURL sql.NullString
|
|
var heroImageURL sql.NullString
|
|
if err := scanner.Scan(
|
|
&target.ID,
|
|
&target.Provider,
|
|
&target.LaunchRefJSON,
|
|
&target.Name,
|
|
&iconKey,
|
|
&iconURL,
|
|
&maskIconURL,
|
|
&heroImageURL,
|
|
&target.Enabled,
|
|
&target.Source,
|
|
&target.SortOrder,
|
|
&target.CreatedAtUnixMS,
|
|
&target.UpdatedAtUnixMS,
|
|
); err != nil {
|
|
return Target{}, fmt.Errorf("scan agent target: %w", err)
|
|
}
|
|
if iconKey.Valid {
|
|
target.IconKey = iconKey.String
|
|
}
|
|
if iconURL.Valid {
|
|
target.IconURL = iconURL.String
|
|
}
|
|
if maskIconURL.Valid {
|
|
target.MaskIconURL = maskIconURL.String
|
|
}
|
|
if heroImageURL.Valid {
|
|
target.HeroImageURL = heroImageURL.String
|
|
}
|
|
normalized, err := s.normalizeTarget(target)
|
|
if err != nil {
|
|
return Target{}, err
|
|
}
|
|
return normalized, nil
|
|
}
|