llm-bridge-core 0.2.5

Protocol transform library for LLM API translation between Anthropic and OpenAI.
Documentation

llm-bridge-core

Protocol transform library for LLM API translation between Anthropic and OpenAI.

CI crates.io docs.rs license

Overview

llm-bridge-core is a Rust library that translates request payloads, response payloads, and streaming SSE events between the Anthropic Messages API and OpenAI-compatible APIs (Chat Completions and Responses). It is library-first, protocol-only, and has zero gateway concerns — no auth, billing, routing, or rate-limiting.

No feature flags are required; all capabilities are included by default. No workspace-internal dependencies — it can be used standalone from crates.io.

Features

  • Anthropic → OpenAI Chat Completions: Convert Anthropic Messages requests to OpenAI Chat Completions format
  • Anthropic → OpenAI Responses: Convert Anthropic Messages requests to OpenAI Responses format
  • OpenAI Chat Completions → Anthropic: Convert OpenAI Chat Completions requests back to Anthropic Messages
  • OpenAI Responses → Anthropic: Convert OpenAI Responses requests back to Anthropic Messages
  • Streaming: Cross-protocol SSE → SSE translation with cross-chunk state management
  • Thinking: Anthropic thinking blocks ↔ OpenAI reasoning content translation
  • Tool use: Cross-protocol tool call / tool result translation with semantic equivalence
  • Response transforms: Anthropic response → OpenAI/Responses format for upstream response mapping
  • Header transforms: Automatic content-type detection and x-no-response-completion handling

Unsupported fields are logged before omission rather than silently dropped.

Installation

[dependencies]
llm-bridge-core = "0.2"

MSRV: Rust 1.80+ (edition 2024).

Quick Start

Non-streaming

use llm_bridge_core::model::{TransformRequest, TransformResponse};
use llm_bridge_core::transform;

let req = TransformRequest::builder()
    .path("/v1/messages")
    .body(anthropic_json_bytes)
    .build();

// Anthropic → OpenAI Chat Completions
let response: TransformResponse = transform::anthropic_to_openai(&req)?;

// Anthropic → OpenAI Responses
let response: TransformResponse = transform::anthropic_to_openai_responses(&req)?;

// OpenAI Chat Completions → Anthropic
let response: TransformResponse = transform::openai_to_anthropic(&req)?;

// OpenAI Responses → Anthropic
let response: TransformResponse = transform::responses_to_anthropic(&req)?;

Streaming

use llm_bridge_core::model::{ApiFormat, StreamState};
use llm_bridge_core::stream;

let mut state = StreamState::default();

// Parse SSE frames and get structured events
let events = stream::transform_stream_events(sse_bytes, ApiFormat::OpenaiChat, &mut state)?;

// Or convert directly to target SSE bytes
let anthropic_sse = stream::transform_stream_to_anthropic_sse(
    sse_bytes, ApiFormat::OpenaiChat, &mut state,
)?;
let openai_sse = stream::transform_stream_to_openai_sse(
    sse_bytes, ApiFormat::AnthropicMessages, &mut state,
)?;

Key Types

The public API is organized into three modules:

Module Key Types Purpose
model TransformRequest, TransformResponse, TransformError, ApiFormat, ContentBlock, StreamEvent, StreamState, Usage, StopReason Core data model and error types
transform anthropic_to_openai, openai_to_anthropic, responses_to_anthropic, anthropic_to_openai_responses, transform_stream, etc. Non-streaming and streaming protocol transforms
stream transform_stream_events, transform_stream_to_anthropic_sse, transform_stream_to_openai_sse, SseFrame, parse_sse_frames, events_to_sse Low-level SSE parsing and streaming transform primitives

Error Handling

All transforms return Result<_, TransformError>. The error enum provides structured variants you can match on:

Variant When
InvalidFormat Malformed JSON or unsupported format
MissingRequiredField Required field absent in request/response
BufferLimitExceeded Input exceeds resource limits (see below)
StreamInterrupted SSE stream ended unexpectedly
UpstreamError Upstream provider returned an error
LossyDowngrade Field unsupported by target protocol (logged, then omitted)

