Ignore reasoning blocks in runtime adapters without affecting tool/text flows
After the parser can accept thinking-style blocks, the CLI and tools adapters must explicitly ignore them so only user-visible text and tool calls drive runtime behavior. This keeps reasoning metadata from surfacing as text or interfering with tool accumulation. Constraint: Runtime behavior must remain unchanged for normal text/tool streaming Rejected: Treat thinking blocks as assistant text | would leak hidden reasoning into visible output and session flow Confidence: high Scope-risk: narrow Directive: If future features need persisted reasoning blocks, add a dedicated runtime representation instead of overloading text handling Tested: cargo test -p rusty-claude-cli response_to_events_ignores_thinking_blocks -- --nocapture; cargo test -p tools response_to_events_ignores_thinking_blocks -- --nocapture Not-tested: End-to-end interactive run against a live thinking-enabled model
This commit is contained in:
@@ -2557,6 +2557,8 @@ impl ApiClient for AnthropicRuntimeClient {
|
|||||||
input.push_str(&partial_json);
|
input.push_str(&partial_json);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ContentBlockDelta::ThinkingDelta { .. }
|
||||||
|
| ContentBlockDelta::SignatureDelta { .. } => {}
|
||||||
},
|
},
|
||||||
ApiStreamEvent::ContentBlockStop(_) => {
|
ApiStreamEvent::ContentBlockStop(_) => {
|
||||||
if let Some(rendered) = markdown_stream.flush(&renderer) {
|
if let Some(rendered) = markdown_stream.flush(&renderer) {
|
||||||
@@ -3056,6 +3058,7 @@ fn push_output_block(
|
|||||||
};
|
};
|
||||||
*pending_tool = Some((id, name, initial_input));
|
*pending_tool = Some((id, name, initial_input));
|
||||||
}
|
}
|
||||||
|
OutputContentBlock::Thinking { .. } | OutputContentBlock::RedactedThinking { .. } => {}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -4007,4 +4010,43 @@ mod tests {
|
|||||||
if name == "read_file" && input == "{\"path\":\"rust/Cargo.toml\"}"
|
if name == "read_file" && input == "{\"path\":\"rust/Cargo.toml\"}"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn response_to_events_ignores_thinking_blocks() {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
let events = response_to_events(
|
||||||
|
MessageResponse {
|
||||||
|
id: "msg-3".to_string(),
|
||||||
|
kind: "message".to_string(),
|
||||||
|
model: "claude-opus-4-6".to_string(),
|
||||||
|
role: "assistant".to_string(),
|
||||||
|
content: vec![
|
||||||
|
OutputContentBlock::Thinking {
|
||||||
|
thinking: "step 1".to_string(),
|
||||||
|
signature: Some("sig_123".to_string()),
|
||||||
|
},
|
||||||
|
OutputContentBlock::Text {
|
||||||
|
text: "Final answer".to_string(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stop_reason: Some("end_turn".to_string()),
|
||||||
|
stop_sequence: None,
|
||||||
|
usage: Usage {
|
||||||
|
input_tokens: 1,
|
||||||
|
output_tokens: 1,
|
||||||
|
cache_creation_input_tokens: 0,
|
||||||
|
cache_read_input_tokens: 0,
|
||||||
|
},
|
||||||
|
request_id: None,
|
||||||
|
},
|
||||||
|
&mut out,
|
||||||
|
)
|
||||||
|
.expect("response conversion should succeed");
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
&events[0],
|
||||||
|
AssistantEvent::TextDelta(text) if text == "Final answer"
|
||||||
|
));
|
||||||
|
assert!(!String::from_utf8(out).expect("utf8").contains("step 1"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1953,6 +1953,8 @@ impl ApiClient for AnthropicRuntimeClient {
|
|||||||
input.push_str(&partial_json);
|
input.push_str(&partial_json);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ContentBlockDelta::ThinkingDelta { .. }
|
||||||
|
| ContentBlockDelta::SignatureDelta { .. } => {}
|
||||||
},
|
},
|
||||||
ApiStreamEvent::ContentBlockStop(_) => {
|
ApiStreamEvent::ContentBlockStop(_) => {
|
||||||
if let Some((id, name, input)) = pending_tool.take() {
|
if let Some((id, name, input)) = pending_tool.take() {
|
||||||
@@ -2147,6 +2149,7 @@ fn push_output_block(
|
|||||||
};
|
};
|
||||||
*pending_tool = Some((id, name, initial_input));
|
*pending_tool = Some((id, name, initial_input));
|
||||||
}
|
}
|
||||||
|
OutputContentBlock::Thinking { .. } | OutputContentBlock::RedactedThinking { .. } => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3192,8 +3195,9 @@ mod tests {
|
|||||||
use super::{
|
use super::{
|
||||||
agent_permission_policy, allowed_tools_for_subagent, execute_agent_with_spawn,
|
agent_permission_policy, allowed_tools_for_subagent, execute_agent_with_spawn,
|
||||||
execute_tool, final_assistant_text, mvp_tool_specs, persist_agent_terminal_state,
|
execute_tool, final_assistant_text, mvp_tool_specs, persist_agent_terminal_state,
|
||||||
AgentInput, AgentJob, GlobalToolRegistry, SubagentToolExecutor,
|
response_to_events, AgentInput, AgentJob, GlobalToolRegistry, SubagentToolExecutor,
|
||||||
};
|
};
|
||||||
|
use api::{MessageResponse, OutputContentBlock, Usage};
|
||||||
use plugins::{PluginTool, PluginToolDefinition, PluginToolPermission};
|
use plugins::{PluginTool, PluginToolDefinition, PluginToolPermission};
|
||||||
use runtime::{
|
use runtime::{
|
||||||
ApiRequest, AssistantEvent, ConversationRuntime, RuntimeError, Session, ToolExecutor,
|
ApiRequest, AssistantEvent, ConversationRuntime, RuntimeError, Session, ToolExecutor,
|
||||||
@@ -4026,6 +4030,42 @@ mod tests {
|
|||||||
let _ = std::fs::remove_file(path);
|
let _ = std::fs::remove_file(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn response_to_events_ignores_thinking_blocks() {
|
||||||
|
let events = response_to_events(MessageResponse {
|
||||||
|
id: "msg-1".to_string(),
|
||||||
|
kind: "message".to_string(),
|
||||||
|
model: "claude-opus-4-6".to_string(),
|
||||||
|
role: "assistant".to_string(),
|
||||||
|
content: vec![
|
||||||
|
OutputContentBlock::Thinking {
|
||||||
|
thinking: "step 1".to_string(),
|
||||||
|
signature: Some("sig_123".to_string()),
|
||||||
|
},
|
||||||
|
OutputContentBlock::Text {
|
||||||
|
text: "Final answer".to_string(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stop_reason: Some("end_turn".to_string()),
|
||||||
|
stop_sequence: None,
|
||||||
|
usage: Usage {
|
||||||
|
input_tokens: 1,
|
||||||
|
output_tokens: 1,
|
||||||
|
cache_creation_input_tokens: 0,
|
||||||
|
cache_read_input_tokens: 0,
|
||||||
|
},
|
||||||
|
request_id: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
&events[0],
|
||||||
|
AssistantEvent::TextDelta(text) if text == "Final answer"
|
||||||
|
));
|
||||||
|
assert!(!events
|
||||||
|
.iter()
|
||||||
|
.any(|event| matches!(event, AssistantEvent::ToolUse { .. })));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn agent_rejects_blank_required_fields() {
|
fn agent_rejects_blank_required_fields() {
|
||||||
let missing_description = execute_tool(
|
let missing_description = execute_tool(
|
||||||
|
|||||||
Reference in New Issue
Block a user