Bridge API
Complete API reference for the Bridge class - the core component for connecting frontend and backend adapters.
Constructor
Section titled “Constructor”new Bridge(frontend, backend, config?)
Section titled “new Bridge(frontend, backend, config?)”Creates a new Bridge instance.
Parameters:
frontend: FrontendAdapter- Adapter for parsing input formatbackend: BackendAdapter | Router- Adapter (or router) that executes requests against an AI providerconfig?: Partial<BridgeConfig>- Optional configuration (see BridgeConfig)
Returns: Bridge instance
Example:
import { Bridge } from '@johnhenry/aimatey-core';import { OpenAIFrontendAdapter } from '@johnhenry/aimatey-frontend/openai';import { AnthropicBackendAdapter } from '@johnhenry/aimatey-backend/anthropic';
const bridge = new Bridge( new OpenAIFrontendAdapter(), new AnthropicBackendAdapter({ apiKey: process.env.ANTHROPIC_API_KEY }), { timeout: 30000, retries: 3 });Methods
Section titled “Methods”chat(request, options?)
Section titled “chat(request, options?)”Execute a chat completion request.
Parameters:
request: any- Request in frontend adapter formatoptions?: RequestOptions- Per-request overrides (see RequestOptions)
Returns: Promise<any> - Response in frontend adapter format
Throws:
ProviderError- If the provider API returns an errorNetworkError- If the network request failsValidationError- If request validation failsAdapterError(CONNECTION_TIMEOUT/PROVIDER_TIMEOUT) - If the request times outAdapterError(ROUTING_FAILED) - Ifoptions.backendnames a backend that is not registered
Example:
const response = await bridge.chat({ model: 'gpt-4', messages: [ { role: 'user', content: 'Hello!' } ]});
console.log(response.choices[0].message.content);chatStream(request, options?)
Section titled “chatStream(request, options?)”Execute a streaming chat completion request.
Parameters:
request: any- Request in frontend adapter format (withstream: true)options?: RequestOptions- Per-request overrides (see RequestOptions)
Returns: AsyncGenerator<any> - Stream of chunks in frontend adapter format. chatStream()
is an async generator, so it returns the stream synchronously - do not await the call itself.
Throws:
ProviderError- If the provider API returns an errorStreamError- If stream parsing failsValidationError- If request validation failsAdapterError(ROUTING_FAILED) - Ifoptions.backendnames a backend that is not registered
Example:
const stream = bridge.chatStream({ model: 'gpt-4', messages: [{ role: 'user', content: 'Count to 10' }], stream: true});
for await (const chunk of stream) { const content = chunk.choices?.[0]?.delta?.content; if (content) { process.stdout.write(content); }}use(middleware)
Section titled “use(middleware)”Add middleware to the bridge.
Parameters:
middleware: Middleware- Middleware to add to the chain
Returns: Bridge (for chaining)
Example:
import { createLoggingMiddleware, createCachingMiddleware } from '@johnhenry/aimatey-middleware';
bridge .use(createLoggingMiddleware({ level: 'info' })) .use(createCachingMiddleware({ ttl: 3_600_000 })); // ttl is millisecondsexecuteIR(request, options?)
Section titled “executeIR(request, options?)”Execute a request using the IR format directly (bypassing the frontend adapter).
Parameters:
request: IRChatRequest- Request in IR formatoptions?: RequestOptions- Per-request overrides (see RequestOptions)
Returns: Promise<IRChatResponse> - Response in IR format
Example:
const irResponse = await bridge.executeIR({ messages: [{ role: 'user', content: 'Hello' }], parameters: { model: 'claude-haiku-4-5-20251001', temperature: 0.7 }, metadata: { requestId: 'req_1', timestamp: Date.now() }});
console.log(irResponse.message.content);executeIRStream(request, options?)
Section titled “executeIRStream(request, options?)”Execute a streaming request using IR format directly. This is an async generator - it returns the stream synchronously, so the call itself is not awaited.
Parameters:
request: IRChatRequest- Request in IR format with streaming enabledoptions?: RequestOptions- Per-request overrides (see RequestOptions)
Returns: IRChatStream - AsyncGenerator<IRStreamChunk>
Example:
const stream = bridge.executeIRStream({ messages: [{ role: 'user', content: 'Hello' }], parameters: { model: 'claude-haiku-4-5-20251001' }, metadata: { requestId: 'req_2', timestamp: Date.now() }, stream: true});
for await (const chunk of stream) { if (chunk.type === 'content') { process.stdout.write(chunk.delta); }}clone(config)
Section titled “clone(config)”frontend and backend are readonly - a bridge cannot be re-pointed at a
different adapter after construction. To run the same request through another
provider, build a second bridge; to change only configuration, clone() returns
a new bridge with the same adapters and middleware and a merged config.
Parameters:
config: Partial<BridgeConfig>- Configuration overrides
Returns: Bridge - A new bridge instance
Example:
import { OpenAIBackendAdapter } from '@johnhenry/aimatey-backend/openai';
// Same adapters, longer timeoutconst patientBridge = bridge.clone({ timeout: 120000 });
// A different backend means a new bridgeconst openaiBridge = new Bridge( bridge.frontend, new OpenAIBackendAdapter({ apiKey: process.env.OPENAI_API_KEY }));on(event, handler)
Section titled “on(event, handler)”Subscribe to bridge events.
Parameters:
event: BridgeEventType | '*'- Event name, or'*'for every eventhandler: BridgeEventListener- Event handler function
Returns: Bridge (for chaining). off() and once() have the same shape.
Events:
BridgeEventType declares eleven event names (see BridgeEventType),
but only these six are emitted by Bridge today:
request:start- Fired before a non-streaming request is sentrequest:success- Fired after a successful non-streaming responserequest:error- Fired when a non-streaming request failsstream:start- Fired when streaming startsstream:complete- Fired when streaming finishesstream:error- Fired when a stream fails
The remaining names (request:cancelled, stream:chunk, backend:selected,
backend:failover, middleware:executed) are reserved and never fire.
Example:
bridge.on('request:start', (event) => { console.log('Request:', event.request.messages);});
bridge.on('request:success', (event) => { console.log('Response tokens:', event.response?.usage?.totalTokens);});
bridge.on('request:error', (event) => { console.error('Error:', event.error?.message);});Properties
Section titled “Properties”frontend
Section titled “frontend”Type: FrontendAdapter
Read-only
The frontend adapter instance the bridge was constructed with.
backend
Section titled “backend”Type: BackendAdapter | Router
Read-only
The backend adapter (or router) the bridge was constructed with.
config
Section titled “config”Type: BridgeConfig
Read-only
The resolved bridge configuration.
getMiddleware() / getStreamingMiddleware()
Section titled “getMiddleware() / getStreamingMiddleware()”There is no middlewares property. The registered middleware is read back through
methods instead:
bridge.getMiddleware(); // readonly Middleware[]bridge.getStreamingMiddleware(); // readonly StreamingMiddleware[]BridgeConfig
Section titled “BridgeConfig”Configuration options for Bridge, passed as the third constructor argument.
interface BridgeConfig { /** Enable debug mode with detailed logging (default: false) */ debug?: boolean;
/** Global request timeout in milliseconds (default: 30000) */ timeout?: number;
/** Maximum retries for transient failures (default: 0) */ retries?: number;
/** Default model to use if the request does not specify one */ defaultModel?: string;
/** Router configuration, when the backend is a Router */ routerConfig?: Partial<RouterConfig>;
/** Add a request ID to metadata if not present (default: true) */ autoRequestId?: boolean;
/** Custom configuration options */ custom?: Record<string, unknown>;}RequestOptions
Section titled “RequestOptions”Per-request overrides, passed as the second argument to chat() and chatStream().
interface RequestOptions { /** Request timeout in milliseconds */ timeout?: number;
/** AbortSignal for request cancellation */ signal?: AbortSignal;
/** Backend override, by registered name (router only) */ backend?: string;
/** Additional metadata merged into `metadata.custom` */ metadata?: Record<string, unknown>;
/** Skip middleware execution for this request (default: false) */ skipMiddleware?: boolean;
/** Custom request options */ custom?: Record<string, unknown>;}Per-request backend selection
Section titled “Per-request backend selection”backend picks a specific backend for one request, by the name it was registered
under. It only applies when the bridge’s backend is a Router - with a single
backend adapter there is no routing to override and the option is ignored.
const router = new Router({ routingStrategy: 'explicit', defaultBackend: 'openai' });router.register('openai', new OpenAIBackendAdapter({ apiKey: process.env.OPENAI_API_KEY }));router.register('anthropic', new AnthropicBackendAdapter({ apiKey: process.env.ANTHROPIC_API_KEY }));
const bridge = new Bridge(new OpenAIFrontendAdapter(), router);
// Routed by the router's configured strategyawait bridge.chat({ model: 'gpt-4', messages });
// Forced to Anthropic for this request onlyawait bridge.chat({ model: 'gpt-4', messages }, { backend: 'anthropic' });The bridge writes the override onto metadata.custom.backend, which is the channel
the router reads its explicit routing decision from. It is applied last, so it takes
precedence over a metadata.custom.backend already present on the request and over a
backend key passed through options.metadata.
A name that is not registered is rejected up front with an AdapterError carrying
ErrorCode.ROUTING_FAILED, so a typo cannot be served by a different provider:
await bridge.chat(request, { backend: 'antropic' });// AdapterError: Requested backend 'antropic' is not registered.// Registered backends: openai, anthropicA backend that is registered but is currently unhealthy or has an open circuit breaker is not an error - the router’s normal fallback applies.
BridgeEventType
Section titled “BridgeEventType”The event names accepted by on() / off() / once(). (BridgeEvent is a
different thing - the interface describing an event object.)
type BridgeEventType = | 'request:start' | 'request:success' | 'request:error' | 'request:cancelled' | 'stream:start' | 'stream:chunk' | 'stream:complete' | 'stream:error' | 'backend:selected' | 'backend:failover' | 'middleware:executed';Only request:start, request:success, request:error, stream:start,
stream:complete and stream:error are actually emitted.
Error Handling
Section titled “Error Handling”Bridge can throw the following errors:
Every error thrown by a bridge derives from AdapterError, which carries a
machine-readable code, a category, and an isRetryable flag.
ProviderError
Section titled “ProviderError”Thrown when the provider API returns an error response.
import { AdapterError, ProviderError } from '@johnhenry/aimatey-errors';
try { await bridge.chat(request);} catch (error) { if (error instanceof ProviderError) { console.log('Provider failed:', error.provenance.backend); console.log('Code:', error.code); console.log('Message:', error.message); } else if (error instanceof AdapterError) { console.log('Adapter error:', error.code, error.category); }}ValidationError
Section titled “ValidationError”Thrown when request validation fails. The per-field detail lives on
validationDetails.
import { ValidationError } from '@johnhenry/aimatey-errors';
try { await bridge.chat(invalidRequest);} catch (error) { if (error instanceof ValidationError) { console.log('Validation failed:', error.validationDetails); }}Timeouts
Section titled “Timeouts”There is no TimeoutError class. A timeout surfaces as an AdapterError whose
code is CONNECTION_TIMEOUT (the network call timed out) or PROVIDER_TIMEOUT
(the provider reported a timeout).
import { AdapterError, ErrorCode } from '@johnhenry/aimatey-errors';
const bridge = new Bridge(frontend, backend, { timeout: 5000 });
try { await bridge.chat(request);} catch (error) { if ( error instanceof AdapterError && (error.code === ErrorCode.CONNECTION_TIMEOUT || error.code === ErrorCode.PROVIDER_TIMEOUT) ) { console.log('Request timed out after 5s'); }}Advanced Usage
Section titled “Advanced Usage”Custom Backend Adapter
Section titled “Custom Backend Adapter”import type { BackendAdapter, AdapterMetadata, IRChatRequest, IRChatResponse} from '@johnhenry/aimatey-types';
class CustomBackend implements BackendAdapter { readonly metadata: AdapterMetadata = { name: 'custom', version: '1.0.0', provider: 'Custom', capabilities: { streaming: true, multiModal: false, systemMessageStrategy: 'in-messages', supportsMultipleSystemMessages: true } };
fromIR(request: IRChatRequest): unknown { return request; }
toIR(_response: unknown, originalRequest: IRChatRequest): IRChatResponse { return { message: { role: 'assistant', content: 'Custom response' }, finishReason: 'stop', usage: { promptTokens: 5, completionTokens: 5, totalTokens: 10 }, metadata: originalRequest.metadata }; }
async execute(request: IRChatRequest): Promise<IRChatResponse> { return this.toIR(null, request); }
async *executeStream(request: IRChatRequest) { yield { type: 'start' as const, sequence: 0, metadata: request.metadata }; yield { type: 'content' as const, sequence: 1, delta: 'Custom' }; yield { type: 'done' as const, sequence: 2, finishReason: 'stop' as const }; }}
const bridge = new Bridge( new OpenAIFrontendAdapter(), new CustomBackend());Middleware Stack
Section titled “Middleware Stack”import { createLoggingMiddleware, createCachingMiddleware, createRetryMiddleware, createCostTrackingMiddleware} from '@johnhenry/aimatey-middleware';
const bridge = new Bridge(frontend, backend);
// Add middleware in orderbridge .use(createLoggingMiddleware({ level: 'info' })) .use(createRetryMiddleware({ maxAttempts: 3 })) .use(createCachingMiddleware({ ttl: 3_600_000 })) // ttl is milliseconds .use(createCostTrackingMiddleware({ dailyThreshold: 100 }));See Also
Section titled “See Also”- Router API - Multi-backend routing
- Middleware API - Middleware reference
- Error Handling - Error types
- Tutorial: Simple Bridge - Getting started
- Examples - Code examples