Transports
A transport is the pluggable layer that connects the SDK to a backend. It implements the ChatTransport interface: open a connection, send messages, stream responses, and close.
You pick a transport based on what your backend supports. The rest of the SDK (sessions, message normalization, React adapter) works identically regardless of which transport you use.
Transport tiers
The SDK ships transports at three capability levels:
| Tier | Wire style | Factory | Streaming | Reconnect |
|---|---|---|---|---|
| 0 -- HTTP | Request-response | createHttpTransport() | No (synthetic events) | No |
| 1 -- SSE/Proxy | POST + server-sent events | createProxyTransport() / createSessionApiTransport() | Yes | Via idempotency replay |
| 2 -- WebSocket | Full duplex | createWebSocketTransport() | Yes | Yes (reconnect + resume) |
| Voice WebSocket | Full duplex audio + control | createVoiceWebSocketTransport() | Yes (audio frames) | Yes |
| WebRTC voice (stub) | SDP + data channels | createWebRTCVoiceTransport() | Throws VOICE_PROVIDER_UNAVAILABLE in v1 | — |
| Agent-graph wrapper | Wraps any tier above | createAgentGraphTransport({ graph, fallback }) | Inherits from fallback | Inherits from fallback |
Tier 0 posts JSON and gets a single JSON response. The transport replays it as synthetic response.started, text.delta, and response.completed events so your code sees the same event stream as higher tiers. Use this when your infra cannot hold connections open (e.g. API Gateway with a 30-second limit).
Tier 1 posts a request and reads a server-sent event (SSE) stream. This is the most common production setup. createProxyTransport() targets a proxy server you control; createSessionApiTransport() targets the Google CES API directly.
Tier 2 opens a persistent WebSocket. Messages flow both directions without new HTTP requests. The transport supports both reconnect() (re-open the socket) and resumeStream() (resume a partially-delivered turn from a cursor). Use this for lowest-latency, real-time experiences.
Mock transport
For local development, createMockTransport() simulates realistic streaming with no backend:
import { createMockTransport } from 'gecx-chat/testing';
const transport = createMockTransport({
latencyMs: 50,
scenarios: [
{
id: 'greeting',
trigger: 'hello',
steps: [
{ event: { type: 'response.started', responseId: '1', requestId: '1', timestamp: '' } },
{ event: { type: 'text.delta', delta: 'Hi there!', responseId: '1', requestId: '1', timestamp: '' } },
{ event: { type: 'response.completed', responseId: '1', requestId: '1', timestamp: '' } },
],
},
],
});
const client = createChatClient({ transport });
The mock transport matches incoming messages against scenario triggers. If no scenario matches, it echoes the user's message back as a streamed response.
Recovery
When a transport connection drops mid-turn, the SDK automatically attempts to recover using a configurable RecoveryPolicy:
const client = createChatClient({
transport,
recovery: {
maxAttempts: 5, // retry up to 5 times
initialBackoffMs: 500, // start at 500ms
maxBackoffMs: 15_000, // cap at 15 seconds
jitter: 'equal', // randomize to avoid stampedes
resumeMode: 'resume', // pick up where we left off
},
});
Resume modes:
| Mode | Behavior |
|---|---|
resume | Send a cursor to the server; server replays only unseen events. Requires tier-2 transport. Falls back to replay if unsupported. |
replay | Re-send the same request with the same idempotency key. The server's idempotency cache returns the in-progress or completed response. |
none | Discard the in-flight turn. The caller must re-send. |
The session emits reconnecting and reconnected events so your UI can show connection status.
Building a custom transport
Implement the ChatTransport interface to connect to any backend:
interface ChatTransport {
connect(sessionId: string): Promise<void>;
send(request: SendRequest): Promise<void>;
stream(request: SendRequest, signal?: AbortSignal): AsyncIterable<TransportEvent>;
close(): Promise<void>;
// Optional:
reconnect?(reason: string): Promise<void>;
resumeStream?(cursor: TurnCursor, signal?: AbortSignal): AsyncIterable<TransportEvent>;
}
Set capabilities on your transport to tell the SDK what recovery primitives it supports. Without it, the SDK defaults to server-stream class with no reconnect or resume.
Contract testing custom transports
runTransportContractTests(transport) from gecx-chat/testing/vitest is a reusable Vitest-friendly harness that exercises the ChatTransport contract end to end:
connect()accepts a session id and resolves.- Capability class advertised on the transport matches its actual behaviour.
protocolVersionis set.stream()returns anAsyncIterable<TransportEvent>that respectsAbortSignal.close()resolves.- For tier-2 transports, conditional reconnect/resume behaviour matches the contract.
The bundled mockTransport is itself validated by this harness. Use it to validate any custom transport before shipping. See Testing.
What's next
- Sessions & Lifecycle -- how sessions use transports to send and receive
- Auth & Security -- how tokens reach the transport
- Error Knowledge Base -- transport error codes and remediation
docs/concepts/transports.md