Use TransformError::sanitized_message() for safe client-facing error strings.

Resource Limits

Constant Value Purpose
MAX_SSE_STREAM_BYTES 1 MB Maximum total SSE data processed per stream chunk
MAX_MESSAGES_COUNT 10,000 Maximum messages per request
MAX_JSON_DEPTH 64 Maximum JSON nesting depth for input validation

Protocol Translation Matrix

Non-streaming

Source → Target Function Status
Anthropic → OpenAI Chat Completions transform::anthropic_to_openai
OpenAI Chat Completions → Anthropic transform::openai_to_anthropic
Anthropic → OpenAI Responses transform::anthropic_to_openai_responses
OpenAI Responses → Anthropic transform::responses_to_anthropic

Response Transforms

Transform Function
Anthropic response → OpenAI Chat format transform::anthropic_response_to_openai_response
Anthropic response → OpenAI Responses format transform::anthropic_response_to_responses_response
OpenAI response → Anthropic format transform::openai_response_to_anthropic_message

Streaming

Source SSE → Target SSE Function Status
OpenAI Chat → Anthropic stream::transform_stream_to_anthropic_sse
OpenAI Responses → Anthropic stream::transform_stream_to_anthropic_sse
Anthropic → OpenAI Chat Completions stream::transform_stream_to_openai_sse
Anthropic → OpenAI Responses stream::transform_stream_to_openai_responses_sse

Same-protocol passthrough (e.g., Anthropic → Anthropic) is handled outside the transform core — it is a caller/proxy concern.

Crate Structure

crates/core/src/
├── lib.rs              # Public API: model, stream, transform modules
├── model.rs            # Core types, errors, resource limits, validation
├── transform/          # Non-streaming protocol translation
│   ├── mod.rs          # Public re-exports
│   ├── anthropic_to_openai.rs
│   ├── anthropic_to_responses.rs
│   ├── openai_to_anthropic.rs
│   ├── responses_to_anthropic.rs
│   ├── response_transforms.rs
│   ├── header_helpers.rs
│   ├── shared.rs
│   ├── streaming_entry.rs
│   └── tests.rs
├── stream/             # Streaming SSE protocol translation
│   ├── mod.rs          # Public re-exports and stream dispatcher
│   ├── sse_parser.rs   # SSE frame parser
│   ├── sse_output.rs   # SSE serialization (events → SSE)
│   ├── frame_dispatch.rs
│   ├── anthropic_to_openai.rs
│   ├── anthropic_to_responses.rs
│   ├── responses_to_anthropic_stream.rs
│   ├── openai_stream.rs
│   ├── openai_types.rs
│   ├── anthropic_types.rs
│   ├── stream_helpers.rs
│   └── tests.rs
├── examples/           # Runnable examples (see below)
│   ├── basic_nonstream.rs
│   ├── all_transforms.rs
│   ├── streaming_text.rs
│   ├── streaming_tool_use.rs
│   ├── error_handling.rs
│   ├── chat-roundtrip.rs
│   └── http-proxy.rs
└── tests/              # Integration tests
    └── end_to_end_fixtures.rs

Running Examples

All examples are self-contained (except http-proxy which needs API keys). No network access required.

cargo run --example basic_nonstream       # Anthropic → OpenAI non-streaming
cargo run --example all_transforms        # All transform paths comparison
cargo run --example streaming_text        # OpenAI SSE → Anthropic SSE text stream
cargo run --example streaming_tool_use    # Streaming tool call translation
cargo run --example error_handling        # Error handling patterns
cargo run --example chat-roundtrip        # Anthropic ↔ OpenAI roundtrip verification

For the HTTP proxy example with primary/backup failover, see examples/README.md.

Safety

#![forbid(unsafe_code)] — this crate contains zero unsafe code.

Documentation

Versioning

This crate follows semantic versioning. Breaking changes to the transform API bump the minor version while the major version is 0.

License

MIT