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
This commit is contained in:
Zhichang Yu
2026-06-12 22:58:28 +08:00
committed by GitHub
parent cafa0f2e4f
commit 3fa15c0e2f
232 changed files with 44641 additions and 3993 deletions

View File

@@ -22,6 +22,7 @@ import (
"strings"
"testing"
)
// joinModelNames extracts model names from a ListModelResponse slice and
// joins them with sep, for use in test assertions.
func joinModelNames(models []ListModelResponse, sep string) string {
@@ -32,8 +33,6 @@ func joinModelNames(models []ListModelResponse, sep string) string {
return strings.Join(names, sep)
}
func readProviderConfig(t *testing.T, fileName string) []byte {
t.Helper()
@@ -56,14 +55,53 @@ func readPPIOProviderConfig(t *testing.T) []byte {
return readProviderConfig(t, "ppio.json")
}
func TestHostedProviderConfigsLoadSharedDrivers(t *testing.T) {
dir := t.TempDir()
for _, fileName := range []string{"mineru.json", "paddleocr.json"} {
// setupProviderTestDir creates a temporary directory populated with provider
// config files and conf/all_models.json, then changes the working directory to
// it. InitProviderManager hardcodes a read of conf/all_models.json relative to
// CWD, so the test must run from a directory that contains conf/all_models.json.
//
// Provider configs MUST be copied before the chdir because readProviderConfig
// resolves file paths relative to the test binary's original CWD.
//
// Caller must defer the returned restore function.
func setupProviderTestDir(t *testing.T, configFileNames ...string) (dir string, restore func()) {
t.Helper()
dir = t.TempDir()
// Copy provider configs first — readProviderConfig uses relative paths
// that are only valid from the original CWD.
for _, fileName := range configFileNames {
if err := os.WriteFile(filepath.Join(dir, fileName), readProviderConfig(t, fileName), 0o600); err != nil {
t.Fatalf("write %s config: %v", fileName, err)
}
}
confDir := filepath.Join(dir, "conf")
if err := os.MkdirAll(confDir, 0o755); err != nil {
t.Fatalf("create conf dir: %v", err)
}
allModelsSrc := filepath.Join("..", "..", "..", "conf", "all_models.json")
data, err := os.ReadFile(allModelsSrc)
if err != nil {
t.Fatalf("read all_models.json: %v", err)
}
if err := os.WriteFile(filepath.Join(confDir, "all_models.json"), data, 0o600); err != nil {
t.Fatalf("write all_models.json: %v", err)
}
orig, _ := os.Getwd()
if err := os.Chdir(dir); err != nil {
t.Fatalf("chdir: %v", err)
}
return dir, func() { os.Chdir(orig) }
}
func TestHostedProviderConfigsLoadSharedDrivers(t *testing.T) {
dir, restore := setupProviderTestDir(t, "mineru.json", "paddleocr.json")
defer restore()
err := InitProviderManager(dir)
if err != nil {
t.Fatalf("InitProviderManager: %v", err)
@@ -101,12 +139,8 @@ func TestHostedProviderConfigsLoadSharedDrivers(t *testing.T) {
}
func TestLocalOCRProviderConfigsLoadLocalDrivers(t *testing.T) {
dir := t.TempDir()
for _, fileName := range []string{"mineru_local.json", "paddleocr_local.json"} {
if err := os.WriteFile(filepath.Join(dir, fileName), readProviderConfig(t, fileName), 0o600); err != nil {
t.Fatalf("write %s config: %v", fileName, err)
}
}
dir, restore := setupProviderTestDir(t, "mineru_local.json", "paddleocr_local.json")
defer restore()
err := InitProviderManager(dir)
if err != nil {
@@ -139,12 +173,8 @@ func TestLocalOCRProviderConfigsLoadLocalDrivers(t *testing.T) {
}
func TestProviderConfigsLoadURLSuffixKeys(t *testing.T) {
dir := t.TempDir()
for _, fileName := range []string{"cohere.json", "xai.json"} {
if err := os.WriteFile(filepath.Join(dir, fileName), readProviderConfig(t, fileName), 0o600); err != nil {
t.Fatalf("write %s config: %v", fileName, err)
}
}
dir, restore := setupProviderTestDir(t, "cohere.json", "xai.json")
defer restore()
err := InitProviderManager(dir)
if err != nil {
@@ -152,7 +182,6 @@ func TestProviderConfigsLoadURLSuffixKeys(t *testing.T) {
}
pm := GetProviderManager()
cohere := pm.FindProvider("CoHere")
if cohere == nil {
t.Fatal("CoHere provider not found")
@@ -206,10 +235,8 @@ func TestProviderConfigRejectsUnknownURLSuffixKey(t *testing.T) {
}
func TestPPIOProviderConfigLoadsIntoProviderManager(t *testing.T) {
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "ppio.json"), readPPIOProviderConfig(t), 0o600); err != nil {
t.Fatalf("write ppio config: %v", err)
}
dir, restore := setupProviderTestDir(t, "ppio.json")
defer restore()
err := InitProviderManager(dir)
if err != nil {
@@ -217,7 +244,6 @@ func TestPPIOProviderConfigLoadsIntoProviderManager(t *testing.T) {
}
pm := GetProviderManager()
provider := pm.FindProvider("ppio")
if provider == nil {
t.Fatal("PPIO provider not found")
@@ -295,10 +321,8 @@ func TestPPIOProviderConfigLoadsIntoProviderManager(t *testing.T) {
}
func TestSiliconFlowProviderConfigLoadsLatestProModels(t *testing.T) {
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "siliconflow.json"), readProviderConfig(t, "siliconflow.json"), 0o600); err != nil {
t.Fatalf("write siliconflow config: %v", err)
}
dir, restore := setupProviderTestDir(t, "siliconflow.json")
defer restore()
err := InitProviderManager(dir)
if err != nil {
@@ -306,7 +330,6 @@ func TestSiliconFlowProviderConfigLoadsLatestProModels(t *testing.T) {
}
pm := GetProviderManager()
provider := pm.FindProvider("SiliconFlow")
if provider == nil {
t.Fatal("SiliconFlow provider not found")