@@ -428,24 +428,32 @@ func (e *workflowEngine) executeToolStep(
428428 }
429429
430430 // Call tool with retry logic
431- output , retryCount , err := e .callToolWithRetry (ctx , target , step , expandedArgs , workflowCtx )
431+ result , retryCount , err := e .callToolWithRetry (ctx , target , step , expandedArgs , workflowCtx )
432432
433433 // Handle result
434434 if err != nil {
435435 return e .handleToolStepFailure (step , workflowCtx , retryCount , err )
436436 }
437437
438- return e .handleToolStepSuccess (ctx , step , workflowCtx , output , retryCount )
438+ // Extract output map from result.
439+ // Prefer StructuredContent (already a map), fall back to Content array conversion.
440+ output := result .StructuredContent
441+ if output == nil {
442+ output = conversion .ContentArrayToMap (result .Content )
443+ }
444+
445+ return e .handleToolStepSuccess (ctx , step , workflowCtx , output , result .Content , retryCount )
439446}
440447
441448// callToolWithRetry calls a tool with retry logic using exponential backoff.
449+ // Returns the full ToolCallResult so callers can access both StructuredContent and Content.
442450func (e * workflowEngine ) callToolWithRetry (
443451 ctx context.Context ,
444452 target * vmcp.BackendTarget ,
445453 step * WorkflowStep ,
446454 args map [string ]any ,
447455 _ * WorkflowContext ,
448- ) (map [ string ] any , int , error ) {
456+ ) (* vmcp. ToolCallResult , int , error ) {
449457 maxRetries , initialDelay := e .getRetryConfig (step )
450458
451459 // Configure exponential backoff
@@ -455,7 +463,7 @@ func (e *workflowEngine) callToolWithRetry(
455463 expBackoff .Reset ()
456464
457465 attemptCount := 0
458- operation := func () (map [ string ] any , error ) {
466+ operation := func () (* vmcp. ToolCallResult , error ) {
459467 attemptCount ++
460468 // TODO: For composite tools, we may want to propagate metadata from the parent request
461469 result , err := e .backendClient .CallTool (ctx , target , step .Tool , args , nil )
@@ -482,30 +490,20 @@ func (e *workflowEngine) callToolWithRetry(
482490 return nil , fmt .Errorf ("%w: %s" , vmcp .ErrToolExecutionFailed , errorMsg )
483491 }
484492
485- // Extract output map from result.
486- // Workflow logic uses map[string]any for template variable substitution.
487- // Prefer StructuredContent (already a map), fall back to Content array conversion.
488- // The _meta field is not needed for workflow execution, so we don't extract it.
489- if result .StructuredContent != nil {
490- return result .StructuredContent , nil
491- }
492-
493- // Fallback: convert Content array to map for backward compatibility.
494- // This happens when backends return only Content without StructuredContent.
495- return conversion .ContentArrayToMap (result .Content ), nil
493+ return result , nil
496494 }
497495
498496 // Execute with retry
499497 // Safe conversion: maxRetries is capped by maxRetryCount constant (10)
500- output , err := backoff .Retry (ctx , operation ,
498+ result , err := backoff .Retry (ctx , operation ,
501499 backoff .WithBackOff (expBackoff ),
502500 backoff .WithMaxTries (uint (maxRetries + 1 )), // #nosec G115 -- +1 because it includes the initial attempt
503501 backoff .WithNotify (func (_ error , duration time.Duration ) {
504502 slog .Debug ("retrying step" , "step" , step .ID , "after" , duration )
505503 }),
506504 )
507505
508- return output , attemptCount - 1 , err // Return retry count (attempts - 1)
506+ return result , attemptCount - 1 , err // Return retry count (attempts - 1)
509507}
510508
511509// extractErrorMessage extracts a user-friendly error message from a failed tool call result.
@@ -595,9 +593,10 @@ func (e *workflowEngine) handleToolStepSuccess(
595593 step * WorkflowStep ,
596594 workflowCtx * WorkflowContext ,
597595 output map [string ]any ,
596+ content []vmcp.Content ,
598597 retryCount int ,
599598) error {
600- workflowCtx .RecordStepSuccess (step .ID , output )
599+ workflowCtx .RecordStepSuccess (step .ID , output , content )
601600
602601 // Update retry count
603602 if result , exists := workflowCtx .GetStepResult (step .ID ); exists {
@@ -698,7 +697,7 @@ func (*workflowEngine) handleElicitationAccept(
698697 "content" : response .Content ,
699698 }
700699
701- workflowCtx .RecordStepSuccess (step .ID , output )
700+ workflowCtx .RecordStepSuccess (step .ID , output , nil )
702701 slog .Debug ("step completed with user-provided data" , "step" , step .ID )
703702 return nil
704703}
@@ -772,7 +771,7 @@ func (*workflowEngine) handleElicitationAction(
772771 "action" : reason ,
773772 "skipped" : true ,
774773 }
775- workflowCtx .RecordStepSuccess (step .ID , output )
774+ workflowCtx .RecordStepSuccess (step .ID , output , nil )
776775 // Return a special error that the workflow engine can detect
777776 // For now, we'll just complete the step successfully
778777 return nil
@@ -795,7 +794,7 @@ func (*workflowEngine) handleElicitationAction(
795794 output := map [string ]any {
796795 "action" : reason ,
797796 }
798- workflowCtx .RecordStepSuccess (step .ID , output )
797+ workflowCtx .RecordStepSuccess (step .ID , output , nil )
799798 return nil
800799
801800 default :
0 commit comments