Files
ragflow/internal/agent/component/registry.go
Zhichang Yu 3fa15c0e2f feat(agent): Go port — canvas engine, 22 components, DSL v2, 13 endpoints (#15952)
Ports the agent canvas subsystem from Python to Go.

## What's included

### Canvas Engine (Phase 0/1)
- State engine, scheduler, variable resolver, Redis checkpoint store,
cancel protocol
- **209 tests** across canvas / component / io packages

### 22 Components (P0–P4)
| Tier | Components |
|---|---|
| P0 T1+T2+T3 | LLM, Agent, ExitLoop, Switch, Categorize, Begin,
Message, Invoke |
| P1 T3 | VariableAggregator, VariableAssigner, StringTransform,
ListOperations, DataOperations |
| P2 T3 | Iteration, IterationItem, Loop, LoopItem |
| P3 T3 | UserFillUp, Fillup |
| P4 T5 | Browser, ExcelProcessor, DocsGenerator |

### DSL v2 Schema (Phase 2.5)
- Typed v2 in-memory model with v1-to-v2 auto-detect converter
- v1 legacy field stripping per plan §2.11.7

### HTTP Endpoints & Bug Fixes (Plans PR1–PR3)
- **DELETE SQL bug fix**: gorm v2 `Where("id = ?", id).Delete(...)`
pattern
- **CreateAgent validation**: title/DSL required, duplicate check, 103
envelope
- **13 new endpoints**: templates, prompts, tags, sessions CRUD,
chat/completions (SSE + non-stream stubs), rerun, test_db_connection,
logs, webhook/logs
- **756 Go unit tests** (745 → 756, +18)
- **17 → 0 Python integration test failures** (test_agents.py +
test_session_management/)

### Tools
21 eino tools: HTTPHelper, search tools, financial/data tools, mandatory
stubs

### Infrastructure
OTel observability, NATS message queue, DeepDoc gRPC client, SSRF
guards, IDOR mitigation
2026-06-12 22:58:28 +08:00

77 lines
2.3 KiB
Go

// Package component — registry (orchestrator-owned, DO NOT EDIT).
//
// Registry maps component names to factories. Each component's init() calls
// Register(name, factory) to enroll itself; lookup is case-insensitive
// (Python v1 case-insensitivity per dsl-v1-corner-cases.md §13).
package component
import (
"fmt"
"strings"
"sync"
)
// Factory constructs a Component from a params map (loaded from the DSL).
// Returning an error here aborts the run with a clear message.
type Factory func(params map[string]any) (Component, error)
var (
registryMu sync.RWMutex
registry = make(map[string]Factory)
)
// Register enrolls a component factory under name (case-insensitive).
// Intended to be called from init() in each component's <name>.go file.
func Register(name string, f Factory) {
registryMu.Lock()
defer registryMu.Unlock()
key := strings.ToLower(strings.TrimSpace(name))
if key == "" {
panic("component: Register called with empty name")
}
if _, exists := registry[key]; exists {
panic(fmt.Sprintf("component: %q already registered", name))
}
registry[key] = f
}
// New constructs a Component by name. Returns an error if the name is
// unknown or the factory rejects the params. The empty-string case is
// treated as "not found" so the error message is consistent.
func New(name string, params map[string]any) (Component, error) {
registryMu.RLock()
f, ok := registry[strings.ToLower(strings.TrimSpace(name))]
registryMu.RUnlock()
if !ok {
return nil, fmt.Errorf("component: unknown component %q (registered: %s)", name, RegisteredNames())
}
if f == nil {
return nil, fmt.Errorf("component: nil factory for %q", name)
}
return f(params)
}
// RegisteredNames returns the sorted list of registered component names.
// Used for diagnostics and the API 500 path "list available components".
func RegisteredNames() []string {
registryMu.RLock()
defer registryMu.RUnlock()
names := make([]string, 0, len(registry))
for n := range registry {
names = append(names, n)
}
// Stable order for error messages / UI listing.
sortStrings(names)
return names
}
// sortStrings is a small in-place insertion sort to avoid the sort package
// dependency for a list that's <50 items long in practice.
func sortStrings(s []string) {
for i := 1; i < len(s); i++ {
for j := i; j > 0 && s[j-1] > s[j]; j-- {
s[j-1], s[j] = s[j], s[j-1]
}
}
}