# llm-bridge-core
Protocol transform library for LLM API translation between Anthropic and OpenAI.
[](https://github.com/TokenFleet-AI/llm-bridge-rust/actions/workflows/ci.yml)
[](https://crates.io/crates/llm-bridge-core)
[](https://docs.rs/llm-bridge-core)
[](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
```toml
[dependencies]
llm-bridge-core = "0.2"
```
MSRV: Rust 1.80+ (edition 2024).
## Quick Start
### Non-streaming
```rust
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
```rust
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:
| `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:
| `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
| `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
| 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
| 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
| 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.
```bash
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
- **API docs**: [docs.rs/llm-bridge-core](https://docs.rs/llm-bridge-core)
- **Examples**: [examples/README.md](examples/README.md)
- **Project specs**: [specs/](https://github.com/TokenFleet-AI/llm-bridge-rust/tree/master/specs)
- **Research docs**: [docs/](https://github.com/TokenFleet-AI/llm-bridge-rust/tree/master/docs)
- **Server crate**: [apps/server](https://github.com/TokenFleet-AI/llm-bridge-rust/tree/master/apps/server)
## Versioning
This crate follows semantic versioning. Breaking changes to the transform API bump the minor version while the major version is `0`.
## License
MIT