Re-add markdown streaming (#2029)

Wait for newlines, then render markdown on a line by line basis. Word wrap it for the current terminal size and then spit it out line by line into the UI. Also adds tests and fixes some UI regressions.
This commit is contained in:
easong-openai
2025-08-12 17:37:28 -07:00
committed by GitHub
parent 97a27ffc77
commit 6340acd885
42 changed files with 35887 additions and 1026 deletions

View File

@@ -588,6 +588,9 @@ where
Poll::Ready(Some(Ok(ResponseEvent::ReasoningSummaryDelta(_)))) => {
continue;
}
Poll::Ready(Some(Ok(ResponseEvent::ReasoningSummaryPartAdded))) => {
continue;
}
}
}
}

View File

@@ -507,12 +507,18 @@ async fn process_sse<S>(
| "response.function_call_arguments.delta"
| "response.in_progress"
| "response.output_item.added"
| "response.output_text.done"
| "response.reasoning_summary_part.added"
| "response.reasoning_summary_text.done" => {
// Currently, we ignore these events, but we handle them
| "response.output_text.done" => {
// Currently, we ignore this event, but we handle it
// separately to skip the logging message in the `other` case.
}
"response.reasoning_summary_part.added" => {
// Boundary between reasoning summary sections (e.g., titles).
let event = ResponseEvent::ReasoningSummaryPartAdded;
if tx_event.send(Ok(event)).await.is_err() {
return;
}
}
"response.reasoning_summary_text.done" => {}
other => debug!(other, "sse event"),
}
}

View File

@@ -144,6 +144,7 @@ pub enum ResponseEvent {
OutputTextDelta(String),
ReasoningSummaryDelta(String),
ReasoningContentDelta(String),
ReasoningSummaryPartAdded,
}
#[derive(Debug, Serialize)]

View File

@@ -75,6 +75,7 @@ use crate::protocol::AgentReasoningDeltaEvent;
use crate::protocol::AgentReasoningEvent;
use crate::protocol::AgentReasoningRawContentDeltaEvent;
use crate::protocol::AgentReasoningRawContentEvent;
use crate::protocol::AgentReasoningSectionBreakEvent;
use crate::protocol::ApplyPatchApprovalRequestEvent;
use crate::protocol::AskForApproval;
use crate::protocol::BackgroundEventEvent;
@@ -1477,6 +1478,13 @@ async fn try_run_turn(
};
sess.tx_event.send(event).await.ok();
}
ResponseEvent::ReasoningSummaryPartAdded => {
let event = Event {
id: sub_id.to_string(),
msg: EventMsg::AgentReasoningSectionBreak(AgentReasoningSectionBreakEvent {}),
};
sess.tx_event.send(event).await.ok();
}
ResponseEvent::ReasoningContentDelta(delta) => {
if sess.show_raw_agent_reasoning {
let event = Event {

View File

@@ -235,8 +235,7 @@ impl SandboxPolicy {
}
}
/// Always returns `true` for now, as we do not yet support restricting read
/// access.
/// Always returns `true`; restricting read access is not supported.
pub fn has_full_disk_read_access(&self) -> bool {
true
}
@@ -384,6 +383,8 @@ pub enum EventMsg {
/// Agent reasoning content delta event from agent.
AgentReasoningRawContentDelta(AgentReasoningRawContentDeltaEvent),
/// Signaled when the model begins a new reasoning summary section (e.g., a new titled block).
AgentReasoningSectionBreak(AgentReasoningSectionBreakEvent),
/// Ack the client's configure message.
SessionConfigured(SessionConfiguredEvent),
@@ -531,6 +532,9 @@ pub struct AgentReasoningRawContentDeltaEvent {
pub delta: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AgentReasoningSectionBreakEvent {}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AgentReasoningDeltaEvent {
pub delta: String,