2020import java .util .HashMap ;
2121import java .util .List ;
2222import java .util .Map ;
23+ import java .util .concurrent .TimeUnit ;
2324import java .util .stream .Collectors ;
2425
26+ import com .alibaba .cloud .ai .example .manus .planning .service .UserInputService ;
2527import io .micrometer .common .util .StringUtils ;
2628import org .slf4j .Logger ;
2729import org .slf4j .LoggerFactory ;
5052import com .alibaba .cloud .ai .example .manus .recorder .entity .AgentExecutionRecord ;
5153import com .alibaba .cloud .ai .example .manus .recorder .entity .ThinkActRecord ;
5254import com .alibaba .cloud .ai .example .manus .tool .TerminateTool ;
55+ import com .alibaba .cloud .ai .example .manus .tool .ToolCallBiFunctionDef ;
56+ import com .alibaba .cloud .ai .example .manus .tool .FormInputTool ;
57+ import org .springframework .beans .factory .annotation .Autowired ;
5358
5459import static org .springframework .ai .chat .memory .ChatMemory .CONVERSATION_ID ;
5560
@@ -75,6 +80,8 @@ public class DynamicAgent extends ReActAgent {
7580
7681 private final ToolCallingManager toolCallingManager ;
7782
83+ private final UserInputService userInputService ;
84+
7885 public void clearUp (String planId ) {
7986 Map <String , ToolCallBackContext > toolCallBackContext = toolCallbackProvider .getToolCallBackContext ();
8087 for (ToolCallBackContext toolCallBack : toolCallBackContext .values ()) {
@@ -85,18 +92,23 @@ public void clearUp(String planId) {
8592 log .error ("Error cleaning up tool callback context: {}" , e .getMessage (), e );
8693 }
8794 }
95+ // Also remove any pending form input tool for this planId
96+ if (userInputService != null ) {
97+ userInputService .removeFormInputTool (planId );
98+ }
8899 }
89100
90101 public DynamicAgent (LlmService llmService , PlanExecutionRecorder planExecutionRecorder ,
91102 ManusProperties manusProperties , String name , String description , String nextStepPrompt ,
92103 List <String > availableToolKeys , ToolCallingManager toolCallingManager ,
93- Map <String , Object > initialAgentSetting ) {
104+ Map <String , Object > initialAgentSetting , UserInputService userInputService ) {
94105 super (llmService , planExecutionRecorder , manusProperties , initialAgentSetting );
95106 this .agentName = name ;
96107 this .agentDescription = description ;
97108 this .nextStepPrompt = nextStepPrompt ;
98109 this .availableToolKeys = availableToolKeys ;
99110 this .toolCallingManager = toolCallingManager ;
111+ this .userInputService = userInputService ;
100112 }
101113
102114 @ Override
@@ -176,7 +188,6 @@ protected AgentExecResult act() {
176188 thinkActRecord .startAction ("Executing tool: " + toolCall .name (), toolCall .name (), toolCall .arguments ());
177189 ToolExecutionResult toolExecutionResult = toolCallingManager .executeToolCalls (userPrompt , response );
178190
179- // setData(getData());
180191 ToolResponseMessage toolResponseMessage = (ToolResponseMessage ) toolExecutionResult .conversationHistory ()
181192 .get (toolExecutionResult .conversationHistory ().size () - 1 );
182193
@@ -187,16 +198,53 @@ protected AgentExecResult act() {
187198
188199 thinkActRecord .finishAction (llmCallResponse , "SUCCESS" );
189200 String toolcallName = toolCall .name ();
190- AgentExecResult agentExecResult = null ;
191- // 如果是终止工具,则返回完成状态
192- // 否则返回运行状态
193- if (TerminateTool .name .equals (toolcallName )) {
194- agentExecResult = new AgentExecResult (llmCallResponse , AgentState .COMPLETED );
201+
202+ // Handle FormInputTool logic
203+ if (FormInputTool .name .equals (toolcallName )) {
204+ ToolCallBiFunctionDef formInputToolDef = getToolCallBackContext (toolcallName ).getFunctionInstance ();
205+ if (formInputToolDef instanceof FormInputTool ) {
206+ FormInputTool formInputTool = (FormInputTool ) formInputToolDef ;
207+ // Check if the tool is waiting for user input
208+ if (formInputTool .getInputState () == FormInputTool .InputState .AWAITING_USER_INPUT ) {
209+ log .info ("FormInputTool is awaiting user input for planId: {}" , getPlanId ());
210+ userInputService .storeFormInputTool (getPlanId (), formInputTool );
211+ // Wait for user input or timeout
212+ waitForUserInputOrTimeout (formInputTool );
213+
214+ // After waiting, check the state again
215+ if (formInputTool .getInputState () == FormInputTool .InputState .INPUT_RECEIVED ) {
216+ log .info ("User input received for planId: {}" , getPlanId ());
217+ // The UserInputService.submitUserInputs would have updated
218+ // the tool's internal state.
219+ // We can now get the updated state string for the LLM.
220+ llmCallResponse = formInputTool .getCurrentToolStateString ();
221+ // Update the toolResponseMessage in memory for the next LLM
222+ // call
223+ ToolResponseMessage .ToolResponse updatedToolResponse = new ToolResponseMessage .ToolResponse (
224+ toolCall .id (), toolCall .name (), llmCallResponse );
225+ ToolResponseMessage updatedToolResponseMessage = new ToolResponseMessage (
226+ List .of (updatedToolResponse ), Map .of ());
227+ llmService .getAgentMemory ().add (getPlanId (), updatedToolResponseMessage );
228+
229+ }
230+ else if (formInputTool .getInputState () == FormInputTool .InputState .INPUT_TIMEOUT ) {
231+ log .warn ("Input timeout occurred for FormInputTool for planId: {}" , getPlanId ());
232+ userInputService .removeFormInputTool (getPlanId ()); // Clean up
233+ return new AgentExecResult ("Input timeout occurred." , AgentState .IN_PROGRESS ); // Or
234+ // FAILED
235+ }
236+ }
237+ }
195238 }
196- else {
197- agentExecResult = new AgentExecResult (llmCallResponse , AgentState .IN_PROGRESS );
239+
240+ // If the tool is TerminateTool, return completed state
241+ if (TerminateTool .name .equals (toolcallName )) {
242+ userInputService .removeFormInputTool (getPlanId ()); // Clean up any pending
243+ // form
244+ return new AgentExecResult (llmCallResponse , AgentState .COMPLETED );
198245 }
199- return agentExecResult ;
246+
247+ return new AgentExecResult (llmCallResponse , AgentState .IN_PROGRESS );
200248 }
201249 catch (Exception e ) {
202250 ToolCall toolCall = response .getResult ().getOutput ().getToolCalls ().get (0 );
@@ -207,6 +255,7 @@ protected AgentExecResult act() {
207255 log .error (e .getMessage ());
208256
209257 thinkActRecord .recordError (e .getMessage ());
258+ userInputService .removeFormInputTool (getPlanId ()); // Clean up on error
210259
211260 return new AgentExecResult (e .getMessage (), AgentState .FAILED );
212261 }
@@ -255,6 +304,17 @@ protected Message addThinkPrompt(List<Message> messages) {
255304 return systemMessage ;
256305 }
257306
307+ private ToolCallBackContext getToolCallBackContext (String toolKey ) {
308+ Map <String , ToolCallBackContext > toolCallBackContext = toolCallbackProvider .getToolCallBackContext ();
309+ if (toolCallBackContext .containsKey (toolKey )) {
310+ return toolCallBackContext .get (toolKey );
311+ }
312+ else {
313+ log .warn ("Tool callback for {} not found in the map." , toolKey );
314+ return null ;
315+ }
316+ }
317+
258318 @ Override
259319 public List <ToolCallback > getToolCallList () {
260320 List <ToolCallback > toolCallbacks = new ArrayList <>();
@@ -326,4 +386,39 @@ public String convertEnvDataToString() {
326386 return envDataStringBuilder .toString ();
327387 }
328388
389+ // Add a method to wait for user input or handle timeout.
390+ private void waitForUserInputOrTimeout (FormInputTool formInputTool ) {
391+ log .info ("Waiting for user input for planId: {}..." , getPlanId ());
392+ long startTime = System .currentTimeMillis ();
393+ // Get timeout from ManusProperties and convert to milliseconds
394+ long userInputTimeoutMs = getManusProperties ().getUserInputTimeout () * 1000L ;
395+
396+ while (formInputTool .getInputState () == FormInputTool .InputState .AWAITING_USER_INPUT ) {
397+ if (System .currentTimeMillis () - startTime > userInputTimeoutMs ) {
398+ log .warn ("Timeout waiting for user input for planId: {}" , getPlanId ());
399+ formInputTool .handleInputTimeout (); // This will change its state to
400+ // INPUT_TIMEOUT
401+ break ;
402+ }
403+ try {
404+ // Poll for input state change. In a real scenario, this might involve
405+ // a more sophisticated mechanism like a Future or a callback from the UI.
406+ TimeUnit .MILLISECONDS .sleep (500 ); // Check every 500ms
407+ }
408+ catch (InterruptedException e ) {
409+ log .warn ("Interrupted while waiting for user input for planId: {}" , getPlanId ());
410+ Thread .currentThread ().interrupt ();
411+ formInputTool .handleInputTimeout (); // Treat interruption as timeout for
412+ // simplicity
413+ break ;
414+ }
415+ }
416+ if (formInputTool .getInputState () == FormInputTool .InputState .INPUT_RECEIVED ) {
417+ log .info ("User input received for planId: {}" , getPlanId ());
418+ }
419+ else if (formInputTool .getInputState () == FormInputTool .InputState .INPUT_TIMEOUT ) {
420+ log .warn ("User input timed out for planId: {}" , getPlanId ());
421+ }
422+ }
423+
329424}
0 commit comments