Files
agent-radar 2ed3415fb7
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
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

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
}