Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ agentscope:
server-side-memory: true
max-thread-sessions: 1000
session-timeout-minutes: 30
enable-reasoning: false

# Logging
logging:
Expand Down
50 changes: 49 additions & 1 deletion agentscope-examples/agui/src/main/resources/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,19 @@
.message.tool {
background: rgba(255, 158, 100, 0.1);
border-left: 3px solid var(--accent-orange);
margin: 0 60px;
margin: 0 60px 16px 60px;
font-size: 0.85rem;
}

.message.reasoning {
background: rgba(158, 206, 106, 0.08);
border-left: 3px solid var(--accent-green);
margin: 0 60px 16px 60px;
font-size: 0.85rem;
font-style: italic;
opacity: 0.9;
}

.message.error {
background: rgba(247, 118, 142, 0.1);
border-left: 3px solid var(--accent-red);
Expand All @@ -137,6 +146,7 @@
.message.user .message-role { color: var(--text-secondary); }
.message.assistant .message-role { color: var(--accent-blue); }
.message.tool .message-role { color: var(--accent-orange); }
.message.reasoning .message-role { color: var(--accent-green); }

.message-content {
white-space: pre-wrap;
Expand Down Expand Up @@ -327,6 +337,14 @@ <h1>AgentScope AG-UI Demo</h1>
return;
}

if (append && role === 'reasoning' && currentReasoningDiv) {
// Append to current reasoning message
const contentEl = currentReasoningDiv.querySelector('.message-content');
contentEl.textContent += content;
messages.scrollTop = messages.scrollHeight;
return;
}

const div = document.createElement('div');
div.className = `message ${role}`;
div.innerHTML = `
Expand All @@ -338,6 +356,8 @@ <h1>AgentScope AG-UI Demo</h1>

if (role === 'assistant') {
currentAssistantDiv = div;
} else if (role === 'reasoning') {
currentReasoningDiv = div;
}
}

Expand Down Expand Up @@ -380,6 +400,9 @@ <h1>AgentScope AG-UI Demo</h1>

let assistantContent = '';
let currentMessageId = null;
let reasoningContent = '';
let currentReasoningMessageId = null;
let currentReasoningDiv = null;

try {
await client.run({
Expand All @@ -390,6 +413,29 @@ <h1>AgentScope AG-UI Demo</h1>
onRunStarted: () => {
console.log('Run started');
currentAssistantDiv = null;
currentReasoningDiv = null;
reasoningContent = '';
},
onReasoningMessageStart: (messageId, role) => {
console.log('Reasoning message start:', messageId, role);
hideTypingIndicator();
currentReasoningMessageId = messageId;
reasoningContent = '';
currentReasoningDiv = null;
},
onReasoningContent: (delta, messageId) => {
console.log('Reasoning content delta:', delta);
if (reasoningContent === '') {
appendMessage('reasoning', delta);
} else {
appendMessage('reasoning', delta, true);
}
reasoningContent += delta;
},
onReasoningMessageEnd: (messageId) => {
console.log('Reasoning message end:', messageId);
currentReasoningDiv = null;
reasoningContent = '';
},
onTextMessageStart: (messageId, role) => {
console.log('Text message start:', messageId, role);
Expand Down Expand Up @@ -463,6 +509,8 @@ <h1>AgentScope AG-UI Demo</h1>
stopBtn.style.display = 'none';
hideTypingIndicator();
currentAssistantDiv = null;
currentReasoningDiv = null;
reasoningContent = '';
if (statusText.textContent === 'Running...') {
setStatus('ready', 'Ready');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
* messages: [{ id: 'msg-1', role: 'user', content: 'Hello!' }]
* }, {
* onTextContent: (delta) => console.log(delta),
* onReasoningContent: (delta) => console.log('Reasoning:', delta),
* onRunFinished: () => console.log('Done')
* });
*/
Expand Down Expand Up @@ -71,6 +72,9 @@ class AguiClient {
* @param {Object} [input.state] - Optional state
* @param {Object} [input.forwardedProps] - Optional forwarded properties
* @param {Object} callbacks - Event callbacks
* @param {Function} [callbacks.onReasoningMessageStart] - Called when reasoning message starts
* @param {Function} [callbacks.onReasoningContent] - Called with reasoning content delta
* @param {Function} [callbacks.onReasoningMessageEnd] - Called when reasoning message ends
* @returns {Promise} Resolves when the run completes
*/
async run(input, callbacks = {}) {
Expand Down Expand Up @@ -220,6 +224,22 @@ class AguiClient {
callbacks.onTextMessageEnd?.(event.messageId);
break;

case 'REASONING_MESSAGE_START':
callbacks.onReasoningMessageStart?.(event.messageId, event.role);
break;

case 'REASONING_MESSAGE_CONTENT':
// Ensure delta is not null/undefined
const reasoningDelta = event.delta || '';
if (reasoningDelta) {
callbacks.onReasoningContent?.(reasoningDelta, event.messageId);
}
break;

case 'REASONING_MESSAGE_END':
callbacks.onReasoningMessageEnd?.(event.messageId);
break;

case 'TOOL_CALL_START':
callbacks.onToolCallStart?.(event.toolCallId, event.toolCallName);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ public class AguiAdapterConfig {
private final ToolMergeMode toolMergeMode;
private final boolean emitStateEvents;
private final boolean emitToolCallArgs;
private final boolean enableReasoning;
private final Duration runTimeout;
private final String defaultAgentId;

private AguiAdapterConfig(Builder builder) {
this.toolMergeMode = builder.toolMergeMode;
this.emitStateEvents = builder.emitStateEvents;
this.emitToolCallArgs = builder.emitToolCallArgs;
this.enableReasoning = builder.enableReasoning;
this.runTimeout = builder.runTimeout;
this.defaultAgentId = builder.defaultAgentId;
}
Expand Down Expand Up @@ -67,6 +69,19 @@ public boolean isEmitToolCallArgs() {
return emitToolCallArgs;
}

/**
* Check if reasoning/thinking content should be emitted.
*
* <p>When enabled, ThinkingBlock content will be converted to REASONING_* events
* according to the AG-UI Reasoning draft specification. When disabled (default),
* ThinkingBlock content is ignored and no reasoning events are emitted.
*
* @return true if reasoning events should be emitted
*/
public boolean isEnableReasoning() {
return enableReasoning;
}

/**
* Get the run timeout duration.
*
Expand Down Expand Up @@ -111,6 +126,7 @@ public static class Builder {
private ToolMergeMode toolMergeMode = ToolMergeMode.MERGE_FRONTEND_PRIORITY;
private boolean emitStateEvents = true;
private boolean emitToolCallArgs = true;
private boolean enableReasoning = false;
private Duration runTimeout = Duration.ofMinutes(10);
private String defaultAgentId;

Expand Down Expand Up @@ -147,6 +163,21 @@ public Builder emitToolCallArgs(boolean emitToolCallArgs) {
return this;
}

/**
* Set whether to enable reasoning/thinking content output.
*
* <p>When enabled, ThinkingBlock content will be converted to REASONING_* events
* according to the AG-UI Reasoning draft specification. Default is false to ensure
* backward compatibility and privacy compliance.
*
* @param enableReasoning true to enable reasoning events
* @return This builder
*/
public Builder enableReasoning(boolean enableReasoning) {
this.enableReasoning = enableReasoning;
return this;
}

/**
* Set the run timeout duration.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import io.agentscope.core.message.ContentBlock;
import io.agentscope.core.message.Msg;
import io.agentscope.core.message.TextBlock;
import io.agentscope.core.message.ThinkingBlock;
import io.agentscope.core.message.ToolResultBlock;
import io.agentscope.core.message.ToolUseBlock;
import io.agentscope.core.util.JsonException;
Expand All @@ -46,10 +47,18 @@
*
* <p><b>Event Mapping:</b>
* <ul>
* <li>AgentScope REASONING events → AG-UI TEXT_MESSAGE_* events</li>
* <li>AgentScope REASONING events → AG-UI TEXT_MESSAGE_* events (for TextBlock)</li>
* <li>AgentScope REASONING events → AG-UI REASONING_* events (for ThinkingBlock, when enabled)</li>
* <li>AgentScope TOOL_RESULT events → AG-UI TOOL_CALL_END events</li>
* <li>ToolUseBlock content → AG-UI TOOL_CALL_START events</li>
* </ul>
*
* <p><b>Reasoning Support:</b>
* <ul>
* <li>ThinkingBlock content is converted to REASONING_* events according to AG-UI Reasoning draft</li>
* <li>Reasoning output is disabled by default (enableReasoning=false) for backward compatibility</li>
* <li>Set enableReasoning=true in AguiAdapterConfig to enable reasoning events</li>
* </ul>
*/
public class AguiAgentAdapter {

Expand Down Expand Up @@ -156,6 +165,41 @@ private List<AguiEvent> convertEvent(Event event, EventConversionState state) {
state.endMessage(messageId);
}
}
} else if (block instanceof ThinkingBlock thinkingBlock) {
// Handle thinking blocks - convert to REASONING_* events (only if enabled)
// According to AG-UI Reasoning draft: https://docs.ag-ui.com/drafts/reasoning
if (config.isEnableReasoning()) {
String thinking = thinkingBlock.getThinking();
if (thinking != null && !thinking.isEmpty()) {
String messageId = msg.getId();

// Start reasoning message if not started
if (!state.hasStartedReasoningMessage(messageId)) {
events.add(
new AguiEvent.ReasoningMessageStart(
state.threadId,
state.runId,
messageId,
"assistant"));
state.startReasoningMessage(messageId);
}

if (!event.isLast()) {
// In incremental mode, thinking is already the delta
events.add(
new AguiEvent.ReasoningMessageContent(
state.threadId, state.runId, messageId, thinking));
} else {
// End reasoning message if this is the last event
events.add(
new AguiEvent.ReasoningMessageEnd(
state.threadId, state.runId, messageId));
state.endReasoningMessage(messageId);
}
}
}
// If reasoning is disabled, ThinkingBlock content is ignored (backward
// compatibility)
} else if (block instanceof ToolUseBlock toolUse) {
// End any active text message before starting tool call
if (state.hasActiveTextMessage()) {
Expand Down Expand Up @@ -242,6 +286,14 @@ private Flux<AguiEvent> finishRun(EventConversionState state) {
}
}

// End any reasoning messages that weren't properly ended
for (String messageId : state.getStartedReasoningMessages()) {
if (!state.hasEndedReasoningMessage(messageId)) {
events.add(
new AguiEvent.ReasoningMessageEnd(state.threadId, state.runId, messageId));
}
}

// Emit RUN_FINISHED
events.add(new AguiEvent.RunFinished(state.threadId, state.runId));

Expand Down Expand Up @@ -300,6 +352,8 @@ private static class EventConversionState {
private final Set<String> endedMessages = new LinkedHashSet<>();
private final Set<String> startedToolCalls = new LinkedHashSet<>();
private final Set<String> endedToolCalls = new LinkedHashSet<>();
private final Set<String> startedReasoningMessages = new LinkedHashSet<>();
private final Set<String> endedReasoningMessages = new LinkedHashSet<>();
private String currentTextMessageId = null;

EventConversionState(String threadId, String runId) {
Expand Down Expand Up @@ -358,5 +412,25 @@ boolean hasEndedToolCall(String toolCallId) {
Set<String> getStartedToolCalls() {
return startedToolCalls;
}

boolean hasStartedReasoningMessage(String messageId) {
return startedReasoningMessages.contains(messageId);
}

void startReasoningMessage(String messageId) {
startedReasoningMessages.add(messageId);
}

void endReasoningMessage(String messageId) {
endedReasoningMessages.add(messageId);
}

boolean hasEndedReasoningMessage(String messageId) {
return endedReasoningMessages.contains(messageId);
}

Set<String> getStartedReasoningMessages() {
return startedReasoningMessages;
}
}
}
Loading
Loading