claude-rust-provider 0.2.0

Anthropic API provider with SSE streaming
Documentation
use claude_rust_types::{StopReason, StreamEvent};
use serde_json::Value;

pub fn parse_sse_event(event_type: &str, data: &str) -> Option<StreamEvent> {
    let v: Value = serde_json::from_str(data).ok()?;

    match event_type {
        "content_block_start" => {
            let cb = v.get("content_block")?;
            match cb.get("type")?.as_str()? {
                "tool_use" => Some(StreamEvent::ToolUseStart {
                    id: cb.get("id")?.as_str()?.to_string(),
                    name: cb.get("name")?.as_str()?.to_string(),
                }),
                "text" => {
                    let text = cb.get("text")?.as_str().unwrap_or_default();
                    if text.is_empty() {
                        None
                    } else {
                        Some(StreamEvent::ContentDelta {
                            text: text.to_string(),
                        })
                    }
                }
                "thinking" => None,
                _ => None,
            }
        }

        "content_block_delta" => {
            let delta = v.get("delta")?;
            match delta.get("type")?.as_str()? {
                "text_delta" => Some(StreamEvent::ContentDelta {
                    text: delta.get("text")?.as_str()?.to_string(),
                }),
                "input_json_delta" => Some(StreamEvent::ToolUseDelta {
                    json_chunk: delta.get("partial_json")?.as_str()?.to_string(),
                }),
                "thinking_delta" => None,
                _ => None,
            }
        }

        "content_block_stop" => None,

        "message_delta" => {
            let delta = v.get("delta")?;
            let reason = match delta.get("stop_reason")?.as_str()? {
                "end_turn" => StopReason::EndTurn,
                "tool_use" => StopReason::ToolUse,
                "max_tokens" => StopReason::MaxTokens,
                _ => StopReason::EndTurn,
            };
            Some(StreamEvent::Stop { reason })
        }

        "error" => {
            let msg = v
                .get("error")
                .and_then(|e| e.get("message"))
                .and_then(|m| m.as_str())
                .unwrap_or("unknown API error");
            Some(StreamEvent::Error {
                message: msg.to_string(),
            })
        }

        _ => None,
    }
}

pub fn parse_sse_lines(text: &str) -> Vec<(String, String)> {
    let mut events = Vec::new();
    let mut current_event = String::new();
    let mut current_data = String::new();

    for line in text.lines() {
        if line.is_empty() {
            if !current_data.is_empty() {
                events.push((current_event.clone(), current_data.clone()));
                current_event.clear();
                current_data.clear();
            }
            continue;
        }
        if let Some(val) = line.strip_prefix("event: ") {
            current_event = val.to_string();
        } else if let Some(val) = line.strip_prefix("data: ") {
            current_data = val.to_string();
        }
    }

    if !current_data.is_empty() {
        events.push((current_event, current_data));
    }

    events
}