|
33 | 33 | import io.agentscope.core.event.AllToolsDeniedEvent; |
34 | 34 | import io.agentscope.core.event.ConfirmResult; |
35 | 35 | import io.agentscope.core.event.ExceedMaxItersEvent; |
| 36 | +import io.agentscope.core.event.ExternalExecutionResultEvent; |
36 | 37 | import io.agentscope.core.event.ModelCallEndEvent; |
37 | 38 | import io.agentscope.core.event.ModelCallStartEvent; |
38 | 39 | import io.agentscope.core.event.RequestStopEvent; |
@@ -1720,12 +1721,7 @@ private Mono<Msg> doCallInner(List<Msg> msgs) { |
1720 | 1721 | // ConfirmResults (via Msg.METADATA_CONFIRM_RESULTS) before we can proceed. |
1721 | 1722 | List<ToolUseBlock> asking = askingToolCalls(); |
1722 | 1723 | if (!asking.isEmpty()) { |
1723 | | - List<ConfirmResult> confirmResults = extractAndValidateConfirmResults(msgs, asking); |
1724 | | - publishEvent( |
1725 | | - new UserConfirmResultEvent( |
1726 | | - resolvePendingConfirmRequestReplyId(), confirmResults)); |
1727 | | - applyConfirmResults(confirmResults); |
1728 | | - clearPendingConfirmRequest(); |
| 1724 | + validateAndAcceptConfirmResults(msgs, asking); |
1729 | 1725 | return resumeAgent(); |
1730 | 1726 | } |
1731 | 1727 |
|
@@ -1793,15 +1789,15 @@ private List<ConfirmResult> extractConfirmResults(List<Msg> msgs) { |
1793 | 1789 | } |
1794 | 1790 |
|
1795 | 1791 | /** |
1796 | | - * Validate the user-provided confirmation payload against the currently ASKING tool calls. |
| 1792 | + * Validate and accept a permission-HITL resume payload against the currently ASKING tool |
| 1793 | + * calls. |
1797 | 1794 | * |
1798 | 1795 | * <p>Permission HITL resumes with one or more confirmations for currently ASKING tool |
1799 | 1796 | * calls. Confirmations may cover a subset of ASKING calls, but no result may reference a |
1800 | | - * stale or unrelated tool call. Returning a copied list gives downstream event emission and |
1801 | | - * state mutation the same trusted payload. |
| 1797 | + * stale or unrelated tool call. Once accepted, the normalized results are applied to agent |
| 1798 | + * state and the correlated resume event is emitted. |
1802 | 1799 | */ |
1803 | | - private List<ConfirmResult> extractAndValidateConfirmResults( |
1804 | | - List<Msg> msgs, List<ToolUseBlock> asking) { |
| 1800 | + private void validateAndAcceptConfirmResults(List<Msg> msgs, List<ToolUseBlock> asking) { |
1805 | 1801 | List<ConfirmResult> results = extractConfirmResults(msgs); |
1806 | 1802 | if (results.isEmpty()) { |
1807 | 1803 | String pendingSummary = |
@@ -1864,57 +1860,53 @@ private List<ConfirmResult> extractAndValidateConfirmResults( |
1864 | 1860 | } |
1865 | 1861 | normalized.add(result); |
1866 | 1862 | } |
1867 | | - return normalized; |
| 1863 | + |
| 1864 | + String replyId = resolvePendingRequestReplyId(Msg.METADATA_CONFIRM_REQUEST_REPLY_ID); |
| 1865 | + if (!replyId.isEmpty()) { |
| 1866 | + publishEvent(new UserConfirmResultEvent(replyId, normalized)); |
| 1867 | + clearPendingRequestReplyId(Msg.METADATA_CONFIRM_REQUEST_REPLY_ID); |
| 1868 | + } |
| 1869 | + |
| 1870 | + applyConfirmResults(normalized); |
1868 | 1871 | } |
1869 | 1872 |
|
1870 | | - /** |
1871 | | - * Resolve the reply id from the assistant message that originally paused for confirmation. |
1872 | | - * |
1873 | | - * <p>This keeps {@link UserConfirmResultEvent} correlated with the prior |
1874 | | - * {@link RequireUserConfirmEvent}, even though the confirmation arrives in a later |
1875 | | - * {@code agent.call(...)} invocation. |
1876 | | - */ |
1877 | | - private String resolvePendingConfirmRequestReplyId() { |
1878 | | - Msg confirmRequestMsg = findLastAssistantMsg(); |
1879 | | - if (confirmRequestMsg == null || confirmRequestMsg.getMetadata() == null) { |
| 1873 | + /** Resolve the reply id for the pending HITL request stored on the last assistant message. */ |
| 1874 | + private String resolvePendingRequestReplyId(String metadataKey) { |
| 1875 | + Msg requestMsg = findLastAssistantMsg(); |
| 1876 | + if (requestMsg == null || requestMsg.getMetadata() == null) { |
1880 | 1877 | return ""; |
1881 | 1878 | } |
1882 | | - Object raw = confirmRequestMsg.getMetadata().get(Msg.METADATA_CONFIRM_REQUEST_REPLY_ID); |
| 1879 | + Object raw = requestMsg.getMetadata().get(metadataKey); |
1883 | 1880 | return raw instanceof String s ? s : ""; |
1884 | 1881 | } |
1885 | 1882 |
|
1886 | 1883 | /** |
1887 | | - * Persist the reply id for the pending confirmation request on the live assistant message. |
| 1884 | + * Persist the reply id for a pending HITL request on the live assistant message. |
1888 | 1885 | * |
1889 | | - * <p>The assistant message already owns the ASKING {@link ToolUseBlock}s, so storing the |
1890 | | - * correlation metadata there lets the next call recover it from session state. |
| 1886 | + * <p>The assistant message owns the paused {@link ToolUseBlock}s, so storing the correlation |
| 1887 | + * metadata there lets the next call recover it from session state. |
1891 | 1888 | */ |
1892 | | - private void persistPendingConfirmRequest(String replyId) { |
| 1889 | + private void persistPendingRequestReplyId(String metadataKey, String replyId) { |
1893 | 1890 | Msg lastAssistant = findLastAssistantMsg(); |
1894 | 1891 | if (lastAssistant == null) { |
1895 | 1892 | return; |
1896 | 1893 | } |
1897 | 1894 | Map<String, Object> metadata = new HashMap<>(lastAssistant.getMetadata()); |
1898 | | - metadata.put(Msg.METADATA_CONFIRM_REQUEST_REPLY_ID, replyId); |
| 1895 | + metadata.put(metadataKey, replyId); |
1899 | 1896 | replaceLastAssistantMsg(lastAssistant.withMetadata(metadata)); |
1900 | 1897 | } |
1901 | 1898 |
|
1902 | | - /** |
1903 | | - * Remove confirmation-request correlation metadata after the resume payload is accepted. |
1904 | | - * |
1905 | | - * <p>Leaving it behind would make later agent turns appear to belong to an already-closed |
1906 | | - * HITL request. |
1907 | | - */ |
1908 | | - private void clearPendingConfirmRequest() { |
| 1899 | + /** Remove HITL correlation metadata after the resume payload is accepted. */ |
| 1900 | + private void clearPendingRequestReplyId(String metadataKey) { |
1909 | 1901 | Msg lastAssistant = findLastAssistantMsg(); |
1910 | 1902 | if (lastAssistant == null || lastAssistant.getMetadata() == null) { |
1911 | 1903 | return; |
1912 | 1904 | } |
1913 | | - if (!lastAssistant.getMetadata().containsKey(Msg.METADATA_CONFIRM_REQUEST_REPLY_ID)) { |
| 1905 | + if (!lastAssistant.getMetadata().containsKey(metadataKey)) { |
1914 | 1906 | return; |
1915 | 1907 | } |
1916 | 1908 | Map<String, Object> metadata = new HashMap<>(lastAssistant.getMetadata()); |
1917 | | - metadata.remove(Msg.METADATA_CONFIRM_REQUEST_REPLY_ID); |
| 1909 | + metadata.remove(metadataKey); |
1918 | 1910 | replaceLastAssistantMsg(lastAssistant.withMetadata(metadata)); |
1919 | 1911 | } |
1920 | 1912 |
|
@@ -2234,7 +2226,12 @@ private void validateAndAddToolResults(List<Msg> msgs, Set<String> pendingIds) { |
2234 | 2226 | + ", Pending: " |
2235 | 2227 | + pendingIds); |
2236 | 2228 | } |
2237 | | - |
| 2229 | + String replyId = |
| 2230 | + resolvePendingRequestReplyId(Msg.METADATA_EXTERNAL_EXECUTION_REQUEST_REPLY_ID); |
| 2231 | + if (!replyId.isEmpty()) { |
| 2232 | + publishEvent(new ExternalExecutionResultEvent(replyId, results)); |
| 2233 | + clearPendingRequestReplyId(Msg.METADATA_EXTERNAL_EXECUTION_REQUEST_REPLY_ID); |
| 2234 | + } |
2238 | 2235 | state.contextMutable().addAll(msgs); |
2239 | 2236 | } |
2240 | 2237 |
|
@@ -2853,7 +2850,8 @@ Flux<AgentEvent> actingStream( |
2853 | 2850 | // completion; |
2854 | 2851 | // initialise it to empty since no successful execution happened. |
2855 | 2852 | resultHolder.set(List.of()); |
2856 | | - persistPendingConfirmRequest(replyId); |
| 2853 | + persistPendingRequestReplyId( |
| 2854 | + Msg.METADATA_CONFIRM_REQUEST_REPLY_ID, replyId); |
2857 | 2855 | return Flux.<AgentEvent>just( |
2858 | 2856 | new RequireUserConfirmEvent(replyId, pending), |
2859 | 2857 | new RequestStopEvent( |
@@ -3046,6 +3044,10 @@ private Flux<AgentEvent> runToolBatch( |
3046 | 3044 | results); |
3047 | 3045 | if (!suspendedCalls |
3048 | 3046 | .isEmpty()) { |
| 3047 | + persistPendingRequestReplyId( |
| 3048 | + Msg |
| 3049 | + .METADATA_EXTERNAL_EXECUTION_REQUEST_REPLY_ID, |
| 3050 | + replyId); |
3049 | 3051 | sink.next( |
3050 | 3052 | new RequireExternalExecutionEvent( |
3051 | 3053 | replyId, |
|
0 commit comments