Files
kandev/apps/backend/internal/backendapp/settings_domain_operations.go
T
2026-09-10 11:35:46 +01:00

803 lines
27 KiB
Go

//nolint:goconst // Resource type identifiers are protocol vocabulary kept readable at dispatch sites.
package backendapp
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"sort"
"strconv"
"strings"
"github.com/kandev/kandev/internal/automation"
editormodels "github.com/kandev/kandev/internal/editors/models"
editorservice "github.com/kandev/kandev/internal/editors/service"
"github.com/kandev/kandev/internal/notifications/dto"
"github.com/kandev/kandev/internal/settingscatalog"
storage "github.com/kandev/kandev/internal/system/storage"
taskmodels "github.com/kandev/kandev/internal/task/models"
taskservice "github.com/kandev/kandev/internal/task/service"
utilitymodels "github.com/kandev/kandev/internal/utility/models"
workflowcontroller "github.com/kandev/kandev/internal/workflow/controller"
workflowmodels "github.com/kandev/kandev/internal/workflow/models"
)
//nolint:cyclop,funlen // The central read dispatch preserves one domain-owned adapter boundary.
func (s *settingsOperations) readDomainSettings(ctx context.Context, target settingscatalog.ResourceTarget, keys []string) (any, error) {
if err := s.requireDomainDependency(target.ResourceType); err != nil {
return nil, err
}
if target.ResourceID == nil && target.ResourceType != "storage_maintenance" {
return nil, fmt.Errorf("resource_id is required for %q", target.ResourceType)
}
if err := s.validateDomainWorkspace(ctx, target); err != nil {
return nil, err
}
var value any
var err error
switch target.ResourceType {
case "workflow":
value, err = s.deps.task.GetWorkflow(ctx, *target.ResourceID)
case "workflow_step":
var response *workflowcontroller.GetStepResponse
response, err = s.deps.workflowCtrl.GetStep(ctx, *target.ResourceID)
if response != nil && response.Step != nil {
value = response.Step
} else if err == nil {
err = fmt.Errorf("workflow step lookup returned no step")
}
case "workspace":
value, err = s.deps.task.GetWorkspace(ctx, *target.ResourceID)
case "repository":
value, err = s.deps.task.GetRepository(ctx, *target.ResourceID)
case "repository_script":
value, err = s.deps.task.GetRepositoryScript(ctx, *target.ResourceID)
case "repository_set":
value, err = s.deps.task.GetRepositorySet(ctx, *target.ResourceID)
case "executor":
value, err = s.deps.task.GetExecutor(ctx, *target.ResourceID)
case "executor_profile":
value, err = s.deps.task.GetExecutorProfile(ctx, *target.ResourceID)
case "environment":
value, err = s.deps.task.GetEnvironment(ctx, *target.ResourceID)
case "task":
value, err = s.deps.task.GetTask(ctx, *target.ResourceID)
case "prompt":
value, err = s.readPrompt(ctx, *target.ResourceID)
case "utility_agent":
value, err = s.deps.utility.GetAgentByID(ctx, *target.ResourceID)
case "editor":
value, err = s.readEditor(ctx, *target.ResourceID)
case "notification_provider":
value, err = s.readNotificationProvider(ctx, *target.ResourceID)
case "automation":
value, err = s.deps.automation.GetAutomation(ctx, *target.ResourceID)
case "automation_trigger":
value, err = s.deps.automation.GetTrigger(ctx, *target.ResourceID)
case "runtime_flag":
value, err = s.readRuntimeFlag(ctx, *target.ResourceID)
case "storage_maintenance":
value, err = s.deps.storage.GetSettings(ctx)
default:
return nil, fmt.Errorf("settings read is not implemented for resource type %q", target.ResourceType)
}
if err != nil {
return nil, err
}
values := projectJSON(value, s.registry, target.ResourceType, keys)
redactSettingsValues(values, s.registry, target.ResourceType)
return map[string]any{"target": target, "values": values, "source": target.ResourceType}, nil
}
//nolint:cyclop,funlen // The central update dispatch preserves one domain-owned adapter boundary.
func (s *settingsOperations) updateDomainSettings(ctx context.Context, target settingscatalog.ResourceTarget, changes map[string]json.RawMessage, options map[string]json.RawMessage) (any, error) {
if err := s.requireDomainDependency(target.ResourceType); err != nil {
return nil, err
}
if target.ResourceID == nil && target.ResourceType != "storage_maintenance" {
return nil, fmt.Errorf("resource_id is required for %q", target.ResourceType)
}
if err := s.validateDomainWorkspace(ctx, target); err != nil {
return nil, err
}
normalized, err := normalizeChangeMap(changes, target.ResourceType)
if err != nil {
return nil, err
}
var updated any
switch target.ResourceType {
case "workflow":
var request taskservice.UpdateWorkflowRequest
if err := decodePatch(normalized, &request); err != nil {
return nil, err
}
updated, err = s.deps.task.UpdateWorkflow(ctx, *target.ResourceID, &request)
case "workflow_step":
updated, err = s.updateWorkflowStep(ctx, *target.ResourceID, normalized)
case "workspace":
var request taskservice.UpdateWorkspaceRequest
if err := decodePatch(normalized, &request); err != nil {
return nil, err
}
updated, err = s.deps.task.UpdateWorkspace(ctx, *target.ResourceID, &request)
case "repository":
var request taskservice.UpdateRepositoryRequest
if err := decodePatch(normalized, &request); err != nil {
return nil, err
}
updated, err = s.deps.task.UpdateRepository(ctx, *target.ResourceID, &request)
case "repository_script":
var request taskservice.UpdateRepositoryScriptRequest
if err := decodePatch(normalized, &request); err != nil {
return nil, err
}
updated, err = s.deps.task.UpdateRepositoryScript(ctx, *target.ResourceID, &request)
case "repository_set":
var request taskservice.UpdateRepositorySetRequest
if err := decodePatch(normalized, &request); err != nil {
return nil, err
}
updated, err = s.deps.task.UpdateRepositorySet(ctx, *target.ResourceID, &request)
case "executor":
var request taskservice.UpdateExecutorRequest
if err := decodePatch(normalized, &request); err != nil {
return nil, err
}
updated, err = s.deps.task.UpdateExecutor(ctx, *target.ResourceID, &request)
case "executor_profile":
var request taskservice.UpdateExecutorProfileRequest
if err := decodePatch(normalized, &request); err != nil {
return nil, err
}
updated, err = s.deps.task.UpdateExecutorProfile(ctx, *target.ResourceID, &request)
case "environment":
var request taskservice.UpdateEnvironmentRequest
if err := decodePatch(normalized, &request); err != nil {
return nil, err
}
updated, err = s.deps.task.UpdateEnvironment(ctx, *target.ResourceID, &request)
case "task":
var request taskservice.UpdateTaskRequest
if err := decodePatch(normalized, &request); err != nil {
return nil, err
}
updated, err = s.deps.task.UpdateTask(ctx, *target.ResourceID, &request)
case "prompt":
updated, err = s.updatePrompt(ctx, *target.ResourceID, normalized)
case "utility_agent":
updated, err = s.updateUtilityAgent(ctx, *target.ResourceID, normalized)
case "editor":
updated, err = s.updateEditor(ctx, *target.ResourceID, normalized)
case "notification_provider":
updated, err = s.updateNotificationProvider(ctx, *target.ResourceID, normalized)
case "automation":
var request automation.UpdateAutomationRequest
if err := decodePatch(normalized, &request); err != nil {
return nil, err
}
updated, err = s.deps.automation.UpdateAutomation(ctx, *target.ResourceID, &request)
case "automation_trigger":
updated, err = s.updateAutomationTrigger(ctx, *target.ResourceID, normalized)
case "runtime_flag":
updated, err = s.updateRuntimeFlag(ctx, *target.ResourceID, normalized)
case "storage_maintenance":
updated, err = s.updateStorageSettings(ctx, normalized, options)
default:
return nil, fmt.Errorf("settings update is not implemented for resource type %q", target.ResourceType)
}
if err != nil {
return nil, err
}
return map[string]any{"target": target, "accepted_fields": changePaths(changes), "settings": s.sanitizeSettingsValue(target.ResourceType, updated), "source": target.ResourceType}, nil
}
func decodePatch(changes map[string]json.RawMessage, target any) error {
payload, err := json.Marshal(changes)
if err != nil {
return err
}
decoder := json.NewDecoder(bytes.NewReader(payload))
decoder.DisallowUnknownFields()
if err := decoder.Decode(target); err != nil {
return fmt.Errorf("invalid settings patch: %w", err)
}
var trailing any
if err := decoder.Decode(&trailing); err != io.EOF {
return fmt.Errorf("invalid settings patch: trailing JSON data")
}
return nil
}
func (s *settingsOperations) updateWorkflowStep(ctx context.Context, id string, changes map[string]json.RawMessage) (any, error) {
request := workflowcontroller.UpdateStepRequest{ID: id}
if err := decodePatch(changes, &request); err != nil {
return nil, err
}
response, err := s.deps.workflowCtrl.UpdateStep(ctx, request)
if err != nil {
return nil, err
}
if response == nil {
return nil, fmt.Errorf("workflow step update returned no step")
}
return response.Step, nil
}
func (s *settingsOperations) readPrompt(ctx context.Context, id string) (map[string]any, error) {
prompts, err := s.deps.prompts.ListPrompts(ctx)
if err != nil {
return nil, err
}
for _, prompt := range prompts {
if prompt != nil && prompt.ID == id {
return map[string]any{
"id": prompt.ID, "name": prompt.Name, "content": prompt.Content, "builtin": prompt.Builtin,
"created_at": prompt.CreatedAt, "updated_at": prompt.UpdatedAt,
}, nil
}
}
return nil, fmt.Errorf("prompt not found")
}
func (s *settingsOperations) updatePrompt(ctx context.Context, id string, changes map[string]json.RawMessage) (any, error) {
var name, content *string
if err := decodeOptionalChange(changes, "name", &name); err != nil {
return nil, err
}
if err := decodeOptionalChange(changes, "content", &content); err != nil {
return nil, err
}
return s.deps.prompts.UpdatePrompt(ctx, id, name, content)
}
func (s *settingsOperations) readEditor(ctx context.Context, id string) (any, error) {
editors, err := s.deps.editors.ListEditors(ctx)
if err != nil {
return nil, err
}
for _, editor := range editors {
if editor != nil && editor.ID == id {
return editor, nil
}
}
return nil, fmt.Errorf("editor not found")
}
func (s *settingsOperations) updateUtilityAgent(ctx context.Context, id string, changes map[string]json.RawMessage) (any, error) {
var name, description, prompt, agentID, model, profileID, bindingState *string
var enabled *bool
for key, target := range map[string]any{
"name": &name, "description": &description, "prompt": &prompt, "agent_id": &agentID,
"model": &model, "agent_profile_id": &profileID, "profile_binding_state": &bindingState,
"enabled": &enabled,
} {
if err := decodeOptionalChange(changes, key, target); err != nil {
return nil, err
}
}
return s.deps.utility.UpdateAgent(ctx, id, name, description, prompt, agentID, model, profileID, bindingState, enabled)
}
func (s *settingsOperations) updateEditor(ctx context.Context, id string, changes map[string]json.RawMessage) (any, error) {
var name, kind *string
var enabled *bool
var config json.RawMessage
if err := decodeOptionalChange(changes, "name", &name); err != nil {
return nil, err
}
if err := decodeOptionalChange(changes, "kind", &kind); err != nil {
return nil, err
}
if err := decodeOptionalChange(changes, "enabled", &enabled); err != nil {
return nil, err
}
if raw, ok := changes["config"]; ok {
config = append(json.RawMessage(nil), raw...)
}
return s.deps.editors.UpdateEditor(ctx, editorservice.UpdateEditorInput{
EditorID: id, Name: name, Kind: kind, Config: config, Enabled: enabled,
})
}
func (s *settingsOperations) readNotificationProvider(ctx context.Context, id string) (any, error) {
response, err := s.deps.notifications.ListProviders(ctx)
if err != nil {
return nil, err
}
for _, provider := range response.Providers {
if provider.ID == id {
return provider, nil
}
}
return nil, fmt.Errorf("notification provider not found")
}
func (s *settingsOperations) updateNotificationProvider(ctx context.Context, id string, changes map[string]json.RawMessage) (any, error) {
var request dto.UpdateProviderRequest
if err := decodePatch(changes, &request); err != nil {
return nil, err
}
return s.deps.notifications.UpdateProvider(ctx, id, request)
}
func (s *settingsOperations) readRuntimeFlag(ctx context.Context, key string) (any, error) {
states, err := s.deps.runtimeFlags.ListStates(ctx)
if err != nil {
return nil, err
}
for _, state := range states {
if state.Key == key {
return runtimeFlagSettingsValue(state), nil
}
}
return nil, fmt.Errorf("runtime flag not found")
}
func (s *settingsOperations) updateRuntimeFlag(ctx context.Context, key string, changes map[string]json.RawMessage) (any, error) {
if !settingscatalog.RuntimeFlagAgentMutable(key) {
return nil, fmt.Errorf("runtime flag %q is interactive-only", key)
}
raw, ok := changes["override"]
if !ok {
return nil, fmt.Errorf("override is required")
}
var value *bool
if string(raw) != "null" {
var parsed bool
if err := json.Unmarshal(raw, &parsed); err != nil {
return nil, fmt.Errorf("override must be boolean or null")
}
value = &parsed
}
states, err := s.deps.runtimeFlags.SetOverride(ctx, key, value)
if err != nil {
return nil, err
}
for _, state := range states {
if state.Key == key {
return runtimeFlagSettingsValue(state), nil
}
}
return nil, fmt.Errorf("runtime flag not found after update")
}
func runtimeFlagSettingsValue(value any) map[string]any {
payload := map[string]any{}
encoded, err := json.Marshal(value)
if err != nil || json.Unmarshal(encoded, &payload) != nil {
return map[string]any{}
}
override, exists := payload["override_value"]
if !exists {
override = nil
}
payload["override"] = override
delete(payload, "override_value")
return payload
}
func (s *settingsOperations) updateAutomationTrigger(ctx context.Context, id string, changes map[string]json.RawMessage) (any, error) {
var request automation.UpdateTriggerRequest
if err := decodePatch(changes, &request); err != nil {
return nil, err
}
if err := s.deps.automation.UpdateTrigger(ctx, id, &request); err != nil {
return nil, err
}
return s.deps.automation.GetTrigger(ctx, id)
}
func (s *settingsOperations) requireDomainDependency(resourceType string) error {
var available bool
switch resourceType {
case "workflow":
available = s.deps.task != nil
case "workflow_step":
available = s.deps.workflow != nil && s.deps.workflowCtrl != nil
case "workspace", "repository", "repository_script", "repository_set", "executor", "executor_profile", "environment", "task":
available = s.deps.task != nil
case "prompt":
available = s.deps.prompts != nil
case "utility_agent":
available = s.deps.utility != nil
case "editor":
available = s.deps.editors != nil
case "notification_provider":
available = s.deps.notifications != nil
case "automation", "automation_trigger":
available = s.deps.automation != nil
case "runtime_flag":
available = s.deps.runtimeFlags != nil
case "storage_maintenance":
available = s.deps.storage != nil
default:
return fmt.Errorf("settings dependency is not defined for resource type %q", resourceType)
}
if !available {
return fmt.Errorf("settings dependency for %q is unavailable", resourceType)
}
return nil
}
//nolint:cyclop,gocognit,funlen // Each resource type resolves its owning workspace through its domain service.
func (s *settingsOperations) validateDomainWorkspace(ctx context.Context, target settingscatalog.ResourceTarget) error {
if target.WorkspaceID == nil || *target.WorkspaceID == "" {
return nil
}
if target.ResourceID == nil {
return fmt.Errorf("resource_id is required for %q", target.ResourceType)
}
workspaceID := ""
switch target.ResourceType {
case "workspace":
workspaceID = *target.ResourceID
case "workflow":
item, err := s.deps.task.GetWorkflow(ctx, *target.ResourceID)
if err != nil {
return err
}
workspaceID = item.WorkspaceID
case "workflow_step":
response, err := s.deps.workflowCtrl.GetStep(ctx, *target.ResourceID)
if err != nil {
return err
}
if response == nil || response.Step == nil {
return fmt.Errorf("workflow step lookup returned no step")
}
workflow, err := s.deps.task.GetWorkflow(ctx, response.Step.WorkflowID)
if err != nil {
return err
}
workspaceID = workflow.WorkspaceID
case "repository":
item, err := s.deps.task.GetRepository(ctx, *target.ResourceID)
if err != nil {
return err
}
workspaceID = item.WorkspaceID
case "repository_script":
item, err := s.deps.task.GetRepositoryScript(ctx, *target.ResourceID)
if err != nil {
return err
}
repository, err := s.deps.task.GetRepository(ctx, item.RepositoryID)
if err != nil {
return err
}
workspaceID = repository.WorkspaceID
case "repository_set":
item, err := s.deps.task.GetRepositorySet(ctx, *target.ResourceID)
if err != nil {
return err
}
workspaceID = item.WorkspaceID
case "task":
item, err := s.deps.task.GetTask(ctx, *target.ResourceID)
if err != nil {
return err
}
workspaceID = item.WorkspaceID
case "automation":
item, err := s.deps.automation.GetAutomation(ctx, *target.ResourceID)
if err != nil {
return err
}
if item == nil {
return fmt.Errorf("automation not found")
}
workspaceID = item.WorkspaceID
case "automation_trigger":
trigger, err := s.deps.automation.GetTrigger(ctx, *target.ResourceID)
if err != nil {
return err
}
if trigger == nil {
return fmt.Errorf("automation trigger not found")
}
automation, err := s.deps.automation.GetAutomation(ctx, trigger.AutomationID)
if err != nil {
return err
}
if automation == nil {
return fmt.Errorf("automation not found")
}
workspaceID = automation.WorkspaceID
default:
return fmt.Errorf("resource type %q does not support workspace targeting", target.ResourceType)
}
if workspaceID != *target.WorkspaceID {
return fmt.Errorf("resource %q is not in workspace %q", *target.ResourceID, *target.WorkspaceID)
}
return nil
}
func (s *settingsOperations) updateStorageSettings(ctx context.Context, changes map[string]json.RawMessage, options map[string]json.RawMessage) (any, error) {
dedicatedDocker, adoptGoCache := false, false
for key, raw := range options {
switch key {
case "confirm_dedicated_docker":
if err := json.Unmarshal(raw, &dedicatedDocker); err != nil {
return nil, fmt.Errorf("confirm_dedicated_docker must be boolean")
}
case "adopt_go_cache":
if err := json.Unmarshal(raw, &adoptGoCache); err != nil {
return nil, fmt.Errorf("adopt_go_cache must be boolean")
}
default:
return nil, fmt.Errorf("unsupported storage option %q", key)
}
}
return s.deps.storage.PatchSettingsWithConfirmations(ctx, changes, storage.NewSaveConfirmations(dedicatedDocker, adoptGoCache))
}
func decodeOptionalChange(changes map[string]json.RawMessage, key string, target any) error {
raw, ok := changes[key]
if !ok {
return nil
}
if err := json.Unmarshal(raw, target); err != nil {
return fmt.Errorf("invalid %s setting: %w", key, err)
}
return nil
}
//nolint:cyclop,gocognit,funlen,maintidx // Resource lookup intentionally keeps each domain's list and label semantics together.
func (s *settingsOperations) listDomainSettingsResources(ctx context.Context, resourceType string, workspaceID *string, query string, limit int, cursor string) (any, error) {
if err := s.requireDomainDependency(resourceType); err != nil {
return nil, err
}
resources := make([]map[string]any, 0)
needle := strings.ToLower(strings.TrimSpace(query))
var targetError error
add := func(id, label, wsID string, extra map[string]any) {
if targetError != nil || id == "" || (workspaceID != nil && strings.TrimSpace(*workspaceID) != "" && wsID != strings.TrimSpace(*workspaceID)) {
return
}
if needle != "" && !strings.Contains(strings.ToLower(id), needle) && !strings.Contains(strings.ToLower(label), needle) {
return
}
target := settingscatalog.ResourceTarget{ResourceType: resourceType, ResourceID: settingsStringPointer(id)}
if s.registry != nil {
if domain, ok := s.registry.Domain(resourceType); ok &&
(domain.Target.AllowsWorkspaceID || domain.Target.RequiresWorkspaceID) {
target.WorkspaceID = nonEmptyPointer(wsID)
}
}
if s.registry != nil {
if err := s.registry.ValidateTarget(target); err != nil {
targetError = err
return
}
}
item := map[string]any{
"target": target,
"label": label,
}
for key, value := range extra {
item[key] = value
}
resources = append(resources, item)
}
var err error
switch resourceType {
case "workflow":
if workspaceID == nil || *workspaceID == "" {
return nil, fmt.Errorf("workspace_id is required for workflow lookup")
}
items, listErr := s.deps.task.ListWorkflows(ctx, *workspaceID, false)
err = listErr
for _, item := range items {
if item != nil {
add(item.ID, item.Name, item.WorkspaceID, nil)
}
}
case "workflow_step":
if workspaceID == nil || *workspaceID == "" {
return nil, fmt.Errorf("workspace_id is required for workflow step lookup")
}
items, listErr := s.deps.workflow.ListStepsByWorkspaceID(ctx, *workspaceID)
err = listErr
for _, item := range items {
if item != nil {
add(item.ID, item.Name, *workspaceID, map[string]any{"workflow_id": item.WorkflowID})
}
}
case "workspace":
items, listErr := s.deps.task.ListWorkspaces(ctx)
err = listErr
for _, item := range items {
if item != nil {
add(item.ID, item.Name, item.ID, nil)
}
}
case "repository":
if workspaceID == nil || *workspaceID == "" {
return nil, fmt.Errorf("workspace_id is required for repository lookup")
}
items, listErr := s.deps.task.ListRepositories(ctx, *workspaceID)
err = listErr
for _, item := range items {
if item != nil {
add(item.ID, item.Name, item.WorkspaceID, nil)
}
}
case "repository_script":
if workspaceID == nil || *workspaceID == "" {
return nil, fmt.Errorf("workspace_id is required for repository script lookup")
}
repositories, listErr := s.deps.task.ListRepositories(ctx, *workspaceID)
err = listErr
for _, repository := range repositories {
if repository == nil {
continue
}
items, scriptErr := s.deps.task.ListRepositoryScripts(ctx, repository.ID)
if scriptErr != nil {
err = scriptErr
break
}
for _, item := range items {
if item != nil {
add(item.ID, item.Name, *workspaceID, map[string]any{"repository_id": item.RepositoryID})
}
}
}
case "repository_set":
if workspaceID == nil || *workspaceID == "" {
return nil, fmt.Errorf("workspace_id is required for repository set lookup")
}
items, listErr := s.deps.task.ListRepositorySets(ctx, *workspaceID)
err = listErr
for _, item := range items {
if item != nil {
add(item.ID, item.Name, item.WorkspaceID, nil)
}
}
case "executor":
items, listErr := s.deps.task.ListExecutors(ctx)
err = listErr
for _, item := range items {
if item != nil {
add(item.ID, item.Name, "", map[string]any{"type": item.Type})
}
}
case "executor_profile":
items, listErr := s.deps.task.ListAllExecutorProfiles(ctx)
err = listErr
for _, item := range items {
if item != nil {
add(item.ID, item.Name, "", map[string]any{"executor_id": item.ExecutorID})
}
}
case "environment":
items, listErr := s.deps.task.ListEnvironments(ctx)
err = listErr
for _, item := range items {
if item != nil {
add(item.ID, item.Name, "", map[string]any{"kind": item.Kind})
}
}
case "prompt":
items, listErr := s.deps.prompts.ListPrompts(ctx)
err = listErr
for _, item := range items {
if item != nil {
add(item.ID, item.Name, "", nil)
}
}
case "utility_agent":
items, listErr := s.deps.utility.ListAgents(ctx)
err = listErr
for _, item := range items {
if item != nil {
add(item.ID, item.Name, "", nil)
}
}
case "editor":
items, listErr := s.deps.editors.ListEditors(ctx)
err = listErr
for _, item := range items {
if item != nil {
add(item.ID, item.Name, "", nil)
}
}
case "notification_provider":
response, listErr := s.deps.notifications.ListProviders(ctx)
err = listErr
for _, item := range response.Providers {
add(item.ID, item.Name, "", map[string]any{"type": item.Type})
}
case "automation":
if workspaceID == nil || *workspaceID == "" {
return nil, fmt.Errorf("workspace_id is required for automation lookup")
}
items, listErr := s.deps.automation.ListAutomations(ctx, *workspaceID)
err = listErr
for _, item := range items {
if item != nil {
add(item.ID, item.Name, item.WorkspaceID, nil)
}
}
case "automation_trigger":
if workspaceID == nil || *workspaceID == "" {
return nil, fmt.Errorf("workspace_id is required for automation trigger lookup")
}
automations, listErr := s.deps.automation.ListAutomations(ctx, *workspaceID)
err = listErr
for _, item := range automations {
if item == nil {
continue
}
for _, trigger := range item.Triggers {
add(trigger.ID, fmt.Sprintf("%s (%s)", item.Name, trigger.Type), item.WorkspaceID, map[string]any{
"automation_id": item.ID,
"type": trigger.Type,
})
}
}
case "runtime_flag":
items, listErr := s.deps.runtimeFlags.ListStates(ctx)
err = listErr
for _, item := range items {
agentMutable := settingscatalog.RuntimeFlagAgentMutable(item.Key)
add(item.Key, item.Label, "", map[string]any{
"env_locked": item.EnvLocked,
"agent_mutable": agentMutable,
"support": runtimeFlagSupport(agentMutable),
"classification": runtimeFlagClassification(agentMutable),
"writable": agentMutable,
})
}
default:
return nil, fmt.Errorf("resource lookup is not implemented for resource type %q", resourceType)
}
if err != nil {
return nil, err
}
if targetError != nil {
return nil, fmt.Errorf("resource lookup returned invalid target: %w", targetError)
}
sort.Slice(resources, func(i, j int) bool {
return fmt.Sprint(resources[i]["label"]) < fmt.Sprint(resources[j]["label"])
})
return pageSettingsResources(resources, limit, cursor), nil
}
func runtimeFlagSupport(agentMutable bool) settingscatalog.SupportStatus {
if agentMutable {
return settingscatalog.SupportSupported
}
return settingscatalog.SupportException
}
func runtimeFlagClassification(agentMutable bool) settingscatalog.Classification {
if agentMutable {
return settingscatalog.ClassificationWritable
}
return settingscatalog.ClassificationException
}
func pageSettingsResources(resources []map[string]any, limit int, cursor string) map[string]any {
start := parseSettingsCursor(cursor)
if start > len(resources) {
start = len(resources)
}
end := start + limit
if end > len(resources) {
end = len(resources)
}
result := map[string]any{"resources": resources[start:end], "total": len(resources)}
if end < len(resources) {
result["next_cursor"] = strconv.Itoa(end)
}
return result
}
var _ = editormodels.Editor{}
var _ = taskmodels.Task{}
var _ = utilitymodels.UtilityAgent{}
var _ = workflowmodels.WorkflowStep{}