gproxy-protocol
English | 简体中文
HTTP/WebSocket types and host capability contracts for AI API gateways, SDKs, and protocol adapters in Rust. Keep HTTP metadata together with raw or typed bodies, and represent established WebSocket connections independently.
This is the v4 development API. It provides native models for model discovery, token counting, content generation and streaming, context management, embeddings, reranking, files, images, audio, video, and realtime sessions. HTTP metadata stays separate from typed bodies; WebSocket messages and their duplex connections are modeled independently. Independent capability traits describe upstream calls, resource access, and scoped state supplied by a host.
All declared fields and variants follow the referenced vendor API or official client schemas.
rest preserves unknown extensions; documented arbitrary JSON stays arbitrary only where the
source contract permits it. Bounded body codecs and conversion identity helpers are available;
network clients and semantic cross-format conversions remain separate implementation stages.
These types were written against the vendors' own published documentation, and the tests that read that documentation and check the types against it live with the documentation, in a separate repository, rather than here.
Installation
The Cargo dependency for the v4 prerelease is:
[]
= "4.0.0"
Until that version is published, use a local checkout of the repository's 4.0 branch:
[]
= { = "../gproxy/crates/gproxy-protocol" }
HTTP requests and responses
WireRequest<B> contains method, path, query, headers, and body.
WireResponse<B> contains status, headers, and body.
The body type can be raw HttpBody, parsed multipart, or a vendor's JSON body type.
use ;
use ;
let request = GetModelRequest ;
let body: Model = from_str.unwrap;
let response = GetModelResponse ;
assert_eq!;
assert_eq!;
Add serde_json = "1" to run this example. Serialize or deserialize the JSON body itself;
HTTP method, path, query, status, and headers remain outside it. Query strings retain their
original encoding, parameter order, and duplicate keys.
Vendor CountTokensRequest and CountTokensResponse aliases wrap their respective
CountTokensRequestBody and CountTokensResponseBody types in the same HTTP envelopes.
A model name stays in the JSON body when the vendor defines it there; Gemini's outer model
resource is carried in the request path.
Streaming, multipart, and WebSocket
HttpBody::Bytesholds a complete encoded body;HttpBody::Streamholds raw byte chunks. A chunk may split a UTF-8 character, JSON value, SSE event, or multipart boundary.StreamFramingdescribes SSE, an incremental JSON array, or NDJSON. It is format metadata, not a parser or an assumption about transport chunk boundaries.Multipartholds an ordered stream of parts. EachMultipartParthas its own headers and buffered or streaming body. Repeated field names are preserved.- WebSocket handshakes use HTTP requests and responses. The established
WebSocketexposes independent incoming and outgoing streams/sinks, carrying text, binary, ping, pong, and close. It is not an HTTP body variant. Transport adapters handle connection setup and wire framing.
Body codecs
codec provides bounded JSON, SSE, JSON-array, NDJSON, and multipart encoding/decoding.
Pass explicit CodecLimits; transport chunks may split UTF-8, JSON tokens, MIME headers,
or boundaries. Decoders reject malformed or truncated input and enforce cumulative body limits.
| Codec | Incremental interface |
|---|---|
| JSON array / NDJSON | push decoded values as they complete, then finish |
| SSE | SseDecoder::push yields data events or [DONE]; control-only blocks update ID/retry settings |
| Multipart | MultipartDecoder::next_part returns headers and a real streaming body |
Consume each multipart body to EOF or drop it before requesting the next part. The returned
body also drives the input stream, including on wasm with non-Send input. MultipartEncoder
accepts a Multipart part stream, so re-encoding does not require collecting file contents.
SSE uses strict EOF validation: an unterminated data event is an error, not a completed event.
These codecs interpret framing and JSON syntax; vendor semantics belong to typed converters.
Typed generation payloads
Vendor types live under wire::{claude, gemini, openai}. The root claude, gemini, and
openai modules re-export the same types for compatibility.
| API | Request and buffered response | Stream payload |
|---|---|---|
| Claude Messages | wire::claude::generate_content |
wire::claude::stream::StreamEvent |
| Gemini | wire::gemini::generate_content |
wire::gemini::stream::StreamChunk |
| OpenAI Responses | wire::openai::responses::{generate, response} |
wire::openai::responses::stream::StreamEvent |
| OpenAI Chat Completions | wire::openai::chat::{request, response} |
wire::openai::chat::stream::ChatCompletionChunk |
Stream response aliases carry ByteStream. A caller decodes SSE or JSON-array framing and
then deserializes each JSON payload into the corresponding type. These types do not parse
network chunks, accumulate model output, or convert between APIs. Non-JSON transport markers
such as Chat Completions' [DONE] remain the caller's framing responsibility.
Other API models
| Area | Modules |
|---|---|
| Context and conversations | wire::openai::{compact, conversation, memory} |
| Codex client endpoints | wire::openai::{guardian, web_search} |
| Embeddings and reranking | wire::openai::{embeddings, rerank}, wire::gemini::embeddings |
| File operations | wire::openai::files, wire::claude::files, wire::gemini::files |
| Image and audio operations | wire::openai::{images, audio} |
| Video | wire::openai::video (extended and native formats are distinct) |
| Responses WebSocket | wire::openai::responses::websocket |
| Realtime / WebRTC | wire::openai::realtime, wire::gemini::live |
| Gemini voices | wire::gemini::voices (snake_case create/list/get/delete envelopes and query fields) |
Gemini's October 1, 2026 documentation audit adds custom voice selectors,
speech metadata, agentic video processing, transcription settings/results,
Computer Use controls and current safety/finish reasons. Shared audio settings
live in wire::gemini::audio; existing Live exports remain available. Voices
types do not add gateway routes. Interactions-only Omni, music and managed-agent
operations remain outside the modeled GenerateContent dialect.
Multipart forms expose typed metadata and actual MultipartPart file streams. Download
responses retain raw bodies. No model performs uploads, fetches result URLs, or converts
between the native and extended video formats.
spec::OPERATION_SPECS describes the modeled operation/dialect combinations and their wire
formats. It does not assert that a provider supports an operation or that a conversion exists.
TLS, ALPN and HTTP/2 client configuration belong to outbound client implementations.
Host capabilities
capability defines three independent traits for hosts that supply protocol adapters with
upstream calls, resources, or scoped state. An adapter can compose multiple calls without
depending on a complete gateway core.
| Trait | Operations |
|---|---|
Upstream |
Send encoded HTTP requests; connect WebSocket with the handshake response |
ResourceAccess |
Resolve/read resources; publish, recover publication status, and release owned publications |
StateStore |
Read scoped state; atomically create, replace, or delete it with compare-and-exchange |
ResourceReference distinguishes an upstream ID from a URL. Publishing specifies the required
form and a future expiry; unsupported forms fail before publication. Released or expired
publications retain their operation ID as expired, so retries cannot recreate them.
The host supplies destinations, authentication, ownership checks and bound CapabilityLimits.
It must enforce time and byte limits throughout transfer, including after returning a stream.
CapabilityFuture requires Send on native targets and permits non-Send futures on wasm.
There is no dependency on a particular async runtime, HTTP client, or database.
A non-success HTTP status remains a WireResponse; transport/storage failures use
CapabilityError and preserve the original error source. A rejected WebSocket handshake
retains its complete HTTP response body. Cancellation stops local processing and does not
promise remote rollback; a publication operation can be queried by its scoped operation ID.
These are contracts for host implementations. This crate does not yet ship capability
implementations, semantic converters, or a gateway core. Conversion uses declared wire
fields only: it must neither read source rest fields nor populate target rest fields.
Operations and dialects
Use OperationKey { operation, dialect } to identify an API operation and its wire format.
Keys implement Copy, Eq, Hash, and Ord for use in maps and sets.
| Dialect | Content generation API |
|---|---|
OpenAi |
OpenAI Responses |
Claude |
Anthropic Messages |
Gemini |
Gemini GenerateContent |
OpenAiChat |
OpenAI Chat Completions |
OpenAiResponsesWebSocket |
OpenAI Responses over WebSocket |
The dialect identifies the format independently of which service hosts the model. Constructing a key does not check conversion or backend support.
String identifiers
Operation and Dialect support parsing and stable string identifiers. Their Serde
representations use the same strings:
use ;
let operation: Operation = "generate_content".parse.unwrap;
let dialect: Dialect = "openai_chat".parse.unwrap;
assert_eq!;
assert_eq!;
assert_eq!;
assert!;
Features
No features are enabled by default. Public enums are non_exhaustive; include a wildcard arm
when matching them. The exhaustive feature removes that restriction, so adding enum variants
may require updates to your matches when upgrading.
License
MIT.
Compatible-provider model metadata
OpenAI model objects expose the fixed v3 GPROXY model metadata extensions; creation time and ownership may be absent for compatible catalogs. No UA-dependent catalog projection is performed by the protocol or Codex channel.
Compatible-provider reasoning
Chat assistant history, responses and stream deltas declare DeepSeek reasoning_content
and OpenRouter reasoning / reasoning_details (text, summary and encrypted variants).
Native round trips retain their spellings, empty/null values and structured details.
Stream collection joins detail fragments by index or ID and stream synthesis emits the
collected details. Cross-dialect visible reasoning chooses one text spelling before
falling back to text/summary details, avoiding duplicate text when providers send both.
Claude thinking, Gemini thought text and Responses reasoning map to reasoning_content;
plain Chat reasoning maps to their corresponding response/stream content types.
Responses reasoning IDs, summary/text parts and ciphertext travel in reasoning_details
with format=openai-responses-v1; Claude thinking text/signatures and redacted blocks use
format=anthropic-claude-v1. Request and response conversions restore matching formats,
never reinterpret another format's ciphertext. The format tag is a wire discriminator,
not cryptographic verification; the caller must route replay to the original provider/model.
Streaming retains complete reasoning details until the block/response is complete; when
converting Chat back to native formats, reasoning is emitted at choice completion so late
IDs/signatures cannot change an already emitted item. Ordinary answer text remains incremental.
Text/summary fragments append; encrypted data and signatures are snapshots, so repeated
snapshots do not concatenate ciphertext. Plain Chat reasoning without matching signed details
still produces an empty Claude signature. Direct Responses/Claude conversions keep their
existing original-bound state restoration path.