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:

TierWire styleFactoryStreamingReconnect
0 -- HTTPRequest-responsecreateHttpTransport()No (synthetic events)No
1 -- SSE/ProxyPOST + server-sent eventscreateProxyTransport() / createSessionApiTransport()YesVia idempotency replay
2 -- WebSocketFull duplexcreateWebSocketTransport()YesYes (reconnect + resume)
Voice WebSocketFull duplex audio + controlcreateVoiceWebSocketTransport()Yes (audio frames)Yes
WebRTC voice (stub)SDP + data channelscreateWebRTCVoiceTransport()Throws VOICE_PROVIDER_UNAVAILABLE in v1
Agent-graph wrapperWraps any tier abovecreateAgentGraphTransport({ graph, fallback })Inherits from fallbackInherits 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:

ModeBehavior
resumeSend a cursor to the server; server replays only unseen events. Requires tier-2 transport. Falls back to replay if unsupported.
replayRe-send the same request with the same idempotency key. The server's idempotency cache returns the in-progress or completed response.
noneDiscard 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.
  • protocolVersion is set.
  • stream() returns an AsyncIterable<TransportEvent> that respects AbortSignal.
  • 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

Source: docs/concepts/transports.md