va-ai-api-bridge 0.1.7

Protocol translation primitives for AI API request and response shapes.
Documentation
mod state;

use serde_json::{json, Value};

use crate::translator::{anthropic, common};
use crate::{EncodeState, Result, UniversalEvent, WireEvent};

use self::state::*;

pub(super) fn encode(events: &[UniversalEvent], state: &mut EncodeState) -> Result<Vec<WireEvent>> {
    let mut wire_events = Vec::new();
    for event in events {
        match event {
            UniversalEvent::ResponseStart { id, model, .. } => {
                wire_events.push(common::wire_event(json!({
                    "type": "message_start",
                    "message": {
                        "id": id,
                        "type": "message",
                        "model": model,
                        "role": "assistant",
                        "content": [],
                        "stop_reason": Value::Null,
                        "stop_sequence": Value::Null,
                        "usage": {
                            "input_tokens": 0,
                            "output_tokens": 0
                        }
                    }
                })))
            }
            UniversalEvent::ContentStart { index, block } => {
                close_open_content_blocks_before_start(state, &mut wire_events, *index);
                reserve_index(state, *index);
                remember_content_started(state, *index);
                match block {
                    crate::ContentBlock::ToolCall { id, name, .. } => {
                        remember_tool_id(state, id);
                        remember_tool_index(state, id, *index);
                        state
                            .extensions
                            .insert(tool_name_key(id), Value::String(name.clone()));
                        state
                            .extensions
                            .insert(tool_started_key(id), Value::Bool(true));
                        wire_events.push(common::wire_event(json!({
                            "type": "content_block_start",
                            "index": index,
                            "content_block": {
                                "type": "tool_use",
                                "id": id,
                                "name": name,
                                "input": {}
                            }
                        })));
                    }
                    _ => wire_events.push(common::wire_event(json!({
                        "type": "content_block_start",
                        "index": index,
                        "content_block": anthropic::block_to_anthropic_block(block)
                    }))),
                }
            }
            UniversalEvent::TextDelta { index, text } => {
                wire_events.push(common::wire_event(json!({
                    "type": "content_block_delta",
                    "index": index,
                    "delta": {
                        "type": "text_delta",
                        "text": text
                    }
                })))
            }
            UniversalEvent::ReasoningDelta { index, text } => {
                wire_events.push(common::wire_event(json!({
                    "type": "content_block_delta",
                    "index": index,
                    "delta": {
                        "type": "thinking_delta",
                        "thinking": text
                    }
                })))
            }
            UniversalEvent::ToolCallDelta {
                id,
                name,
                arguments_delta,
                ..
            } => {
                let index = ensure_tool_block_started(state, &mut wire_events, id, name.as_deref());
                if !arguments_delta.is_empty() {
                    wire_events.push(common::wire_event(json!({
                        "type": "content_block_delta",
                        "index": index,
                        "delta": {
                            "type": "input_json_delta",
                            "partial_json": arguments_delta
                        }
                    })));
                }
            }
            UniversalEvent::ContentDone { index, .. } => {
                remember_content_closed(state, *index);
                if let Some(id) = tool_id_for_index(state, *index) {
                    state
                        .extensions
                        .insert(tool_closed_key(&id), Value::Bool(true));
                }
                wire_events.push(common::wire_event(json!({
                    "type": "content_block_stop",
                    "index": index
                })));
            }
            UniversalEvent::MessageDone {
                finish_reason,
                usage,
                ..
            } => {
                remember_message_done(state, *finish_reason, usage);
            }
            UniversalEvent::ResponseDone { usage, .. } => {
                close_open_content_blocks(state, &mut wire_events);
                close_open_tool_blocks(state, &mut wire_events);
                if !message_delta_sent(state) {
                    let usage = response_usage(usage, state);
                    wire_events.push(common::wire_event(json!({
                        "type": "message_delta",
                        "delta": {
                            "stop_reason": finish_to_anthropic(normalize_finish_reason(
                                pending_finish_reason(state),
                                state,
                            )),
                            "stop_sequence": Value::Null
                        },
                        "usage": usage
                    })));
                    state
                        .extensions
                        .insert("anthropicMessageDeltaSent".to_string(), Value::Bool(true));
                }
                wire_events.push(common::wire_event(json!({
                "type": "message_stop"
                })));
            }
            UniversalEvent::Error { message, raw } => wire_events.push(common::wire_event(json!({
                "type": "error",
                "error": {
                    "message": message,
                    "raw": raw
                }
            }))),
            UniversalEvent::Unknown { raw, .. } => {
                wire_events.push(common::wire_event(raw.clone()))
            }
            UniversalEvent::MessageStart { .. } => {}
        }
    }
    Ok(wire_events)
}

#[cfg(test)]
mod tests;