fix(claude-code): extract assistant text from nested message.content in stream()

Claude CLI ≥2.x emits type=assistant events where the response text is
inside message.content[{"type":"text","text":"..."}] rather than a flat
content string. The old handler only checked event.content, so every
token was silently dropped and streaming always returned an empty response.

The handler now checks the flat content field first (backward-compatible),
then falls back to joining all text blocks from message.content[].

Refs: RightNow-AI/openfang#295
This commit is contained in:
jam
2026-03-23 09:35:22 +01:00
parent 4b3b602457
commit 62b697c90e
@@ -524,11 +524,30 @@ impl LlmDriver for ClaudeCodeDriver {
Ok(event) => {
match event.r#type.as_str() {
"content" | "text" | "assistant" | "content_block_delta" => {
if let Some(ref content) = event.content {
full_text.push_str(content);
// Older CLI: flat `content` string.
// CLI ≥2.x (type=assistant): text is nested in
// `message.content[].text`; the flat `content`
// field is absent or null.
let chunk = event.content.clone().unwrap_or_default();
let nested: String = event
.message
.as_ref()
.map(|msg| {
msg.content
.iter()
.filter(|b| b.block_type == "text")
.map(|b| b.text.as_str())
.collect::<Vec<_>>()
.join("")
})
.unwrap_or_default();
let text_chunk =
if !chunk.is_empty() { chunk } else { nested };
if !text_chunk.is_empty() {
full_text.push_str(&text_chunk);
let _ = tx
.send(StreamEvent::TextDelta {
text: content.clone(),
text: text_chunk,
})
.await;
}