From 0265ffbc53becb3a115c4e021e72eec674ee2fce Mon Sep 17 00:00:00 2001 From: euvre <93761161+euvre@users.noreply.github.com> Date: Mon, 6 Jul 2026 09:57:00 +0800 Subject: [PATCH] fix(agent): enable single-component debug for Agent in Go backend (#16606) ### Summary This PR fixes two issues that prevented the Agent component's single-component debug/test run from working under the Go backend: 1. **Dynamic input_form generation**: Some components (e.g. `Agent`) do not store a static `input_form` in the DSL. The Go handler now falls back to the runtime component's `GetInputForm()` method, matching Python's `Canvas.get_component_input_form` behavior. This resolves the frontend 102 error: `component has no input_form`. 2. **Tenant ID injection for debug**: Single-component debug runs use a fresh `CanvasState` that previously lacked `tenant_id`. `AgentComponent.Invoke` resolves LLM credentials via the tenant tables, so the debug run failed with `api key is required`. The handler now seeds `state.Sys["tenant_id"]` with the authenticated user's ID, mirroring Python's `@add_tenant_id_to_kwargs` decorator. ### Changes - `internal/handler/agent_component.go`: - Added `componentInputForm` helper that first reads the static `input_form` and, if missing, instantiates the component and calls `GetInputForm()`. - In `DebugComponent`, set `debugState.Sys["tenant_id"] = user.ID` before invoking the component. --- internal/handler/agent_component.go | 49 +++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/internal/handler/agent_component.go b/internal/handler/agent_component.go index b8987d9af9..4e67278acf 100644 --- a/internal/handler/agent_component.go +++ b/internal/handler/agent_component.go @@ -29,7 +29,9 @@ package handler import ( + "context" "errors" + "fmt" "github.com/gin-gonic/gin" @@ -64,7 +66,7 @@ func (h *AgentHandler) GetComponentInputForm(c *gin.Context) { return } - form, err := dsl.ExtractComponentInputForm(cv.DSL, componentID) + form, err := h.componentInputForm(c.Request.Context(), cv.DSL, componentID, user.ID) if err != nil { mapDSLError(c, componentID, err) return @@ -76,6 +78,41 @@ func (h *AgentHandler) GetComponentInputForm(c *gin.Context) { }) } +// componentInputForm returns the input-form schema for a single component. +// It first tries the static input_form stored in the DSL; if the component +// does not define one (e.g. Agent components that generate it dynamically), +// it instantiates the runtime component and calls its GetInputForm method. +// This mirrors Python's Canvas.get_component_input_form which invokes the +// component's own get_input_form when the static field is absent. +func (h *AgentHandler) componentInputForm(ctx context.Context, dslMap map[string]any, componentID, userID string) (map[string]any, error) { + form, err := dsl.ExtractComponentInputForm(dslMap, componentID) + if err == nil { + return form, nil + } + if !errors.Is(err, dsl.ErrMissingInputForm) { + return nil, err + } + + name, err := dsl.ExtractComponentName(dslMap, componentID) + if err != nil { + return nil, err + } + params, _ := dsl.ExtractComponentParams(dslMap, componentID) + comp, err := runtime.DefaultFactory()(name, params) + if err != nil { + return nil, fmt.Errorf("%w: component factory: %v", dsl.ErrMalformedDSL, err) + } + getter, ok := comp.(interface{ GetInputForm() map[string]any }) + if !ok { + return nil, dsl.ErrMissingInputForm + } + form = getter.GetInputForm() + if form == nil { + form = map[string]any{} + } + return form, nil +} + // DebugComponent POST /api/v1/agents/:canvas_id/components/:component_id/debug // // Body shape (python parity): {"params": {"input_name": {"value": ...}, ...}} @@ -166,7 +203,15 @@ func (h *AgentHandler) DebugComponent(c *gin.Context) { // from the request context. We attach a fresh one here so // debug works on a single component without standing up the // full canvas compile. - invokeCtx := runtime.WithState(c.Request.Context(), canvas.NewCanvasState("debug-"+componentID, "debug-task")) + // + // Seed state.Sys["tenant_id"] with the canvas owner so that + // components which resolve LLM credentials from the tenant + // tables (e.g. AgentComponent) can find the API key in single- + // component debug mode. Mirrors Python's @add_tenant_id_to_kwargs + // decorator for the debug endpoint. + debugState := canvas.NewCanvasState("debug-"+componentID, "debug-task") + debugState.Sys["tenant_id"] = user.ID + invokeCtx := runtime.WithState(c.Request.Context(), debugState) outputs, err := comp.Invoke(invokeCtx, inputs) if err != nil {