fix: make Eino exit and transfer work on AgenticMessage state

Official ExitTool still writes classic Message react state, which breaks supervisor delivery on the Agentic path.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ed1s0nZandCursor committed 2026-09-22 10:44:34 +08:00
1 parent 8da3c8c51b
commit 3aa9274675
7 files changed
+338 -10

No files matched your search

@@ -0,0 +1,70 @@
package multiagent
import (
"context"
"fmt"
"reflect"
"github.com/cloudwego/eino/adk"
"github.com/cloudwego/eino/compose"
)
// mutateADKReactState updates ChatModelAgent react state regardless of whether
// the live graph stores typedState[*schema.Message] or typedState[*schema.AgenticMessage].
// Eino v0.9.14 only exports SendToolGenAction against the classic Message state.
func mutateADKReactState(ctx context.Context, mutate func(st reflect.Value) error) error {
if mutate == nil {
return fmt.Errorf("adk react state mutate is nil")
}
return compose.ProcessState(ctx, func(_ context.Context, st any) error {
if st == nil {
return fmt.Errorf("adk react state is nil")
}
v := reflect.ValueOf(st)
if v.Kind() != reflect.Pointer || v.IsNil() {
return fmt.Errorf("unexpected adk react state type %T", st)
}
return mutate(v.Elem())
})
}
func sendADKToolGenAction(ctx context.Context, toolName string, action *adk.AgentAction) error {
if action == nil {
return fmt.Errorf("adk tool gen action is nil")
}
key := toolName
if toolCallID := compose.GetToolCallID(ctx); toolCallID != "" {
key = toolCallID
}
return mutateADKReactState(ctx, func(st reflect.Value) error {
field := st.FieldByName("ToolGenActions")
if !field.IsValid() || field.Kind() != reflect.Map {
return fmt.Errorf("adk react state missing ToolGenActions")
}
if !field.CanSet() {
return fmt.Errorf("cannot set ToolGenActions on adk react state")
}
if field.IsNil() {
field.Set(reflect.MakeMap(field.Type()))
}
field.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(action))
return nil
})
}
func clearADKReturnDirectly(ctx context.Context) error {
return mutateADKReactState(ctx, func(st reflect.Value) error {
zeroExportedField(st, "ReturnDirectlyToolCallID")
zeroExportedField(st, "HasReturnDirectly")
zeroExportedField(st, "ReturnDirectlyEvent")
return nil
})
}
func zeroExportedField(st reflect.Value, name string) {
field := st.FieldByName(name)
if !field.IsValid() || !field.CanSet() {
return
}
field.Set(reflect.Zero(field.Type()))
}